サーチ…


前書き

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...finallytry-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"を出力します。 RuntimeExceptionExceptionサブタイプであるため、最初の(より一般的な) catchがマッチします。 2番目の(より具体的な) catchは決して実行されません。

これから学ぶべき教訓は、(例外タイプに関して)最も具体的なcatchブロックが最初に現れ、最も一般的なcatchブロックが最後に現れるべきであるということです。 (一部のJavaコンパイラは、 catchを実行できない場合に警告しますが、これはコンパイルエラーではありません)。

マルチ例外キャッチブロック

Java SE 7

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;
    }

上の例では、 IOExceptionSQLExceptionは、最小の共通スーパータイプがException例外を検査しException 。つまり、 reportメソッドはreport(Exception)一致する必要がありreport(Exception) 。ただし、コンパイラはthrowIOExceptionまたは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は例外メッセージの国際化を直接サポートしていません。)

いくつかの点を追加する必要があります:

  • checkNumberthrows 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処理しています。しかし、元の例外を捨てているわけではありません。 IOExceptioncauseとして渡すことで、スタックトレースの作成と読み込みで説明したように、スタックトレースに出力できるようにIOExceptionを記録します。

カスタム例外

ほとんどの状況下では、 Exceptionをスローするときに既存の汎用Exceptionクラスを使用する方がコード設計の観点からは簡単です。これは、単純なエラーメッセージを伝えるために例外が必要な場合に特に当てはまります。その場合、チェックされた例外ではないため、 RuntimeExceptionが通常は優先されます。一般的なクラスのエラーに対しては、他の例外クラスが存在します。

カスタム例外クラスを使いたいケースには次のものがあります。

  • 他の人が使用するための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ステートメント

Java SE 7

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 。これらには、

  • InputStreamOutputStreamおよびそのサブクラス
  • ReaderWriter 、およびそれらのサブクラス
  • SocketServerSocketとそのサブクラス
  • Channelとそのサブクラス、および
  • JDBCインタフェースConnectionStatementResultSet 、およびそれらのサブクラス

アプリケーションと第三者のクラスもこれを行う可能性があります。

基本的なtry-with-resourceステートメント

try-with-resourcesの構文は、古典的なtry-catchtry-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ブロックの最後では、 isos両方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されます。

ノート:

  1. stacktraceには、例外自体の詳細は含まれません。これらの詳細を取得するには、 toString()メソッドを使用できます。例えば

       // Print exception and stacktrace
       System.out.println(ex);
       ex.printStackTrace();
    
  2. 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のいくつかのバージョンでは、過剰な再帰のためにアプリケーションが失敗したときに発生する可能性があるように、スタックトレース書式設定コードは繰り返しスタックフレームシーケンスを検出して削除します。

例外チェーンと入れ子スタックトレース

Java SE 1.4

例外の連鎖は、あるコードが例外をキャッチした後、新しい例外を生成してスローし、最初の例外を原因として渡したときに発生します。次に例を示します。

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」で終了します。

"原因:"は、プライマリ例外のcausenullでない場合にのみ出力に含まれnull 。例外は無期限に連鎖できます。その場合、スタックトレースは複数の「原因」トレースを持つことができます。

注: causeメカニズムはJava 1.4.0のThrowable APIでのみ公開されていました。これに先立って、原因を表すためにカスタム例外フィールドを使用するアプリケーションと、カスタムのprintStackTraceメソッドを使用して例外チェーンを実装する必要がありました。

スタックトレースを文字列として取り込む

場合によっては、アプリケーションがJava Stringとしてスタックトレースを取得できる必要があるため、他の目的に使用できます。これを行う一般的なアプローチは、一時的なOutputStreamまたはWriterを作成してメモリ内のバッファに書き込み、それをprintStackTrace(...)渡すことprintStackTrace(...)

Apache CommonsGuavaライブラリは、スタックトレースを文字列として取得するユーティリティメソッドを提供しています。

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 - これはすべての "致命的なエラー"例外のスーパークラスです。

