Javaが静的初期化ブロックからチェック済み例外をスローできないのはなぜですか?この設計決定の背後にある理由は何ですか?
ソースでこれらのチェック済み例外を処理することはできないためです。初期化プロセスを制御することはできず、ソースからstatic {}ブロックを呼び出すことはできないため、try-catchでブロックを囲むことができます。
チェック例外が示すエラーは処理できないため、チェック例外静的ブロックのスローを禁止することにしました。
静的ブロックはchecked例外をスローしてはなりませんが、未チェック/ランタイム例外をスローできます。ただし、上記の理由によると、これらのいずれも処理できません。
要約すると、この制限により、開発者がアプリケーションを回復できないエラーが発生する可能性のあるものを開発者が作成するのを防ぎます(または少なくとも難しくします)。
チェック例外をキャッチし、未チェック例外として再スローすることで、問題を回避できます。この未チェックの例外クラスは、ラッパーとしてうまく機能します: Java.lang.ExceptionInInitializerError
。
サンプルコード:
protected static class _YieldCurveConfigHelperSingleton {
public static YieldCurveConfigHelper _staticInstance;
static {
try {
_staticInstance = new YieldCurveConfigHelper();
}
catch (IOException | SAXException | JAXBException e) {
throw new ExceptionInInitializerError(e);
}
}
}
このように見える必要があります(これはnot valid Java code)
// Not a valid Java Code
static throws SomeCheckedException {
throw new SomeCheckedException();
}
しかし、あなたはそれをキャッチする場所でどのように広告しますか?チェックされた例外にはキャッチが必要です。クラスを初期化する可能性のあるいくつかの例を想像してください(または、既に初期化されているためではないかもしれません)。導入する複雑さの注意を引くために、別の静的イニシャライザーに例を入れます。
static {
try {
ClassA a = new ClassA();
Class<ClassB> clazz = Class.forName(ClassB.class);
String something = ClassC.SOME_STATIC_FIELD;
} catch (Exception oops) {
// anybody knows which type might occur?
}
}
そしてもう一つの厄介なこと-
interface MyInterface {
final static ClassA a = new ClassA();
}
ClassAにチェック済み例外をスローする静的イニシャライザーがあると想像してください。この場合、MyInterface(「隠された」静的イニシャライザーを持つインターフェース)は例外をスローするか、処理する必要があります。そのままにしておく方が良いでしょう。
Javaが静的初期化ブロックからチェック済み例外をスローできないのはなぜですか?
技術的には、これを行うことができます。ただし、チェックされた例外はブロック内でキャッチする必要があります。チェック済み例外は、ブロック外へのpropagateを許可されません。
技術的には、未チェックの例外が静的初期化ブロックから伝播することを許可することも可能です1。しかし、これを意図的に行うのは本当に悪い考えです!問題は、JVM自体が未チェックの例外をキャッチし、それをラップしてExceptionInInitializerError
として再スローすることです。
注意:これはError
であり、通常の例外ではありません。それから回復しようとするべきではありません。
ほとんどの場合、例外はキャッチできません。
public class Test {
static {
int i = 1;
if (i == 1) {
throw new RuntimeException("Bang!");
}
}
public static void main(String[] args) {
try {
// stuff
} catch (Throwable ex) {
// This won't be executed.
System.out.println("Caught " + ex);
}
}
}
$ Java Test
Exception in thread "main" Java.lang.ExceptionInInitializerError
Caused by: Java.lang.RuntimeException: Bang!
at Test.<clinit>(Test.Java:5)
ExceptionInInitializerError
をキャッチするために上記のtry ... catch
を配置できる場所はありません2。
場合によってはキャッチできます。たとえば、Class.forName(...)
を呼び出してクラスの初期化をトリガーした場合、try
で呼び出しを囲み、ExceptionInInitializerError
または後続のNoClassDefFoundError
をキャッチできます。
ただし、ExceptionInInitializerError
からrecoverを実行しようとすると、ロードブロッキングに陥りやすくなります。問題は、エラーをスローする前に、JVMが問題の原因となったクラスを「失敗」としてマークすることです。単にそれを使用することはできません。さらに、失敗したクラスに依存する他のクラスも、初期化を試みると失敗状態になります。前進する唯一の方法は、失敗したすべてのクラスをアンロードすることです。そのmightは動的にロードされたコードに適している3、しかし一般的にはそうではありません。
1-静的ブロック無条件が未チェックの例外をスローした場合、コンパイルエラーです。
2-あなたはmightデフォルトのキャッチされていない例外ハンドラを登録することでそれをインターセプトすることができますが、「メイン」スレッドを開始できません。
3-失敗したクラスを回復したい場合は、それらをロードしたクラスローダーを取り除く必要があります。
この設計決定の背後にある理由は何ですか?
cannotが処理できない例外をスローするコードを書くことからプログラマを保護するためです!
これまで見てきたように、静的初期化子の例外は、典型的なアプリケーションをブリックに変えます。言語設計者ができると思う最も良いことは、チェックされたケースをコンパイルエラーとして扱うことです。 (残念ながら、未チェックの例外に対してもこれを行うのは実用的ではありません。)
それでは、コードが静的イニシャライザで例外をスローする必要がある場合はどうすればよいでしょうか。基本的に、2つの選択肢があります。
ブロック内の例外から(完全に)回復できる場合は、それを行います。
それ以外の場合は、静的初期化ブロック(または静的変数の初期化子)で初期化が行われないようにコードを再構築します。
Java言語仕様 を見てください:静的初期化子の場合、コンパイル時エラーであると述べられています 失敗する チェックされた例外を使用して、突然完了することができます。
記述するコードは静的初期化ブロックを呼び出すことができないため、checked exceptions
をスローすることは役に立ちません。可能であれば、チェックされた例外がスローされたときにjvmは何をしますか? Runtimeexceptions
は伝播されます。
例:SpringのDispatcherServlet(org.springframework.web.servlet.DispatcherServlet)は、チェック例外をキャッチし、別の未チェック例外をスローするシナリオを処理します。
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}