Java Language
例外と例外処理
サーチ…
前書き
Throwable
型のオブジェクトとそのサブタイプは、 throw
キーワードでスタックに送信し、 try…catch
文でtry…catch
ことができます。
構文
void someMethod()throws SomeException {} //メソッド宣言、SomeExceptionがチェックされた例外の型である場合にメソッド呼び出し側に強制的にキャッチさせる
試して{
someMethod(); //code that might throw an exception
}
catch(SomeException e){
System.out.println("SomeException was thrown!"); //code that will run if certain exception (SomeException) is thrown
}
最後に {
//code that will always run, whether try block finishes or not
}
try-catchを使って例外をキャッチする
try...catch
ステートメントを使用して例外をキャッチして処理できます。 (実際にtry
ステートメントはtry...catch...finally
とtry-with-resources
に関する他の例で説明されているように、他の形式をとっています)。
1つのキャッチブロックで試してみる
最もシンプルなフォームは次のようになります。
try {
doSomething();
} catch (SomeException e) {
handle(e);
}
// next statement
単純なtry...catch
の動作は次のとおりです。
-
try
ブロック内のステートメントが実行されます。 -
try
ブロック内のステートメントによって例外がスローされない場合、コントロールはtry...catch
あとに次のステートメントに渡されtry...catch
。 -
try
ブロック内に例外がスローされた場合- 例外オブジェクトは、
SomeException
またはサブタイプのインスタンスかどうかを調べるためにテストされます。 - そうであれば、
catch
ブロックは例外をキャッチします:- 変数
e
は例外オブジェクトにバインドされています。 -
catch
ブロック内のコードが実行されます。 - そのコードが例外をスローすると、新しくスローされた例外が元の例外の代わりに伝播されます。
- それ以外の場合は、
try...catch
後に次の文に制御がtry...catch
ます。
- 変数
- そうでない場合、元の例外は引き続き伝播します。
- 例外オブジェクトは、
複数のキャッチで試してみる
try...catch
は複数のcatch
ブロックを持つこともできます。例えば:
try {
doSomething();
} catch (SomeException e) {
handleOneWay(e)
} catch (SomeOtherException e) {
handleAnotherWay(e);
}
// next statement
複数のcatch
ブロックがある場合、最初のcatch
ブロックから1回ずつ試行され、例外の一致が検出されます。対応するハンドラが(前述のように)実行され、 try...catch
文の次の文に制御が渡されます。 ハンドラコードが例外をスローしたとしても 、一致した後のcatch
ブロックは常にスキップされます 。
「トップダウン」マッチング戦略は、 catch
ブロック内の例外が互いに乖離していない場合に影響します。例えば:
try {
throw new RuntimeException("test");
} catch (Exception e) {
System.out.println("Exception");
} catch (RuntimeException e) {
System.out.println("RuntimeException");
}
このコードスニペットは、 "RuntimeException"ではなく "Exception"を出力します。 RuntimeException
はException
サブタイプであるため、最初の(より一般的な) catch
がマッチします。 2番目の(より具体的な) catch
は決して実行されません。
これから学ぶべき教訓は、(例外タイプに関して)最も具体的なcatch
ブロックが最初に現れ、最も一般的なcatch
ブロックが最後に現れるべきであるということです。 (一部のJavaコンパイラは、 catch
を実行できない場合に警告しますが、これはコンパイルエラーではありません)。
マルチ例外キャッチブロック
Java SE 7以降、単一のcatch
ブロックで無関係な例外のリストを処理できます。例外タイプが縦棒( |
)記号で区切られてリストされます。例えば:
try {
doSomething();
} catch (SomeException | SomeOtherException e) {
handleSomeException(e);
}
マルチ例外キャッチの動作は、単一例外の場合の単純な拡張です。 catch
れた例外は、スローされた例外がリストされた例外の少なくとも1つと一致する場合に一致します。
仕様にはさらに微妙なものがあります。 e
タイプは、リスト内の例外タイプの合成ユニオンです。 e
の値が使用されるとき、その静的型は型共用体の最も一般的な上位型です。ただし、 catch
ブロック内でe
が再スローされた場合、スローされる例外の型は共用体の型です。例えば:
public void method() throws IOException, SQLException
try {
doSomething();
} catch (IOException | SQLException e) {
report(e);
throw e;
}
上の例では、 IOException
とSQLException
は、最小の共通スーパータイプがException
例外を検査しException
。つまり、 report
メソッドはreport(Exception)
一致する必要がありreport(Exception)
。ただし、コンパイラはthrow
がIOException
またはSQLException
のみをthrow
できることを認識します。したがって、 throws Exception
ではなくthrows IOException, SQLException
としてmethod
を宣言できthrows Exception
。 (これは良いことです: Pitfall - Throwable Throwable、Exception、Error、またはRuntimeExceptionを参照してください)
例外のスロー
次の例は、例外をスローする基本を示しています。
public void checkNumber(int number) throws IllegalArgumentException {
if (number < 0) {
throw new IllegalArgumentException("Number must be positive: " + number);
}
}
例外は3行目にスローされます。このステートメントは2つの部分に分けることができます:
new IllegalArgumentException(...)
は、IllegalArgumentException
クラスのインスタンスを作成しており、例外が報告しているエラーを説明するメッセージが表示されます。throw ...
例外オブジェクトをスローします。
例外がスローされると、例外が処理されるまで、囲むステートメントが異常終了します 。これは他の例で説明されています。
上記のように、例外オブジェクトを作成して単一のステートメントにスローすることをお勧めします。また、プログラマーに問題の原因を理解させるために、意味のあるエラーメッセージを例外に含めることをお勧めします。ただし、必ずしもエンドユーザーに表示する必要があるメッセージではありません。 (初めに、Javaは例外メッセージの国際化を直接サポートしていません。)
いくつかの点を追加する必要があります:
checkNumber
をthrows IllegalArgumentException
として宣言しました。IllegalArgumentException
はチェックされた例外であるため、これは厳密には必要ではありません。 「 Java例外の階層 - 未チェックとチェック例外 」を参照してください。ただし、これを行うこと、またメソッドのjavadocコメントにスローされた例外を含めることは良い方法です。throw
文の直後のコードに到達できません 。したがって、もし私たちがこれを書いたら:throw new IllegalArgumentException("it is bad"); return;
コンパイラは
return
文のコンパイルエラーを報告します。
例外チェーニング
多くの標準例外には、従来のmessage
引数に加えて第2のcause
引数を持つコンストラクタがあります。 cause
により、例外を連鎖さcause
ことができます。ここに例があります。
最初に、回復不可能なエラーが発生したときにアプリケーションがスローする未チェックの例外を定義します。 cause
引数を受け入れるコンストラクタが含まれていることに注意してください。
public class AppErrorException extends RuntimeException {
public AppErrorException() {
super();
}
public AppErrorException(String message) {
super(message);
}
public AppErrorException(String message, Throwable cause) {
super(message, cause);
}
}
次に、例外チェーンを示すコードを示します。
public String readFirstLine(String file) throws AppErrorException {
try (Reader r = new BufferedReader(new FileReader(file))) {
String line = r.readLine();
if (line != null) {
return line;
} else {
throw new AppErrorException("File is empty: " + file);
}
} catch (IOException ex) {
throw new AppErrorException("Cannot read file: " + file, ex);
}
}
try
ブロック内のthrow
は問題を検出し、単純なメッセージで例外を報告します。対照的に、 catch
ブロック内のthrow
は新しい(チェックされた)例外でそれをラップすることによってIOException
処理しています。しかし、元の例外を捨てているわけではありません。 IOException
をcause
として渡すことで、スタックトレースの作成と読み込みで説明したように、スタックトレースに出力できるようにIOException
を記録します。
カスタム例外
ほとんどの状況下では、 Exception
をスローするときに既存の汎用Exception
クラスを使用する方がコード設計の観点からは簡単です。これは、単純なエラーメッセージを伝えるために例外が必要な場合に特に当てはまります。その場合、チェックされた例外ではないため、 RuntimeExceptionが通常は優先されます。一般的なクラスのエラーに対しては、他の例外クラスが存在します。
- UnsupportedOperationException - 特定の操作がサポートされていない
- IllegalArgumentException - 無効なパラメータ値がメソッドに渡された場合
- IllegalStateException - あなたのAPIは内部的には決して起こらない状態になっているか、APIを無効な方法で使用した結果発生します
カスタム例外クラスを使いたいんケースには次のものがあります。
- 他の人が使用するためのAPIまたはライブラリを作成しており、APIのユーザーがAPIの例外を特別に捕捉して処理できるようにしたい場合は、例外を他のより一般的な例外と区別することができます 。
- プログラムのある部分で特定の種類のエラーの例外をスローしています。この部分は、プログラムの別の部分で捕捉して処理するもので、他のより一般的なエラーと区別する必要があります。
チェックされていない例外に対してRuntimeException
を拡張するか、RuntimeExceptionのサブクラスでない Exception
を拡張してException
をチェックすることで、独自のカスタム例外を作成することができます。
RuntimeExceptionのサブクラスではないExceptionのサブクラスは、例外をチェックします。
public class StringTooLongException extends RuntimeException {
// Exceptions can have methods and fields like other classes
// those can be useful to communicate information to pieces of code catching
// such an exception
public final String value;
public final int maximumLength;
public StringTooLongException(String value, int maximumLength){
super(String.format("String exceeds maximum Length of %s: %s", maximumLength, value));
this.value = value;
this.maximumLength = maximumLength;
}
}
それらはあらかじめ定義された例外として使用することができます:
void validateString(String value){
if (value.length() > 30){
throw new StringTooLongException(value, 30);
}
}
また、例外がキャッチされて処理される場所でフィールドを使用できます。
void anotherMethod(String value){
try {
validateString(value);
} catch(StringTooLongException e){
System.out.println("The string '" + e.value +
"' was longer than the max of " + e.maximumLength );
}
}
オラクルのJavaドキュメンテーションによれば、次のことに注意してください。
[...]クライアントが例外からリカバリすることが合理的に予想される場合は、チェック例外にしてください。クライアントが例外から回復するために何もできない場合は、チェックされていない例外にしてください。
もっと:
try-with-resourcesステートメント
try-catch-finalステートメントの例が示すように、 finally
節を使用するリソースクリーンアップでは、エッジケースを正しく実装するためにかなりの量の "ボイラープレート"コードが必要です。 Java 7は、この問題をtry-with-resourcesステートメントの形で扱うもっと簡単な方法を提供します。
リソースとは何ですか?
Java 7はjava.lang.AutoCloseable
インタフェースを導入し、 try-with-resourcesステートメントを使用してクラスを管理できるようにしました。 AutoCloseable
を実装するクラスのインスタンスは、 リソースと呼ばれます。これらは通常、ガベージコレクタを処理するのではなく、適時に処分する必要があります。
AutoCloseable
インターフェイスは、単一のメソッドを定義します。
public void close() throws Exception
close()
メソッドはリソースを適切な方法で破棄する必要があります。この仕様では、すでに処分されたリソース上でメソッドを呼び出すことが安全であるべきと述べています。さらに、 Autocloseable
を実装するクラスは、 Exception
よりも具体的な例外をスローするようにclose()
メソッドを宣言することを強く推奨します。
幅広い標準JavaクラスとインタフェースがAutoCloseable
実装していAutoCloseable
。これらには、
-
InputStream
、OutputStream
およびそのサブクラス -
Reader
、Writer
、およびそれらのサブクラス -
Socket
とServerSocket
とそのサブクラス -
Channel
とそのサブクラス、および - JDBCインタフェース
Connection
、Statement
、ResultSet
、およびそれらのサブクラス
アプリケーションと第三者のクラスもこれを行う可能性があります。
基本的なtry-with-resourceステートメント
try-with-resourcesの構文は、古典的なtry-catch 、 try-finallyおよびtry-catch-finallyフォームに基づいています。ここには「基本的な」形式の例があります。 catch
ないフォーム、またはfinally
。
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
}
管理するリソースは、 try
節の後の(...)
セクションで変数として宣言されます。上記の例では、リソース変数stream
を宣言し、それを新しく作成されたPrintStream
初期化します。
リソース変数が初期化されると、 try
ブロックが実行されます。これが完了すると、 stream.close()
が自動的に呼び出され、リソースが漏れないことが保証されます。ブロックがどのように完了しても、 close()
コールが発生することに注意してください。
強化されたtry-with-resourceステートメント
pre-Java 7のtry-catch-finally構文のように、 try-with-resourcesステートメントをcatch
およびfinally
ブロックで拡張することができます。次のコードスニペットは、 PrintStream
コンストラクタがスローできるFileNotFoundException
を処理するために、以前のものにcatch
ブロックを追加します。
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
} catch (FileNotFoundException ex) {
System.err.println("Cannot open the file");
} finally {
System.err.println("All done");
}
リソースの初期化またはtryブロックが例外をスローすると、 catch
ブロックが実行されます。 finally
ブロックは、通常のtry-catch-finallyステートメントと同様に、常に実行されます。
しかし、注意すべきことがいくつかあります:
- リソース変数は
catch
およびfinally
ブロックのスコープ外です。 - ステートメントが
catch
ブロックに一致する前に、リソースのクリーンアップが行われます。 - 自動リソースクリーンアップが例外をスローした場合、それは
catch
ブロックの1つにcatch
れる可能性があります。
複数のリソースの管理
上記のコードスニペットは、管理されている単一のリソースを示しています。実際、 リソースの試行は、1つのステートメントで複数のリソースを管理できます。例えば:
try (InputStream is = new FileInputStream(file1);
OutputStream os = new FileOutputStream(file2)) {
// Copy 'is' to 'os'
}
これは期待通りに動作します。 try
ブロックの最後では、 is
とos
両方is
自動的に閉じられます。注意すべき点がいくつかあります。
- 初期化はコード順に行われ、後のリソース変数初期化子は以前のものの値を使用することができます。
- 正常に初期化されたリソース変数はすべてクリーンアップされます。
- リソース変数は、宣言の逆の順序でクリーンアップされます。
したがって、上記の例では、 is
前に初期化されているos
とした後にクリーンアップ、およびis
初期化中に例外がある場合にクリーンアップされるos
。
try-with-resourceと古典的なtry-catch-finallyの等価性
Java言語仕様では、 try-catch-finallyステートメントの観点からtry-with-resourceフォームの動作を指定しています。 (詳細はJLSを参照してください)
たとえば、この基本的なtry-with-resourceは次のとおりです 。
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
}
try-catch-finallyと同等に定義されています:
// Note that the constructor is not part of the try-catch statement
PrintStream stream = new PrintStream("hello.txt");
// This variable is used to keep track of the primary exception thrown
// in the try statement. If an exception is thrown in the try block,
// any exception thrown by AutoCloseable.close() will be suppressed.
Throwable primaryException = null;
// The actual try block
try {
stream.println("Hello world!");
} catch (Throwable t) {
// If an exception is thrown, remember it for the finally block
primaryException = t;
throw t;
} finally {
if (primaryException == null) {
// If no exception was thrown so far, exceptions thrown in close() will
// not be caught and therefore be passed on to the enclosing code.
stream.close();
} else {
// If an exception has already been thrown, any exception thrown in
// close() will be suppressed as it is likely to be related to the
// previous exception. The suppressed exception can be retrieved
// using primaryException.getSuppressed().
try {
stream.close();
} catch (Throwable suppressedException) {
primaryException.addSuppressed(suppressedException);
}
}
}
(JLSでは、実際のt
およびprimaryException
変数は通常のJavaコードには見えないと指定しています)。
try-with-resourcesの拡張形式は、基本形式と同等のものとして指定されています。例えば:
try (PrintStream stream = new PrintStream(fileName)) {
stream.println("Hello world!");
} catch (NullPointerException ex) {
System.err.println("Null filename");
} finally {
System.err.println("All done");
}
次のものと同等です。
try {
try (PrintStream stream = new PrintStream(fileName)) {
stream.println("Hello world!");
}
} catch (NullPointerException ex) {
System.err.println("Null filename");
} finally {
System.err.println("All done");
}
スタックトレースの作成と読み込み
例外オブジェクトが作成されると(つまり、 new
オブジェクトを作成するとき)、 Throwable
コンストラクタは例外が作成されたコンテキストに関する情報を取得します。その後、この情報をスタックトレースの形で出力することができます。スタックトレースは、最初に例外の原因となった問題の診断に役立ちます。
スタックトレースの印刷
スタックトレースの印刷はprintStackTrace()
メソッドを呼び出すだけです。例えば:
try {
int a = 0;
int b = 0;
int c = a / b;
} catch (ArithmeticException ex) {
// This prints the stacktrace to standard output
ex.printStackTrace();
}
引数のないprintStackTrace()
メソッドは、アプリケーションの標準出力に出力します。現在のSystem.out
です。 printStackTrace(PrintStream)
とprintStackTrace(PrintWriter)
も、指定されたStream
またはWriter
されます。
ノート:
stacktraceには、例外自体の詳細は含まれません。これらの詳細を取得するには、
toString()
メソッドを使用できます。例えば// Print exception and stacktrace System.out.println(ex); ex.printStackTrace();
Stacktrace印刷は控えめに使用する必要があります。 落とし穴 - 過度または不適切なスタックトレースを参照してください。ロギングフレームワークを使用し、ログに記録する例外オブジェクトを渡す方が良い場合がよくあります。
スタックトレースの理解
2つのファイルで2つのクラスからなる以下の簡単なプログラムを考えてみましょう。 (説明のためにファイル名と行番号を示しました。)
File: "Main.java"
1 public class Main {
2 public static void main(String[] args) {
3 new Test().foo();
4 }
5 }
File: "Test.java"
1 class Test {
2 public void foo() {
3 bar();
4 }
5
6 public int bar() {
7 int a = 1;
8 int b = 0;
9 return a / b;
10 }
これらのファイルをコンパイルして実行すると、次のような出力が得られます。
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Test.bar(Test.java:9)
at Test.foo(Test.java:3)
at Main.main(Main.java:3)
私たちに何を伝えているのかを理解するために、この行を一度に読んでみましょう。
1行目は、キャッチされない例外のために「main」というスレッドが終了したことを示しています。例外の完全な名前はjava.lang.ArithmeticException
で、例外メッセージは "/ by zero"です。
この例外のjavadocを調べると、次のようになります。
例外的な算術条件が発生したときにスローされます。たとえば、「ゼロで割る」整数は、このクラスのインスタンスをスローします。
実際、 "/ by zero"というメッセージは、例外の原因が、何らかのコードが何かを0で割り切ろうとしているという強い示唆です。しかし何?
残りの3行はスタックトレースです。各行は、コールスタック上のメソッド(またはコンストラクタ)コールを表し、それぞれが次の3つのことを示します。
- 実行されていたクラスとメソッドの名前、
- ソースコードファイル名、
- 実行されていたステートメントのソースコード行番号
これらのスタックトレースの行には、現在のコールのフレームが一番上に表示されます。上の例の上のフレームはTest.bar
メソッドにあり、Test.javaファイルの9行目にあります。それは次の行です:
return a / b;
我々はどこに以前のファイルの行のカップルを見てみるとb
初期化され、ことが明らかであるb
ゼロの値を持つことになります。これが例外の原因であることは間違いありません。
さらに進める必要がある場合は、stacktraceからbar()
がTest.javaの3行目のfoo()
から呼び出され、 foo()
がMain.main()
から呼び出されたことがMain.main()
ます。
注意:スタックフレーム内のクラスとメソッドの名前は、クラスとメソッドの内部名です。あなたは次のような珍しいケースを認識する必要があります:
- 入れ子クラスまたは内部クラスは、 "OuterClass $ InnerClass"のようになります。
- 匿名の内部クラスは、 "OuterClass $ 1"、 "OuterClass $ 2"などのようになります。
- コンストラクタ内のコード、インスタンスフィールドイニシャライザまたはインスタンスイニシャライザブロックが実行されている場合、メソッド名は ""になります。
- 静的フィールドイニシャライザまたは静的イニシャライザブロック内のコードが実行されている場合、メソッド名は ""になります。
(Javaのいくつかのバージョンでは、過剰な再帰のためにアプリケーションが失敗したときに発生する可能性があるように、スタックトレース書式設定コードは繰り返しスタックフレームシーケンスを検出して削除します。
例外チェーンと入れ子スタックトレース
例外の連鎖は、あるコードが例外をキャッチした後、新しい例外を生成してスローし、最初の例外を原因として渡したときに発生します。次に例を示します。
File: Test,java
1 public class Test {
2 int foo() {
3 return 0 / 0;
4 }
5
6 public Test() {
7 try {
8 foo();
9 } catch (ArithmeticException ex) {
10 throw new RuntimeException("A bad thing happened", ex);
11 }
12 }
13
14 public static void main(String[] args) {
15 new Test();
16 }
17 }
上記のクラスをコンパイルして実行すると、次のようなスタックトレースが取得されます。
Exception in thread "main" java.lang.RuntimeException: A bad thing happened
at Test.<init>(Test.java:10)
at Test.main(Test.java:15)
Caused by: java.lang.ArithmeticException: / by zero
at Test.foo(Test.java:3)
at Test.<init>(Test.java:8)
... 1 more
stacktraceは、クラス名、メソッド、およびコールスタックで始まり、この場合はアプリケーションがクラッシュする例外が発生します。これには、 cause
例外を報告する「原因:」行が続きます。クラス名とメッセージが報告され、続いて原因例外のスタックフレームが報告されます。トレースは、最後のN個のフレームが以前の例外と同じであることを示す「... N more」で終了します。
"原因:"は、プライマリ例外のcause
がnull
でない場合にのみ出力に含まれnull
。例外は無期限に連鎖できます。その場合、スタックトレースは複数の「原因」トレースを持つことができます。
注: cause
メカニズムはJava 1.4.0のThrowable
APIでのみ公開されていました。これに先立って、原因を表すためにカスタム例外フィールドを使用するアプリケーションと、カスタムのprintStackTrace
メソッドを使用して例外チェーンを実装する必要がありました。
スタックトレースを文字列として取り込む
場合によっては、アプリケーションがJava String
としてスタックトレースを取得できる必要があるため、他の目的に使用できます。これを行う一般的なアプローチは、一時的なOutputStream
またはWriter
を作成してメモリ内のバッファに書き込み、それをprintStackTrace(...)
渡すことprintStackTrace(...)
。
Apache CommonsとGuavaライブラリは、スタックトレースを文字列として取得するユーティリティメソッドを提供しています。
org.apache.commons.lang.exception.ExceptionUtils.getStackTrace(Throwable)
com.google.common.base.Throwables.getStackTraceAsString(Throwable)
コードベースでサードパーティライブラリを使用できない場合は、次の方法でタスクを実行します。
/**
* Returns the string representation of the stack trace.
*
* @param throwable the throwable
* @return the string.
*/
public static String stackTraceToString(Throwable throwable) {
StringWriter stringWriter = new StringWriter();
throwable.printStackTrace(new PrintWriter(stringWriter));
return stringWriter.toString();
}
スタックトレースを解析することを意図している場合は、 getStackTrace()
およびgetCause()
を使用する方が、スタックトレースを解析するよりも簡単です。
InterruptedExceptionの処理
InterruptedException
は混乱する獣ですThread.sleep()
ような一見無害なメソッドにThread.sleep()
ますが、誤って処理すると、並行環境ではあまり動作しないコードになります。
InterruptedException
が検出された場合は、コードが現在実行中のスレッドでThread.interrupt()
をThread.interrupt()
ことを意味します。「これは私のコードです!私は決して中断しません! "したがって、次のようなことをしてください:
// Bad. Don't do this.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// disregard
}
しかし、これはまさに「不可能な」出来事を処理する間違った方法です。アプリケーションでInterruptedException
が発生することがないことが分かっている場合は、そのようなイベントをプログラムの前提条件に重大な違反として扱い、できるだけ早く終了する必要があります。
「不可能な」割り込みを処理する適切な方法は、次のようなものです。
// When nothing will interrupt your code
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new AssertionError(e);
}
これは2つのことを行います。 InterruptedException
が最初にスローされていないかのように、スレッドの割り込みステータスが最初に復元され、アプリケーションの基本的な不変条件が破られたことを示すAssertionError
スローされます。 catch
ブロックが決して到達してはならないので、このコードが実行するスレッドを中断しないことが確実であることが分かっている場合は、このコードは安全です。
GuavaのUninterruptibles
クラスを使用すると、このパターンが単純化されます。 Uninterruptibles.sleepUninterruptibly()
呼び出すと、スリープ時間が終了するまでスレッドの中断状態が無視されます(その時点で、後で独自のInterruptedException
を検査してスローするために呼び出されます)。あなたがこのようなコードを決して中断しないことを知っていれば、これはtry-catchブロックであなたのスリープコールをラップする必要性を回避します。
ただし、スレッドが中断されないことを保証することはできません。特に、 Executor
や他のスレッド管理によって実行されるコードを記述している場合、コードが割り込みに迅速に応答することが重要です。そうしないと、アプリケーションがストールしたり、デッドロックしたりします。
このような場合には、 InterruptedException
がコールスタックを伝播できるようにするのが最も良い方法です。つまり、 throws InterruptedException
を各メソッドに順番にthrows InterruptedException
します。これはクルージングに見えるかもしれませんが、実際には望ましいプロパティです。メソッドのシグネチャは、呼び出し側に割り込みに即座に応答することを示します。
// Let the caller determine how to handle the interrupt if you're unsure
public void myLongRunningMethod() throws InterruptedException {
...
}
限られた場合(たとえば、チェックされた例外をthrow
しないメソッドをオーバーライドしている間)は、割り込みを処理するために次に実行されるコードを期待して、例外を発生させずに割り込みステータスをリセットできます。これは中断を処理するのを遅らせますが、完全に抑止するわけではありません。
// Suppresses the exception but resets the interrupted state letting later code
// detect the interrupt and handle it properly.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return ...; // your expectations are still broken at this point - try not to do more work.
}
Java例外階層 - チェックされていない例外とチェックされた例外
すべてのJava例外は、Exceptionクラス階層内のクラスのインスタンスです。これは次のように表すことができます。
-
java.lang.Throwable
- これはすべての例外クラスの基本クラスです。そのメソッドとコンストラクタは、すべての例外に共通する一連の機能を実装します。-
java.lang.Exception
- これは、すべての通常の例外のスーパークラスです。- さまざまな標準クラスとカスタム例外クラス。
-
java.lang.RuntimeException
- 未チェックの例外であるすべての通常の例外のスーパークラス。- さまざまな標準およびカスタムランタイム例外クラス。
-
java.lang.Error
- これはすべての "致命的なエラー"例外のスーパークラスです。
-
ノート:
- チェックし、 未チェック例外の区別は以下の通りです。
-
Throwable
、Exception
およびRuntimeException
クラスは、abstract
クラスとして扱う必要があります。 Pitfall - Throwable Throwable、Exception、ErrorまたはRuntimeExceptionを参照してください。 -
Error
例外は、アプリケーションが回復しようとするのが安全でない、または賢明でない状況でJVMによってスローされます。 -
Throwable
カスタムサブタイプを宣言することは賢明ではありません。 Javaツールとライブラリでは、Error
とException
がThrowable
唯一の直接のサブタイプであると仮定し、その仮定が間違っていると誤動作する可能性があります。
チェックされた例外と未チェックの例外
いくつかのプログラミング言語における例外サポートの批判の1つは、与えられたメソッドまたはプロシージャがスローする可能性のある例外を知ることが難しいということです。処理されない例外がプログラムをクラッシュさせる可能性があることを考えると、例外が脆弱性の原因になることがあります。
Java言語は、この問題をチェックされた例外メカニズムに解決します。まず、Javaは例外を2つのカテゴリに分類します。
チェックされた例外は、通常、アプリケーションが処理できなければならない予想されるイベントを表します。たとえば、
IOException
およびそのサブタイプは、入出力操作で発生する可能性のあるエラー状態を表します。たとえば、ファイルやディレクトリが存在しないためにファイルが開くことができず、ネットワークの接続が切断されたためにネットワークの読み取りと書き込みに失敗するなどの問題があります。チェックされていない例外は、通常、アプリケーションが処理できない予期しないイベントを表します。これらは通常、アプリケーションのバグの結果です。
(以下では、「スローされた」とは、 throw
文によって明示的にthrow
された例外、暗黙的に逆参照、型キャストなどでスローされた例外を指します)同様に、「伝播」とは、ネストされた呼び出しであり、その呼び出しの中で捕捉されません。以下のサンプルコードでこれを説明します)。
チェックされた例外メカニズムの2番目の部分は、チェックされた例外が発生する可能性のあるメソッドに制限があることです。
- チェック例外は、メソッドでスローまたは伝播される場合は、いずれかの方法でキャッチしなければならない 、またはメソッドのは、に記載されている
throws
句を。 ( この例では、throws
節の重要性が説明されています)。 - チェックされた例外がイニシャライザブロックにスローされたり伝播された場合、そのブロックをキャッチしなければなりません。
- チェックされた例外は、フィールド初期化式のメソッド呼び出しでは伝播できません。 (そのような例外をキャッチする方法はありません。)
要するに、チェック例外は処理されるか、宣言されなければなりません。
これらの制限は、チェックされていない例外には適用されません。これには、すべてのケースで未チェックの例外がスローされるため、暗黙的に例外がスローされるすべてのケースが含まれます。
チェックされた例外の例
これらのコードスニペットは、チェックされた例外の制限を説明するためのものです。いずれの場合も、コンパイルエラーのあるバージョンのコードと、エラーが修正された2番目のバージョンが表示されます。
// This declares a custom checked exception.
public class MyException extends Exception {
// constructors omitted.
}
// This declares a custom unchecked exception.
public class MyException2 extends RuntimeException {
// constructors omitted.
}
最初の例は、明示的にスローされたチェックされた例外がメソッドで処理されるべきでない場合に "投げられる"と宣言される方法を示しています。
// INCORRECT
public void methodThrowingCheckedException(boolean flag) {
int i = 1 / 0; // Compiles OK, throws ArithmeticException
if (flag) {
throw new MyException(); // Compilation error
} else {
throw new MyException2(); // Compiles OK
}
}
// CORRECTED
public void methodThrowingCheckedException(boolean flag) throws MyException {
int i = 1 / 0; // Compiles OK, throws ArithmeticException
if (flag) {
throw new MyException(); // Compilation error
} else {
throw new MyException2(); // Compiles OK
}
}
2番目の例は、伝播されたチェック例外をどのように処理できるかを示しています。
// INCORRECT
public void methodWithPropagatedCheckedException() {
InputStream is = new FileInputStream("someFile.txt"); // Compilation error
// FileInputStream throws IOException or a subclass if the file cannot
// be opened. IOException is a checked exception.
...
}
// CORRECTED (Version A)
public void methodWithPropagatedCheckedException() throws IOException {
InputStream is = new FileInputStream("someFile.txt");
...
}
// CORRECTED (Version B)
public void methodWithPropagatedCheckedException() {
try {
InputStream is = new FileInputStream("someFile.txt");
...
} catch (IOException ex) {
System.out.println("Cannot open file: " + ex.getMessage());
}
}
最後の例は、静的フィールドイニシャライザでチェック例外を処理する方法を示しています。
// INCORRECT
public class Test {
private static final InputStream is =
new FileInputStream("someFile.txt"); // Compilation error
}
// CORRECTED
public class Test {
private static final InputStream is;
static {
InputStream tmp = null;
try {
tmp = new FileInputStream("someFile.txt");
} catch (IOException ex) {
System.out.println("Cannot open file: " + ex.getMessage());
}
is = tmp;
}
}
この最後のケースでは、複数回に割り当てることis
できない問題を処理しなければならず、例外の場合でも割り当てられる必要があることに注意してください。
前書き
例外は、プログラムの実行中に発生するエラーです。以下の2つの整数を分割するJavaプログラムを考えてみましょう。
class Division {
public static void main(String[] args) {
int a, b, result;
Scanner input = new Scanner(System.in);
System.out.println("Input two integers");
a = input.nextInt();
b = input.nextInt();
result = a / b;
System.out.println("Result = " + result);
}
}
今度は上記のコードをコンパイルして実行し、0で除算しようとする出力を見てみましょう:
Input two integers
7 0
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Division.main(Disivion.java:14)
0による除算は、整数として表現できない値を生成する無効な演算です。 Javaでは例外をスローすることでこれを処理します 。この場合、例外はArithmeticExceptionクラスのインスタンスです。
注: スタックトレースの作成と読み取りの例では、2つの数字の後の出力の意味を説明しています。
例外の有用性は、それが許すフロー制御です。例外を使用せずに、この問題の典型的な解決方法は、最初にb == 0
:
class Division {
public static void main(String[] args) {
int a, b, result;
Scanner input = new Scanner(System.in);
System.out.println("Input two integers");
a = input.nextInt();
b = input.nextInt();
if (b == 0) {
System.out.println("You cannot divide by zero.");
return;
}
result = a / b;
System.out.println("Result = " + result);
}
}
これはYou cannot divide by zero.
メッセージを表示You cannot divide by zero.
ユーザーがゼロで分割しようとすると、コンソールを終了し、プログラムを正常な方法で終了します。 例外処理を介してこの問題を処理する同等の方法は、 if
フロー制御をtry-catch
ブロックで置き換えることif
。
...
a = input.nextInt();
b = input.nextInt();
try {
result = a / b;
}
catch (ArithmeticException e) {
System.out.println("An ArithmeticException occurred. Perhaps you tried to divide by zero.");
return;
}
...
try catchブロックは次のように実行されます。
-
try
ブロックでコードの実行を開始します。 - tryブロックで例外が発生した場合は、すぐに中止し、この例外がでキャッチされたかどうかを確認する
catch
ブロック(例外のインスタンスであるとき、このケースでArithmeticException
)。 - 例外がキャッチされた場合、それは変数
e
割り当てられ、catch
ブロックが実行されます。 -
try
またはcatch
ブロックのいずれかが完了した場合(つまり、コード実行中にキャッチされない例外が発生しない場合)、引き続きtry-catch
ブロックの下のコードを実行します。
通常、アプリケーションの通常のフロー制御の一部として例外処理を使用し、動作が未定義または予期しないものになるようにすることをお勧めします。たとえば、メソッドが失敗したときにnull
を返す代わりに、例外をスローして、そのメソッドを使用するアプリケーションが、上に示した種類の例外処理を介して状況に対する独自のフロー制御を定義できるようにする方が良いでしょう。ある意味では、特定の型を返さなければならないという問題を回避します。発生した特定の問題を示すために複数の種類の例外がスローされる可能性があります。
例外を使用しない方法と使用方法の詳細については、「 Javaの落とし穴 - 例外の使用法」を参照してください。
try catchブロック内の文を返す
悪い習慣ですが、例外処理ブロックに複数のreturn文を追加することは可能です:
public static int returnTest(int number){
try{
if(number%2 == 0) throw new Exception("Exception thrown");
else return x;
}
catch(Exception e){
return 3;
}
finally{
return 7;
}
}
このメソッドは、try / catchブロックに関連付けられたfinallyブロックが何かが返される前に実行されるため、常に7を返します。さて、最終的にreturn 7;
この値はtry / catchの戻り値に取って代わるものです。
キャッチブロックがプリミティブ値を返し、そのプリミティブ値がfinallyブロックで変更された場合、キャッチブロックで返された値が返され、finallyブロックからの変更は無視されます。
以下の例では、「1」ではなく「0」が印刷されます。
public class FinallyExample {
public static void main(String[] args) {
int n = returnTest(4);
System.out.println(n);
}
public static int returnTest(int number) {
int returnNumber = 0;
try {
if (number % 2 == 0)
throw new Exception("Exception thrown");
else
return returnNumber;
} catch (Exception e) {
return returnNumber;
} finally {
returnNumber = 1;
}
}
}
例外の高度な機能
この例では、例外のいくつかの高度な機能と使用例について説明します。
プログラムで呼び出しスタックを調べる
例外スタックトレースの主な用途は、アプリケーションエラーとそのコンテキストに関する情報を提供し、プログラマが問題を診断して修正できるようにすることです。時々他のもののために使用することができます。たとえば、 SecurityManager
クラスは、呼び出しを行っているコードを信頼する必要があるかどうかを判断するために呼び出しスタックを調べる必要があります。
次のように例外を使用してプログラムスタックで呼び出しスタックを調べることができます。
Exception ex = new Exception(); // this captures the call stack
StackTraceElement[] frames = ex.getStackTrace();
System.out.println("This method is " + frames[0].getMethodName());
System.out.println("Called from method " + frames[1].getMethodName());
これにはいくつかの重要な注意点があります:
StackTraceElement
利用可能な情報は限られています。printStackTrace
で表示される情報より多くの情報はありません。 (フレーム内のローカル変数の値は使用できません。)getStackTrace()
は、JVMがフレームを残すことを許可されていることを示しています。一部の仮想マシンでは、場合によってはスタックトレースから1つ以上のスタックフレームを省略することがあります。極端な場合、このスロー可能オブジェクトに関するスタックトレース情報を持たない仮想マシンは、このメソッドからゼロ長配列を返すことができます。
例外処理の最適化
他のところで述べたように、例外の構築は、現在のスレッド上のすべてのスタックフレームに関する情報をキャプチャして記録するため、かなり高価です。場合によっては、その情報が特定の例外に対して使用されることはないことがわかっています。例えば、スタックトレースは決して印刷されません。その場合、情報をキャプチャしないようにカスタム例外で使用できる実装の仕組みがあります。
スタックトレースに必要なスタックフレーム情報は、 Throwable
コンストラクタがThrowable.fillInStackTrace()
メソッドを呼び出すときに取得されます。このメソッドはpublic
です。つまり、サブクラスはそれをオーバーライドできます。このトリックは、 Throwable
から継承したメソッドを何もしないメソッドでオーバーライドすることです。例えば
public class MyException extends Exception {
// constructors
@Override
public void fillInStackTrace() {
// do nothing
}
}
このアプローチの問題は、 fillInStackTrace()
をオーバーライドする例外がスタックトレースを捕捉できないことと、必要なシナリオでは役に立たないことです。
スタックトレースの消去または置き換え
状況によっては、通常の方法で作成された例外のスタックトレースに、誤った情報または開発者がユーザーに公開したくない情報が含まれていることがあります。これらのシナリオでは、 Throwable.setStackTrace
を使用して、情報を保持するStackTraceElement
オブジェクトの配列を置き換えることができます。
たとえば、次のようにして例外のスタック情報を破棄できます。
exception.setStackTrace(new StackTraceElement[0]);
抑制された例外
Java 7では、 try-with-resources構造と、それに関連する例外抑制の概念が導入されました。以下のスニペットを考えてみましょう。
try (Writer w = new BufferedWriter(new FileWriter(someFilename))) {
// do stuff
int temp = 0 / 0; // throws an ArithmeticException
}
例外がスローされると、 try
はw
でclose()
を呼び出し、バッファされた出力をフラッシュし、 FileWriter
ます。しかし、出力をフラッシュする際にIOException
がスローされるとどうなりますか?
何が起こるかは、リソースをクリーンアップしている間にスローされた例外はすべて抑制されることです。例外がキャッチされ、プライマリ例外の抑制された例外リストに追加されます。次に、 try-with-resourcesは他のリソースのクリーンアップを続行します。最後に、一次例外が再スローされます。
同様のパターンは、リソースの初期化中に例外がスローされた場合、またはtry
ブロックが正常に完了した場合に発生します。スローされた最初の例外が主な例外になり、その後のクリーンアップで発生する例外は抑制されます。
抑制された例外は、 getSuppressedExceptions
を呼び出すことによって、プライマリ例外オブジェクトから取得できます。
try-finallyおよびtry-catch-finallyステートメント
try...catch...finally
文は、例外処理とクリーンアップコードを組み合わせています。 finally
ブロックには、すべての状況で実行されるコードが含まれています。これにより、リソース管理や他の種類のクリーンアップに適しています。
Try-finally
以下は単純な( try...finally
)フォームの例です:
try {
doSomething();
} finally {
cleanUp();
}
try...finally
の動作は次のようになります。
-
try
ブロック内のコードが実行されます。 -
try
ブロックに例外がスローされなかった場合:-
finally
ブロック内のコードが実行されます。 -
finally
ブロックが例外をスローすると、その例外が伝播します。 - それ以外の場合は、
try...finally
後に次の文に制御がtry...finally
。
-
- tryブロックに例外がスローされた場合:
-
finally
ブロック内のコードが実行されます。 -
finally
ブロックが例外をスローすると、その例外が伝播します。 - それ以外の場合、元の例外は引き続き伝播し続けます。
-
finally
ブロック内のコードは常に実行されます。 (唯一の例外は、 System.exit(int)
が呼び出された場合、またはJVMがパニックする場合です)。したがって、 finally
ブロックは常に実行する必要がある正しいプレースコードです。ファイルやその他のリソースを閉じる、ロックを解除するなど)。
try-catch-finally
2番目の例はcatch
とfinally
がどのように一緒に使用できるかを示しています。また、リソースのクリーンアップが簡単ではないことも示しています。
// This code snippet writes the first line of a file to a string
String result = null;
Reader reader = null;
try {
reader = new BufferedReader(new FileReader(fileName));
result = reader.readLine();
} catch (IOException ex) {
Logger.getLogger.warn("Unexpected IO error", ex); // logging the exception
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
// ignore / discard this exception
}
}
}
この例でtry...catch...finally
な(仮説的な)動作の完全なセットはここでは説明するのが難しいです。単純なバージョンは、 finally
ブロックのコードが常に実行されるということです。
これをリソース管理の観点から見ると:
- 私たちは
try
ブロックの前に "リソース"(つまりreader
変数)を宣言して、finally
ブロックのスコープに入るようfinally
ます。 -
new FileReader(...)
を置くことで、catch
はファイルを開くときにスローされたIOError
例外を処理できます。 -
finally
ブロックにはreader.close()
が必要です。これは、try
ブロックまたはcatch
ブロックで傍受できない例外パスがいくつか存在するためです。 - ただし、
reader
が初期化される前に例外がスローされている可能性があるため、明示的なnull
も必要です。 - 最後に、
reader.close()
コールは(仮想的に)例外をスローする可能性があります。これについては気にしませんが、ソースで例外をキャッチしないと、コールスタックをさらに処理する必要があります。
Java 7以降では、 resource-try-with-resources構文を使用してリソースのクリーンアップを大幅に簡略化しています。
メソッド宣言の 'throws'節
Javaのチェック例外メカニズムでは、特定のメソッドが指定されたチェック例外をスローできることをプログラマが宣言する必要があります。これはthrows
節を使用して行われます。例えば:
public class OddNumberException extends Exception { // a checked exception
}
public void checkEven(int number) throws OddNumberException {
if (number % 2 != 0) {
throw new OddNumberException();
}
}
throws OddNumberException
への呼び出しことを宣言しcheckEven
型のある例外スローする可能性 OddNumberException
。
throws
節は型のリストを宣言することができ、チェックされていない例外と検査された例外を含めることができます。
public void checkEven(Double number)
throws OddNumberException, ArithmeticException {
if (!Double.isFinite(number)) {
throw new ArithmeticException("INF or NaN");
} else if (number % 2 != 0) {
throw new OddNumberException();
}
}
チェックされていない例外をスローすると宣言するのは何ですか?
メソッド宣言のthrows
節には2つの目的があります。
これはコンパイラに例外がスローされたことを通知し、コンパイラがキャッチされていない(チェックされた)例外をエラーとして報告できるようにします。
これは、メソッドを呼び出すコードを記述しているプログラマに、例外の例外を通知します。この目的のために、チェックされていない例外を
throws
リストに含めることがよくあります。
注意: throws
リストは、APIドキュメントの生成時にはjavadocツールで使用され、一般的なIDEの「ホバーテキスト」メソッドのヒントでも使用されます。
スローとメソッドのオーバーライド
throws
節は、メソッドオーバーライドの目的でメソッドのシグネチャの一部を形成します。オーバーライドメソッドは、オーバーライドされたメソッドによってスローされるのと同じチェック例外セット、またはサブセットで宣言できます。ただし、オーバーライドメソッドは追加のチェック例外を追加することはできません。例えば:
@Override
public void checkEven(int number) throws NullPointerException // OK—NullPointerException is an unchecked exception
...
@Override
public void checkEven(Double number) throws OddNumberException // OK—identical to the superclass
...
class PrimeNumberException extends OddNumberException {}
class NonEvenNumberException extends OddNumberException {}
@Override
public void checkEven(int number) throws PrimeNumberException, NonEvenNumberException // OK—these are both subclasses
@Override
public void checkEven(Double number) throws IOExcepion // ERROR
このルールの理由は、オーバーライドされたメソッドがオーバーライドされたメソッドがスローできなかったチェック例外をスローできる場合、そのメソッドは型の代用性を破るためです。