ノート:

  1. チェックし、 未チェック例外の区別は以下の通りです。
  2. ThrowableExceptionおよびRuntimeExceptionクラスは、 abstractクラスとして扱う必要があります。 Pitfall - Throwable Throwable、Exception、ErrorまたはRuntimeExceptionを参照してください。
  3. Error例外は、アプリケーションが回復しようとするのが安全でない、または賢明でない状況でJVMによってスローされます。
  4. Throwableカスタムサブタイプを宣言することは賢明ではありません。 Javaツールとライブラリでは、 ErrorExceptionThrowable唯一の直接のサブタイプであると仮定し、その仮定が間違っていると誤動作する可能性があります。

チェックされた例外と未チェックの例外

いくつかのプログラミング言語における例外サポートの批判の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ブロックは次のように実行されます。

  1. tryブロックでコードの実行を開始します。
  2. tryブロックで例外が発生した場合は、すぐに中止し、この例外がでキャッチされたかどうかを確認するcatchブロック(例外のインスタンスであるとき、このケースでArithmeticException )。
  3. 例外がキャッチされた場合、それは変数e割り当てられ、 catchブロックが実行されます。
  4. 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;
        }
    }
}

例外の高度な機能

この例では、例外のいくつかの高度な機能と使用例について説明します。

プログラムで呼び出しスタックを調べる

Java SE 1.4

例外スタックトレースの主な用途は、アプリケーションエラーとそのコンテキストに関する情報を提供し、プログラマが問題を診断して修正できるようにすることです。時々他のもののために使用することができます。たとえば、 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());

これにはいくつかの重要な注意点があります:

  1. StackTraceElement利用可能な情報は限られています。 printStackTraceで表示される情報より多くの情報はありません。 (フレーム内のローカル変数の値は使用できません。)

  2. getStackTrace()は、JVMがフレームを残すことを許可されていることを示しています。

    一部の仮想マシンでは、場合によってはスタックトレースから1つ以上のスタックフレームを省略することがあります。極端な場合、このスロー可能オブジェクトに関するスタックトレース情報を持たない仮想マシンは、このメソッドからゼロ長配列を返すことができます。

例外処理の最適化

他のところで述べたように、例外の構築は、現在のスレッド上のすべてのスタックフレームに関する情報をキャプチャして記録するため、かなり高価です。場合によっては、その情報が特定の例外に対して使用されることはないことがわかっています。例えば、スタックトレースは決して印刷されません。その場合、情報をキャプチャしないようにカスタム例外で使用できる実装の仕組みがあります。

スタックトレースに必要なスタックフレーム情報は、 ThrowableコンストラクタがThrowable.fillInStackTrace()メソッドを呼び出すときに取得されます。このメソッドはpublicです。つまり、サブクラスはそれをオーバーライドできます。このトリックは、 Throwableから継承したメソッドを何もしないメソッドでオーバーライドすることです。例えば

  public class MyException extends Exception {
      // constructors

      @Override 
      public void fillInStackTrace() {
          // do nothing
      }
  }

このアプローチの問題は、 fillInStackTrace()をオーバーライドする例外がスタックトレースを捕捉できないことと、必要なシナリオでは役に立たないことです。

スタックトレースの消去または置き換え

Java SE 1.4

状況によっては、通常の方法で作成された例外のスタックトレースに、誤った情報または開発者がユーザーに公開したくない情報が含まれていることがあります。これらのシナリオでは、 Throwable.setStackTraceを使用して、情報を保持するStackTraceElementオブジェクトの配列を置き換えることができます。

たとえば、次のようにして例外のスタック情報を破棄できます。

 exception.setStackTrace(new StackTraceElement[0]);

抑制された例外

Java SE 7

Java 7では、 try-with-resources構造と、それに関連する例外抑制の概念が導入されました。以下のスニペットを考えてみましょう。

try (Writer w = new BufferedWriter(new FileWriter(someFilename))) {
    // do stuff
    int temp = 0 / 0;    // throws an ArithmeticException
}

例外がスローされると、 trywclose()を呼び出し、バッファされた出力をフラッシュし、 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番目の例はcatchfinallyがどのように一緒に使用できるかを示しています。また、リソースのクリーンアップが簡単ではないことも示しています。

// 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 SE 7

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つの目的があります。

  1. これはコンパイラに例外がスローされたことを通知し、コンパイラがキャッチされていない(チェックされた)例外をエラーとして報告できるようにします。

  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

このルールの理由は、オーバーライドされたメソッドがオーバーライドされたメソッドがスローできなかったチェック例外をスローできる場合、そのメソッドは型の代用性を破るためです。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow