web-dev-qa-db-ja.com

Javaチェック例外の回避策

ラムダとデフォルトのメソッドインターフェースに関する新しいJava 8機能に感謝します。それでも、チェックされた例外には飽き飽きします。たとえば、私はこれを単に書きたいオブジェクト:

    Arrays.asList(p.getClass().getFields()).forEach(
        f -> System.out.println(f.get(p))
    );

ただし、getメソッドはConsumerインターフェースコントラクトと一致しないチェック例外をスローする可能性があるため、その例外をキャッチして次のコードを記述する必要があります。

    Arrays.asList(p.getClass().getFields()).forEach(
            f -> {
                try {
                    System.out.println(f.get(p));
                } catch (IllegalArgumentException | IllegalAccessException ex) {
                    throw new RuntimeException(ex);
                }
            }
    );

しかし、ほとんどの場合、例外をRuntimeExceptionとしてスローし、プログラムにコンパイルエラーなしで例外を処理させるか、させないようにします。

ですから、チェックされた例外の煩わしさに対する問題のある回避策について、あなたの意見をお聞かせください。そのために、補助インターフェースConsumerCheckException<T>とユーティリティ関数rethrowを作成しました Dovalのコメント)次のように:

  @FunctionalInterface
  public interface ConsumerCheckException<T>{
      void accept(T elem) throws Exception;
  }

  public class Wrappers {
      public static <T> Consumer<T> rethrow(ConsumerCheckException<T> c) {
        return elem -> {
          try {
            c.accept(elem);
          } catch (Exception ex) {
            /**
             * within sneakyThrow() we cast to the parameterized type T. 
             * In this case that type is RuntimeException. 
             * At runtime, however, the generic types have been erased, so 
             * that there is no T type anymore to cast to, so the cast
             * disappears.
             */
            Wrappers.<RuntimeException>sneakyThrow(ex);
          }
        };
      }

      /**
       * Reinier Zwitserloot who, as far as I know, had the first mention of this
       * technique in 2009 on the Java posse mailing list.
       * http://www.mail-archive.com/[email protected]/msg05984.html
       */
      public static <T extends Throwable> T sneakyThrow(Throwable t) {
          throw (T) t;
      }
  }

そして今、私は書くことができます:

    Arrays.asList(p.getClass().getFields()).forEach(
            rethrow(f -> System.out.println(f.get(p)))
    );

これがチェックされた例外を回避するのに最適なイディオムであるかどうかはわかりませんが、説明したように、チェックされた例外を処理せずに最初の例を実現するより便利な方法があり、これが私が見つけた最も簡単な方法ですそれを行うには。

この手法の利点、欠点、および制限:

  • 呼び出しコードがチェック済み例外を処理する場合は、ストリームを含むメソッドのthrows句にそれを追加する必要があります。コンパイラーはもはやそれを追加することを強制しないので、それを忘れる方が簡単です。例えば:

    public void test(Object p) throws IllegalAccessException {
        Arrays.asList(p.getClass().getFields()).forEach(rethrow(f -> System.out.println(f.get(p))));
    }
    
  • 呼び出しコードがすでにチェック済み例外を処理している場合、コンパイラーは、ストリームを含むメソッド宣言にthrows句を追加するように通知します(そうしないと、対応するtryステートメントの本文で例外がスローされることはありません)。 。

  • いずれの場合でも、ストリーム自体を囲んで、ストリームを含むメソッド内でチェックされた例外をキャッチすることはできません(試みた場合、コンパイラーは次のように言います:対応するtryステートメントの本文で例外がスローされることはありません)。

  • 文字どおりに宣言されている例外をスローできないメソッドを呼び出す場合は、throws句を含めないでください。例:new String(byteArr、 "UTF-8")はUnsupportedEncodingExceptionをスローしますが、UTF-8はJava仕様により常に存在することが保証されています。ここで、スローの宣言は厄介であり、最小限のボイラープレートでそれを沈黙させるための解決策は大歓迎です。

  • チェックされた例外が嫌いで、Java言語に最初から追加するべきではないと感じた場合(増加する人々がこのように考えており、私はそれらの1人ではありません)。ストリームを含むメソッドのthrows句にチェック済み例外を追加しないでください。チェック済み例外は、UNchecked例外と同じように動作します。

  • 厳密なインターフェイスを実装していて、throws宣言を追加するオプションがなく、例外をスローすることが完全に適切である場合、例外をラップして、それをスローする特権を得るだけで、疑似例外を含むスタックトレースが発生します。実際に何がうまくいかなかったかについての情報を提供しないでください。良い例は、チェックされた例外をスローしないRunnable.run()です。この場合、ストリームを含むメソッドのthrows句にチェック済み例外を追加しないことを決定できます。

  • いずれの場合でも、ストリームを含むメソッドのthrows句にチェック済み例外を追加しない(または追加を忘れる)と決定した場合は、CHECKED例外をスローすることによる次の2つの結果に注意してください。

    1. 呼び出しコードは名前でそれをキャッチすることができません(試みた場合、コンパイラーは次のように言うでしょう:対応するtryステートメントの本体で例外はスローされません)。それはバブルし、おそらく何らかの「キャッチ例外」または「キャッチスロー可能」によってメインプログラムループでキャッチされます。

    2. これは、最小の驚きの原則に違反します。すべての可能な例外を確実にキャッチできるように、RuntimeExceptionをキャッチするだけでは十分ではありません。このため、これはフレームワークコードではなく、完全に制御できるビジネスコードでのみ行う必要があると思います。

参照:

注:この手法を使用する場合は、StackOverflowからLambdaExceptionUtilヘルパークラスをコピーできます: https:/ /stackoverflow.com/questions/27644361/how-can-i-throw-checked-exceptions-from-inside-Java-8-streams 。完全な実装(機能、消費者、サプライヤー...)と例を示します。

16
MarcG

この例では、本当に失敗する可能性がありますか?そうは思わないが、おそらくあなたのケースは特別だ。それが本当に「失敗することはあり得ない」、そしてそれが単に厄介なコンパイラーの事柄である場合、私は例外をラップし、「起こり得ない」というコメントを付けてErrorをスローするのが好きです。 非常に明確メンテナンス用。そうでなければ、彼らは「これはどうして起こり得るのか」と「一体誰がこれを処理するのか?

これは物議を醸す慣行なので、YMMVです。私はおそらくいくつかの反対票を獲得します。

6
user949300

チェックされた例外が単に遅れているこのコードの別のバージョン:

public class Cocoon {
         static <T extends Throwable> T forgetThrowsClause(Throwable t) throws T{
            throw (T) t;
        }

        public static <X, T extends Throwable> Consumer<X> consumer(PeskyConsumer<X,T> touchyConsumer) throws T {
            return new Consumer<X>() {
                @Override
                public void accept(X t) {
                    try {
                        touchyConsumer.accept(t);
                    } catch (Throwable exc) {
                        Cocoon.<RuntimeException>forgetThrowsClause(exc) ;
                    }

                }
            } ;
        }
// and so on for Function, and other codes from Java.util.function
}

魔法はあなたが呼び出す場合:

 myArrayList.forEach(Cocoon.consumer(MyClass::methodThatThrowsException)) ;

あなたのコードは例外をキャッチする義務があります

1
java bear

sneakyThrowはクールです!私は完全にあなたの痛みを感じます。

Javaにとどまる必要がある場合...

Paguroには、チェックされた例外をラップする機能的なインターフェイスがあります したがって、これについてもう考える必要はありません。これには、機能インターフェースを取り、チェックされた例外をラップするClojureトランスデューサー(またはJava 8 Streams)のラインに沿った不変コレクションと機能変換が含まれます。

VAVrThe Eclipse Collections も試してみてください。

それ以外の場合は、Kotlinを使用します

Kotlin はJavaとの双方向互換性があり、チェックされた例外、プリミティブ(プログラマーが考える必要はありません)、より良い型システム、不変性などを想定していません。上記のPaguroライブラリーでは、すべてのJavaコードをKotlinに変換します。これまで使用していたものはすべてJava Kotlinで実行することを好みます。

0
GlenPeterson