Javaチェック例外の回避策


49

ラムダとデフォルトのメソッドインターフェースに関する新しい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);
                }
            }
    );

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

それで、私はチェックされた例外の迷惑のための私の物議を醸す回避策についてあなたの意見を持ちたいです。そのために、次のように補助インターフェイスConsumerCheckException<T>とユーティリティ関数rethrowDovalのコメントの推測に応じて更新)を作成しました。

  @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/javaposse@googlegroups.com/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)))
    );

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



2
ロバートのリンクに加えて、Sneakily Throwing Checked Exceptionsもご覧ください。必要に応じて、でラップする代わりに、sneakyThrowinsideを使用しrethrowて元のチェック済み例外をスローできますRuntimeException。あるいは、同じことを行うProject Lombok@SneakyThrows注釈を使用することもできます。
ドーバル14年

1
また、並列s を使用する場合、Consumers in forEachは並列に実行される場合があることに注意してくださいStream。コンシューマーのウィッシングから発生したスロー可能オブジェクトは、呼び出しスレッドに伝播します。1)同時に実行されている他のコンシューマーを停止せず、適切かどうかは不明です。スロー可能なオブジェクトの1つは、呼び出し元のスレッドに表示されます。
ジョナスプラッカ14


2
ラムダはとてもいです。
Tulainsコルドバ

回答:


16

手法の長所、短所、および制限:

  • 呼び出しコードがチェック済み例外を処理する場合、ストリームを含むメソッドの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人ではありません)、チェック例外をスローに追加しないでくださいストリームを含むメソッドの句。そのため、チェック済み例外は、UNchecked例外のように動作します。

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

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

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

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

参照:

注:この手法を使用する場合はLambdaExceptionUtil、StackOverflowからヘルパークラスをコピーできます:https : //stackoverflow.com/questions/27644361/how-can-i-throw-checked-exceptions-from-inside-java-8 -streams。これにより、完全な実装(関数、コンシューマー、サプライヤ...)と例が提供されます。


6

この例では、実際に失敗することはありますか?そうは思わないが、多分あなたのケースは特別だ。それが本当に「失敗することはできません」、そしてそれがただ厄介なコンパイラーのことなら、私は例外をラップErrorしてコメント付きで「発生しない」を投げるのが好きです。メンテナンスのために物事を非常に明確にします。さもなければ、彼らは「どうしてこれが起こるのだろう」と「一体誰がこれを処理するのか」と疑問に思うでしょう。

これは物議を醸す慣行であるため、YMMV。私はおそらくいくつかのダウン票を得るでしょう。


3
エラーをスローするため、+ 1。何回私はちょうど1行コメント「が起こることはできない」を含むcatchブロックで、デバッグの時間後に終わったが...
アクセル・

3
-1エラーをスローするため。エラーは、JVMで何かが間違っていることを示すためのものであり、上位レベルがそれに応じてエラーを処理する場合があります。その方法を選択した場合は、RuntimeExceptionをスローする必要があります。別の考えられる回避策は、アサート(-eaフラグを有効にする必要があります)またはログです。
デュロス14年

3
@duros:例外が「発生しない」理由に応じて、例外がスローされるという事実は、何かがひどく間違っていることを示している場合があります。たとえば、それをサポートすることが知られてcloneいる封印された型Fooを呼び出し、それがスローされたとしCloneNotSupportedExceptionます。コードが他の予期しない種類とリンクされない限り、どうしてそうなるのFooでしょうか?そして、それが起こった場合、何かを信頼できますか?
supercat 14

1
@supercatのすばらしい例。他のものは、欠落している文字列エンコーディングまたはメッセージダイジェストから例外をスローしていますUTF-8またはSHAが欠落している場合、ランタイムが破損している可能性があります。
user949300 14

1
@durosそれは完全な誤解です。An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.(のJavadocから引用Error)これはまさにこの種の状況であり、したがって、不適切とはほど遠く、Errorhereをスローすることが最も適切なオプションです。
-biziclop

1

チェック例外が遅延しているこのコードの別のバージョン:

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

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


要素が異なるスレッドで消費される並列ストリームでこれを実行するとどうなりますか?
プルーン

0

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

Javaにとどまらなければならない場合...

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

ありVAVrEclipseのコレクションは、試してみます。

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

KotlinはJavaとの双方向の互換性があり、チェックされた例外もプリミティブもありません(まあ、プログラマーが考える必要はありません)、より良い型システム、不変性を前提としています。 mできる限りすべてのJavaコードをKotlinに変換します。今までJavaで行っていたことが、今ではKotlinで行うことを好みます。


誰かがこれをダウン投票しましたが、これは問題ありませんが、なぜダウン投票したのを教えてくれない限り、何も学びません。
グレンペターソン

1
githubでライブラリの+1 。機能的で不変のコレクションを提供するeclipse.org/collectionsまたはプロジェクトvavr も確認できます。これらについてどう思いますか?
firephil

-1

チェック済みの例外は非常に便利であり、その処理は、コーディングする製品の種類によって異なります。

  • 図書館
  • デスクトップアプリケーション
  • いくつかのボックス内で実行されているサーバー
  • アカデミックエクササイズ

通常、ライブラリはチェック例外を処理してはなりませんが、パブリックAPIによってスローされたものとして宣言する必要があります。プレーンなRuntimeExceptonにラップできるまれなケースは、たとえば、ライブラリコード内にプライベートxmlドキュメントを作成して解析し、一時ファイルを作成してから読み取ることです。

デスクトップアプリケーションの場合、独自の例外処理フレームワークを作成して、製品の開発を開始する必要があります。通常、独自のフレームワークを使用して、チェック済み例外を2〜4個のラッパー(RuntimeExcepionのカスタムサブクラス)にラップし、単一の例外ハンドラーで処理します。

サーバーの場合、通常、コードは何らかのフレームワーク内で実行されます。使用しない場合(ベアサーバー)、上記の(ランタイムに基づいて)同様の独自のフレームワークを作成します。

アカデミックなエクササイズでは、いつでも直接RuntimeExceptonにラップできます。誰かが小さなフレームワークを作成することもできます。


-1

あなたは見ていたいことがあり、このプロジェクトは、特に、チェック例外と機能インターフェイスの問題のために作成しました。

これを使用すると、カスタムコードを記述する代わりにこれを行うことができます。

// I don't know the type of f, so it's Foo...
final ThrowingConsumer<Foo> consumer = f -> System.out.println(f.get(p));

これらのThrowing *インターフェースはすべて、対応するThrowing以外のインターフェースを拡張するため、ストリームで直接使用できます。

Arrays.asList(p.getClass().getFields()).forEach(consumer);

または、次のことができます。

import static com.github.fge.lambdas.consumers.Consumers.wrap;

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

と他のこと。


さらに良いfields.forEach((ThrowingConsumer<Field>) f -> out.println(f.get(o)))
-user2418306

ダウンボッターは説明していただけますか?
fge
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.