Java 8ストリーム内からCHECKED例外をスローするにはどうすればよいですか?


287

Java 8ストリーム/ラムダからCHECKED例外をスローするにはどうすればよいですか?

つまり、次のようなコードをコンパイルしたいのです。

public List<Class> getClasses() throws ClassNotFoundException {     

    List<Class> classes = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
              .map(className -> Class.forName(className))
              .collect(Collectors.toList());                  
    return classes;
    }

Class.forName()上記のメソッドClassNotFoundExceptionはチェックされているをスローするため、このコードはコンパイルされません。

チェック済みの例外をランタイム例外内にラップせず、代わりにラップされた未チェックの例外をスローしたくないことに注意してください。チェックされた例外自体をスローし、醜いtry/ catchesをストリームに追加しないでください。


42
簡単な答えは、例外に関する規則を破ることなくしては、不可能です。もちろん、ごまかすことはできますが、他の方は喜んでその方法を説明しますが、これはごまかしであり、そのようなごまかしはしばしばあなたを噛むために戻ってくることに注意してください。あなたはすべきキャッチし、例外をし、それに対処します。ラップして、チェックした例外を後で再スローする場合は、安全に行うことができます。
Brian Goetz 2014

35
@ブライアン、私は他人にチートの仕方を教える必要はありません、私は自分をチートする方法を知っており、あなたが反対票を投じた以下の答えに私のチートの方法を投稿しました。Streamsでチェックされた例外を処理する良い方法はないと判断したJavaのディスカッションに参加していることを知っているので、私のこの質問に気付いたのは驚くべきことですが、「これは良くない」、という理由はありません。その後、もう一度try / catchesを追加します。
MarcG 2014

22
@Brian、率直に言って、実際には、人々がレガシーforステートメントをリファクタリングしようとすると、それらの半分はストリームに変換されますが、残りの半分はリファクタリングを放棄します。それらはコードを読むことをはるかに難しくします、確かに元のステートメントよりももっと。上記のコード例では、「throw ClassNotFoundException」を維持している限り、外部コードとの違いはありません。これが例外に関するルールに違反する実際の例をいくつか教えていただけますか?
MarcG 2014

10
未チェックの例外にラップするラッパーメソッドを作成すると、「コードの混乱」の問題に対処し、型システムを壊すことはありません。チェックされた例外の「卑劣なスロー」に頼るここでの答えは、型システムを壊します。これは、呼び出しコードがチェックされた例外を期待しない(またはキャッチできない)ためです。
Brian Goetz 2014

14
元の例外をアンラップして再スローするために、ストリームの周りに2回目のtry / catchが必要なため、コードの乱雑な反対には対応していません。逆に、チェックされた例外をスローする場合throws ClassNotFoundExceptionは、ストリームを含むメソッド宣言にkeepを保持するだけでよいので、呼び出しコードはチェックされた例外を予期してキャッチできるようになります。
MarcG 2014

回答:


250

あなたの質問に対する簡単な答えは、少なくとも直接はできません。そしてそれはあなたのせいではありません。オラクルはそれを台無しにした。彼らはチェック例外の概念にしがみついていますが、機能的なインターフェイス、ストリーム、ラムダなどを設計するときにチェック例外を処理することを一貫して忘れていました。チェック例外を失敗した実験と呼ぶロバートC.マーティンのような専門家たちのおかげです。

私の意見では、これはAPIの大きなバグであり、言語仕様の小さなバグです。

APIのバグは、チェックされた例外を転送するための機能を提供しないことです。この場合、関数型プログラミングでは実際にこれが非常に理にかなっています。以下で説明するように、そのような機能は簡単に可能でした。

言語仕様のバグは、型パラメーターが型のリストが許可されている状況でのみ使用される限り、型パラメーターが単一の型の代わりに型のリストを推測できないことです(throws句)。

Javaプログラマーとしての私たちの期待は、次のコードをコンパイルすることです。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class CheckedStream {
    // List variant to demonstrate what we actually had before refactoring.
    public List<Class> getClasses(final List<String> names) throws ClassNotFoundException {
        final List<Class> classes = new ArrayList<>();
        for (final String name : names)
            classes.add(Class.forName(name));
        return classes;
    }

    // The Stream function which we want to compile.
    public Stream<Class> getClasses(final Stream<String> names) throws ClassNotFoundException {
        return names.map(Class::forName);
    }
}

ただし、次のようになります。

cher@armor1:~/playground/Java/checkedStream$ javac CheckedStream.java 
CheckedStream.java:13: error: incompatible thrown types ClassNotFoundException in method reference
        return names.map(Class::forName);
                         ^
1 error

機能インタフェースが定義された方法は、現在の例外を転送からコンパイラを防ぎ-言うだろうない宣言がないStream.map()ものであればFunction.apply() throws EStream.map() throws E同様に。

欠けているのは、チェックされた例外を渡すための型パラメーターの宣言です。次のコードは、このようなパススルー型パラメーターが実際に現在の構文で宣言されている方法を示しています。マークされた行の特別なケース(以下で説明する制限)を除いて、このコードはコンパイルされ、期待どおりに動作します。

import java.io.IOException;
interface Function<T, R, E extends Throwable> {
    // Declare you throw E, whatever that is.
    R apply(T t) throws E;
}   

interface Stream<T> {
    // Pass through E, whatever mapper defined for E.
    <R, E extends Throwable> Stream<R> map(Function<? super T, ? extends R, E> mapper) throws E;
}   

class Main {
    public static void main(final String... args) throws ClassNotFoundException {
        final Stream<String> s = null;

        // Works: E is ClassNotFoundException.
        s.map(Class::forName);

        // Works: E is RuntimeException (probably).
        s.map(Main::convertClass);

        // Works: E is ClassNotFoundException.
        s.map(Main::throwSome);

        // Doesn't work: E is Exception.
        s.map(Main::throwSomeMore);  // error: unreported exception Exception; must be caught or declared to be thrown
    }   

    public static Class convertClass(final String s) {
        return Main.class;
    }   

    static class FooException extends ClassNotFoundException {}

    static class BarException extends ClassNotFoundException {}

    public static Class throwSome(final String s) throws FooException, BarException {
        throw new FooException();
    }   

    public static Class throwSomeMore(final String s) throws ClassNotFoundException, IOException  {
        throw new FooException();
    }   
}   

以下の場合にはthrowSomeMore、我々見たいIOException逃しされ、それが実際にミスException

例外の場合でも、型推論は単一の型を探しているように見えるため、これは完璧ではありません。型推論は単一の型をE必要とするため、との共通super部分ClassNotFoundExceptionであるに解決する必要IOExceptionがありExceptionます。

型のリストが許可されている場所で型パラメーターが使用されている場合(throws節)、コンパイラーが複数の型を探すように、型推論の定義を微調整する必要があります。その場合、コンパイラーによって報告される例外タイプthrowsは、単一のキャッチオールスーパータイプではなく、参照されるメソッドのチェック済み例外の元の宣言と同じくらい具体的です。

悪い知らせは、これはOracleがそれを台無しにしたことを意味するということです。確かに、それらはユーザーランドコードを壊しませんが、既存の機能インターフェースに例外タイプのパラメーターを導入すると、これらのインターフェースを明示的に使用するすべてのユーザーランドコードのコンパイルが中断します。これを修正するには、いくつかの新しい構文糖を発明する必要があります。

さらに悪いニュースは、このトピックは2010年にBrian Goetzによってすでに議論されていたということですhttps://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java(新しいリンク:http : //mail.openjdk.java.net/pipermail/lambda -dev / 2010-June / 001484.html)しかし、この調査は最終的にうまくいかず、チェックされた例外とラムダの間の相互作用を軽減するために私が知っているOracleでの現在の作業はないことが通知されます。


16
面白い。並列コードを簡単にするためにストリームを高く評価している人もいれば、よりクリーンなコードを許可している人もいると思います。Brian Goetzは明らかに(並列処理をJavaで作成したため)並列処理を重視しており、Robert MartinはCleanコード(Clean Codeを作成したため)を重視しています。ボイラープレートトライ/キャッチは、並列処理にかかる費用としてはわずかですが、ストリーム内でチェック済み例外を使用する問題にBrian Goetzが悩まされないのも不思議ではありません。また、Robert Martinがチェック例外を混乱させてしまうのを嫌うのも不思議ではありません。
MarcG 2014

5
数年以内に、ストリーム内のチェック済み例外を処理するのが困難になると、次の2つの結果のいずれかになると予測しています。私のUtilExceptionの答え。Java-8ストリームは、チェックされた例外の棺の最後の釘であるに違いない。チェックされた例外がJDKの一部であるという事実ではなかった。私はビジネスコードでチェックされた例外を気に入って使用していますが(特定のユースケースの場合)、ランタイムを拡張した一般的なJDK例外をすべて優先していました。
MarcG 2014

9
@Unihedro問題は、機能インターフェースが例外を転送しないことです。私はラムダのtry-catchブロックが必要になるでしょう、そしてそれは単に意味がありません。ラムダなどで何らかの形で使用されるとすぐに、問題が発生します。基本的に、チェックされた例外をスローするメソッドは、(悪い!)設計により、関数型プログラミングとして関数型インターフェイスとして直接参加することから除外されています。Class.forNamenames.forEach(Class::forName)
クリスチャンHujer 14

26
@ChristianHujer「例外透過性」の探査はまさにそれでした-探査(BGGA提案に由来する探査)。より詳細な分析の結果、価値と複雑さのバランスが悪いことがわかり、重大な問題が発生しました(特に、推論の問題が発生しやすくなり、「キャッチX」が不正確になりました)。言語のアイデアは非常に一般的です「明白」でさえ有望であるように見えますが、より深い調査の結果、欠陥があることが判明しました。これはそれらのケースの1つでした。
Brian Goetz

13
@BrianGoetzあなたが言及した決定不能な推論の問題について利用可能な公開情報はありますか?私は好奇心が強く、それを理解したいと思います。
Christian Hujer、2015年

169

このLambdaExceptionUtilヘルパークラスを使用すると、次のようにJavaストリームでチェック済みの例外を使用できます。

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
      .map(rethrowFunction(Class::forName))
      .collect(Collectors.toList());

Class::forNameスローClassNotFoundExceptionされ、チェックを。ストリーム自体もをスローしClassNotFoundException、一部のラッピングされていないチェックされていない例外はスローしません。

public final class LambdaExceptionUtil {

@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
    void accept(T t) throws E;
    }

@FunctionalInterface
public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
    void accept(T t, U u) throws E;
    }

@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
    R apply(T t) throws E;
    }

@FunctionalInterface
public interface Supplier_WithExceptions<T, E extends Exception> {
    T get() throws E;
    }

@FunctionalInterface
public interface Runnable_WithExceptions<E extends Exception> {
    void run() throws E;
    }

/** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
    return t -> {
        try { consumer.accept(t); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
    return (t, u) -> {
        try { biConsumer.accept(t, u); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

/** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
    return t -> {
        try { return function.apply(t); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */
public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
    return () -> {
        try { return function.get(); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** uncheck(() -> Class.forName("xxx")); */
public static void uncheck(Runnable_WithExceptions t)
    {
    try { t.run(); }
    catch (Exception exception) { throwAsUnchecked(exception); }
    }

/** uncheck(() -> Class.forName("xxx")); */
public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier)
    {
    try { return supplier.get(); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

/** uncheck(Class::forName, "xxx"); */
public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
    try { return function.apply(t); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

@SuppressWarnings ("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }

}

それを使用する方法に関する他の多くの例(静的にインポートした後LambdaExceptionUtil):

@Test
public void test_Consumer_with_checked_exceptions() throws IllegalAccessException {
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));

    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(System.out::println));
    }

@Test
public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
    List<Class> classes1
          = Stream.of("Object", "Integer", "String")
                  .map(rethrowFunction(className -> Class.forName("java.lang." + className)))
                  .collect(Collectors.toList());

    List<Class> classes2
          = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                  .map(rethrowFunction(Class::forName))
                  .collect(Collectors.toList());
    }

@Test
public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException {
    Collector.of(
          rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
          StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
    }

@Test    
public void test_uncheck_exception_thrown_by_method() {
    Class clazz1 = uncheck(() -> Class.forName("java.lang.String"));

    Class clazz2 = uncheck(Class::forName, "java.lang.String");
    }

@Test (expected = ClassNotFoundException.class)
public void test_if_correct_exception_is_still_thrown_by_method() {
    Class clazz3 = uncheck(Class::forName, "INVALID");
    }    

注1:上記rethrowLambdaExceptionUtilクラスのメソッドは、恐れることなく使用でき、どのような状況でも使用できます。最後の問題の解決に協力してくれたユーザー@PaoloCに感謝します。コンパイラーはスロー句を追加するように要求し、Java 8ストリームでネイティブにチェック例外をスローできるかのようにすべてのものを要求します。


注2:上記uncheckLambdaExceptionUtilクラスのメソッドはボーナスメソッドであり、使用したくない場合はクラスから安全に削除できます。それらを使用した場合は、注意して使用してください。次の使用例、長所/短所、制限を理解する前に行ってください。

uncheck宣言した例外を文字通り決してスローできないメソッドを呼び出す場合は、メソッドを使用できます。例:new String(byteArr、 "UTF-8")はUnsupportedEncodingExceptionをスローしますが、UTF-8はJava仕様によって常に存在することが保証されています。ここで、throws宣言は煩わしいものであり、最小限のボイラープレートで沈黙させるためのソリューションは歓迎されます。String text = uncheck(() -> new String(byteArr, "UTF-8"));

uncheckthrows宣言を追加するオプションがない厳密なインターフェイスを実装していても、例外のスローが完全に適切な場合は、メソッドを使用できます。例外をスローする特権を得るためだけに例外をラップすると、スタックトレースが発生し、実際には何がうまくいかなかったかについての情報を提供しない偽の例外が発生します。良い例は、チェックされた例外をスローしないRunnable.run()です。

•いずれの場合でも、uncheckメソッドを使用する場合は、throws句を使用せずにCHECKED例外をスローすることによる次の2つの結果に注意してください。1)呼び出しコードは名前でキャッチできません(試みた場合、コンパイラは言う:対応するtryステートメントの本体で例外がスローされることはありません)。それはバブルし、おそらくいくつかの「catch Exception」または「catch Throwable」によってメインプログラムループに捕捉されます。2)驚きを最小限に抑えるという原則に違反していRuntimeExceptionます。起こり得るすべての例外を確実にキャッチできるようになるには、もはや十分ではありません。このため、これはフレームワークコードではなく、完全に制御できるビジネスコードでのみ行う必要があると思います。


4
この答えは不当に反対投票だったと思います。コードは機能します。チェックされた例外は、スローまたは処理されることになっています。それらをスローしたい場合は、ストリームを含むメソッドに「スロー節」を保持してください。しかし、単純にラップして再スローすることでそれらに対処したい場合は、上記のコードを使用して例外を「チェック解除」し、それらを単独でバブリングさせることをお勧めします。私が知っている唯一の違いは、バブリング例外がRuntimeExceptionを拡張しないことです。私は純粋主義者がそれを嫌うことを知っていますが、これは「必然的に誰かを噛むために戻ってきます」でしょうか?可能性は低いようです。
MarcG 2014

4
@Christian Hujerは、反対意見に正直なところ、「利点、欠点、制限」の説明を追加する前に、以前のバージョンに反対意見を述べました。おそらくそれはその時に値するに値しました。少なくとも結果を理解して説明しようとしない限り、ルールを破る方法を誰かに教えることはできません。私がこの質問を投稿した主な理由は、私の回答の不利な点に対するフィードバックを得るためでした。私はこのフィードバックをここで得たのではなく、programmers.stackexchangeの別の質問から得ました。それから私はここに戻って私の答えを更新しました。
MarcG 2014

16
これは保守不可能なコードを奨励するためだけに反対票を投じました。これは巧妙ではありますが醜いハックであり、私はこの答えが役に立たないと思います。これもまた、この言語の別の「使用しない」です。
Unihedron、2014

12
@Unihedroですが、なぜメンテナンスできなくなったのですか?理由がわかりません。例はありますか?
MarcG 2014

2
私の意見では、@SuppressWarnings ("unchecked")コンパイラの策略は完全に受け入れられません。
するThorbjörnRavnアンデルセン

26

これを安全に行うことはできません。あなたは不正行為をすることができますが、プログラムは壊れて、これは必然的に誰かを噛むために戻ってきます(それはあなたであるべきですが、多くの場合、私たちの不正行為は他の誰かを爆破します)。

少し安全な方法を次に示します(ただし、これはお勧めしません)。

class WrappedException extends RuntimeException {
    Throwable cause;

    WrappedException(Throwable cause) { this.cause = cause; }
}

static WrappedException throwWrapped(Throwable t) {
    throw new WrappedException(t);
}

try 
    source.stream()
          .filter(e -> { ... try { ... } catch (IOException e) { throwWrapped(e); } ... })
          ...
}
catch (WrappedException w) {
    throw (IOException) w.cause;
}

ここでは、ラムダで例外をキャッチし、計算が例外的に失敗したことを示すシグナルをストリームパイプラインからスローし、シグナルをキャッチし、そのシグナルに基づいて基になる例外をスローしています。重要なのは、例外のスローを宣言せずにチェック済みの例外をリークさせるのではなく、常に合成例外をキャッチすることです。


18
ただの質問です。ラムダがチェックされた例外をコンテキスト外に伝播できない原因となった設計上の決定は何でしたか?Functionetcなどの機能的なインターフェースはthrows何もしないことを理解しています。気になるだけです。
2014

4
これthrow w.cause;は、メソッドがスローもキャッチもしないとコンパイラに不満を言わないThrowableでしょうか?したがって、そこへのキャストIOExceptionが必要になる可能性があります。さらに、ラムダが複数のタイプのチェック例外をスローする場合、instanceofチェックの例外がスローされたことを確認するためのチェック(または同様の目的を持つ何か)を行うと、キャッチの本体がやや醜くなります。
Victor Stafusa 2015

10
@schatten 1つの理由は、WEをキャッチするのを忘れることがあり、奇妙な例外(対処方法が誰にもわからない)がリークすることです。(しかし、「あなたは例外をキャッチしたので安全です。」と言うかもしれません。しかし、このおもちゃの例では、コードベースがこのアプローチを採用するのを見るたびに、結局誰かが忘れてしまいます。例外を無視する誘惑には限界がありません。)別のリスク安全に使用することは、特定の(使用サイト、例外)組み合わせに固有であるということです。これは、複数の例外や非同種の用途にうまく対応できません。
Brian Goetz 2016

2
@hoodaticus私はあなたに同意します。つまり、stackoverflow.com / a / 30974991/2365724に示されているように、ラッピングをより多くする(上記のように、「忘却」のリスクを高める)か、4つの巧妙なインターフェースを作成し、ラッピングなしでラムダを使用することを好みますか?ありがとう
PaoloC 2017年

10
率直に言って、このソリューションはまったく機能しません。ストリームのポイントは、ボイラープレートを増やすことではなく減らすことだと思いました。
wvdz 2017

24

あなたはできる!

@marcgを拡張UtilExceptionthrow E、必要に応じて追加します。これにより、コンパイラーはスローされた句を追加するように要求し、Java 8のストリームでネイティブにチェック済み例外をスローできるかのようにすべてのものを要求します

手順:LambdaExceptionUtilIDEにコピーして貼り付け、以下に示すように使用しますLambdaExceptionUtilTest

public final class LambdaExceptionUtil {

    @FunctionalInterface
    public interface Consumer_WithExceptions<T, E extends Exception> {
        void accept(T t) throws E;
    }

    @FunctionalInterface
    public interface Function_WithExceptions<T, R, E extends Exception> {
        R apply(T t) throws E;
    }

    /**
     * .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name))));
     */
    public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
        return t -> {
            try {
                consumer.accept(t);
            } catch (Exception exception) {
                throwActualException(exception);
            }
        };
    }

    /**
     * .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName))
     */
    public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E  {
        return t -> {
            try {
                return function.apply(t);
            } catch (Exception exception) {
                throwActualException(exception);
                return null;
            }
        };
    }

    @SuppressWarnings("unchecked")
    private static <E extends Exception> void throwActualException(Exception exception) throws E {
        throw (E) exception;
    }

}

使用法と動作を示すいくつかのテスト:

public class LambdaExceptionUtilTest {

    @Test(expected = MyTestException.class)
    public void testConsumer() throws MyTestException {
        Stream.of((String)null).forEach(rethrowConsumer(s -> checkValue(s)));
    }

    private void checkValue(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
    }

    private class MyTestException extends Exception { }

    @Test
    public void testConsumerRaisingExceptionInTheMiddle() {
        MyLongAccumulator accumulator = new MyLongAccumulator();
        try {
            Stream.of(2L, 3L, 4L, null, 5L).forEach(rethrowConsumer(s -> accumulator.add(s)));
            fail();
        } catch (MyTestException e) {
            assertEquals(9L, accumulator.acc);
        }
    }

    private class MyLongAccumulator {
        private long acc = 0;
        public void add(Long value) throws MyTestException {
            if(value==null) {
                throw new MyTestException();
            }
            acc += value;
        }
    }

    @Test
    public void testFunction() throws MyTestException {
        List<Integer> sizes = Stream.of("ciao", "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
        assertEquals(2, sizes.size());
        assertEquals(4, sizes.get(0).intValue());
        assertEquals(5, sizes.get(1).intValue());
    }

    private Integer transform(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
        return value.length();
    }

    @Test(expected = MyTestException.class)
    public void testFunctionRaisingException() throws MyTestException {
        Stream.of("ciao", null, "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
    }

}

1
申し訳ありませんが、@ setheronです。<Integer>前に追加してくださいmap。実際、JavaコンパイラはInteger戻り値の型を推測できません。他はすべて正しいはずです。
PaoloC、2015

1
これでうまくいきました。例外の処理を強制することで、MarcGの答えは完璧になりました。
Skychan 2015

1
上記の問題の解決策:このように変数を宣言します。次に、その式を内側のforeach内で使用します。
Skychan 2015

1
@Skychan:この変更された新しいバージョンでは、例外を抑制しなくなったため、推論システムではおそらく少し難しくなります。以下のコメントで、Brian Goetzは、「決定不可能な推論問題」につながる「例外の透明性」について語っています。
MarcG 2015年

3
非常に素晴らしい。唯一の残念なことは、複数のチェック済み例外をスローするメソッドでは完全に機能しないことです。この場合、コンパイラーは一般的なスーパータイプ(例:)をキャッチしますException
wvdz 2017

12

NoException(私のプロジェクト)、jOOλのUncheckedthrowing-lambdasThrowableインターフェース、またはFaux Pasのいずれかを使用するだけです。

// NoException
stream.map(Exceptions.sneak().function(Class::forName));

// jOOλ
stream.map(Unchecked.function(Class::forName));

// throwing-lambdas
stream.map(Throwing.function(Class::forName).sneakyThrow());

// Throwable interfaces
stream.map(FunctionWithThrowable.aFunctionThatUnsafelyThrowsUnchecked(Class::forName));

// Faux Pas
stream.map(FauxPas.throwingFunction(Class::forName));

7

私が書いたライブラリあなたがチェック例外をスローすることを可能にするために、ストリームAPIを拡張します。ブライアンゲッツのトリックを使用しています。

あなたのコードは

public List<Class> getClasses() throws ClassNotFoundException {     
    Stream<String> classNames = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String");

    return ThrowingStream.of(classNames, ClassNotFoundException.class)
               .map(Class::forName)
               .collect(Collectors.toList());
}

7

この回答は17に似ていますが、ラッパー例外定義を回避しています。

List test = new ArrayList();
        try {
            test.forEach(obj -> {

                //let say some functionality throws an exception
                try {
                    throw new IOException("test");
                }
                catch(Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }
        catch (RuntimeException re) {
            if(re.getCause() instanceof IOException) {
                //do your logic for catching checked
            }
            else 
                throw re; // it might be that there is real runtime exception
        }

1
これはシンプルで効果的なソリューションです。
Lin W

2
これはまさにOpが望まなかったことです:ラムダのブロックを試してください。さらに、tryブロックの外側にある他のコードがIOExceptionをRuntimeExceptionにラップしない限り、期待どおりに動作します。これを回避するには、カスタムのラッパー-RuntimeException(プライベート内部クラスとして定義)を使用できます。
Malte Hartwig、2017

5

できません。

ただし、そのような「スローイングラムダ」をより簡単に操作できるようにする私のプロジェクトの1つを確認することもできます。

あなたの場合、あなたはそれを行うことができるでしょう:

import static com.github.fge.lambdas.functions.Functions.wrap;

final ThrowingFunction<String, Class<?>> f = wrap(Class::forName);

List<Class> classes =
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(f.orThrow(MyException.class))
          .collect(Collectors.toList());

そしてキャッチMyException

それは一例です。別の例としては、.orReturn()デフォルト値を設定することができます。

これはまだ進行中の作業であることに注意してください。より良い名前、より多くの機能など


2
ただし、元のチェック済み例外をスローする場合は、ストリームの周囲にtry / catchを追加して、ラップを解除する必要があります。必要に応じてチェックされていない例外をスローでき、必要に応じてデフォルト値をストリームに返す.orThrowChecked()ことができるという考えが好きですが、チェックされた例外自体をスローできるようにするプロジェクトにメソッドを追加する必要があると思います。UtilExceptionこのページで私の答えを見て、この3番目の可能性をプロジェクトに追加するアイデアが気に入ったかどうかを確認してください。
MarcG 2014

「しかし、元のチェック済み例外をスローしたい場合は、ストリームの周りにtry / catchを追加して、ラップを解除する必要があります。これはまだひどいです!」<-はい、しかし選択の余地はありません。ラムダチェックされた例外をコンテキスト外に伝播することはできません。それは設計の「決定」です(私はそれを欠陥と見なしていますが、個人的にはオーウェル)
fge

あなたの考えに関して、申し訳ありませんが、私はそれが何をしているのかよくわかりません。結局、あなたはまだチェックされていない状態で投げます、それでこれは私がやっていることとどう違うのですか?(ただし、インターフェイスが異なります)
fge

とにかく、あなたはプロジェクトに貢献することを歓迎します!また、Stream実装に気づいたことがありますAutoCloseableか?
2014

あなたにこれを聞いてみましょう:あなたのMyException上記はチェックされていない例外である必要がありますか?
MarcG 2014

3

高度なソリューションの上のコメントを要約すると、APIのようなビルダーで未チェックの関数に特別なラッパーを使用して、回復、再スロー、およびサプレッションを提供します。

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(Try.<String, Class<?>>safe(Class::forName)
                  .handle(System.out::println)
                  .unsafe())
          .collect(toList());

以下のコードは、Consumer、Supplier、Functionの各インターフェースを示しています。簡単に拡張できます。この例では、いくつかの公開キーワードが削除されました。

クラスTryはクライアントコードのエンドポイントです。安全なメソッドには、関数の種類ごとに一意の名前を付けることができます。 CheckedConsumerCheckedSupplier、およびCheckedFunctionは、Tryとは独立し使用できるlib関数のチェック済みアナログです。

CheckedBuilderは、いくつかのチェックされた関数で例外を処理するためのインターフェースです。orTryは、前が失敗した場合に別の同じタイプの関数を実行できるようにします。handleは、例外タイプフィルタリングを含む例外処理を提供します。ハンドラーの順序は重要です。安全でないメソッドを減らし、スローして実行チェーンの最後の例外を再スローします。すべての関数が失敗した場合、reduceメソッドorElseおよびorElseGetは、オプションの値のような代替値を返します。また、メソッドsuppressもあります。 CheckedWrapperは、CheckedBuilderの一般的な実装です。

final class Try {

    public static <T> CheckedBuilder<Supplier<T>, CheckedSupplier<T>, T> 
        safe(CheckedSupplier<T> supplier) {
        return new CheckedWrapper<>(supplier, 
                (current, next, handler, orResult) -> () -> {
            try { return current.get(); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().get() : orResult.apply(ex);
            }
        });
    }

    public static <T> Supplier<T> unsafe(CheckedSupplier<T> supplier) {
        return supplier;
    }

    public static <T> CheckedBuilder<Consumer<T>, CheckedConsumer<T>, Void> 
        safe(CheckedConsumer<T> consumer) {
        return new CheckedWrapper<>(consumer, 
                (current, next, handler, orResult) -> t -> {
            try { current.accept(t); } catch (Exception ex) {
                handler.accept(ex);
                if (next.isPresent()) {
                    next.get().accept(t);
                } else {
                    orResult.apply(ex);
                }
            }
        });
    }

    public static <T> Consumer<T> unsafe(CheckedConsumer<T> consumer) {
        return consumer;
    }

    public static <T, R> CheckedBuilder<Function<T, R>, CheckedFunction<T, R>, R> 
        safe(CheckedFunction<T, R> function) {
        return new CheckedWrapper<>(function, 
                (current, next, handler, orResult) -> t -> {
            try { return current.applyUnsafe(t); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().apply(t) : orResult.apply(ex);
            }
        });
    }

    public static <T, R> Function<T, R> unsafe(CheckedFunction<T, R> function) {
        return function;
    }

    @SuppressWarnings ("unchecked")
    static <T, E extends Throwable> T throwAsUnchecked(Throwable exception) throws E { 
        throw (E) exception; 
    }
}

@FunctionalInterface interface CheckedConsumer<T> extends Consumer<T> {
    void acceptUnsafe(T t) throws Exception;
    @Override default void accept(T t) {
        try { acceptUnsafe(t); } catch (Exception ex) {
            Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedFunction<T, R> extends Function<T, R> {
    R applyUnsafe(T t) throws Exception;
    @Override default R apply(T t) {
        try { return applyUnsafe(t); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedSupplier<T> extends Supplier<T> {
    T getUnsafe() throws Exception;
    @Override default T get() {
        try { return getUnsafe(); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

interface ReduceFunction<TSafe, TUnsafe, R> {
    TSafe wrap(TUnsafe current, Optional<TSafe> next, 
            Consumer<Throwable> handler, Function<Throwable, R> orResult);
}

interface CheckedBuilder<TSafe, TUnsafe, R> {
    CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next);

    CheckedBuilder<TSafe, TUnsafe, R> handle(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handle(
            Class<E> exceptionType, Consumer<E> handler);

    CheckedBuilder<TSafe, TUnsafe, R> handleLast(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Class<E> exceptionType, Consumer<? super E> handler);

    TSafe unsafe();
    TSafe rethrow(Function<Throwable, Exception> transformer);
    TSafe suppress();
    TSafe orElse(R value);
    TSafe orElseGet(Supplier<R> valueProvider);
}

final class CheckedWrapper<TSafe, TUnsafe, R> 
        implements CheckedBuilder<TSafe, TUnsafe, R> {

    private final TUnsafe function;
    private final ReduceFunction<TSafe, TUnsafe, R> reduceFunction;

    private final CheckedWrapper<TSafe, TUnsafe, R> root;
    private CheckedWrapper<TSafe, TUnsafe, R> next;

    private Consumer<Throwable> handlers = ex -> { };
    private Consumer<Throwable> lastHandlers = ex -> { };

    CheckedWrapper(TUnsafe function, 
            ReduceFunction<TSafe, TUnsafe, R> reduceFunction) {
        this.function = function;
        this.reduceFunction = reduceFunction;
        this.root = this;
    }

    private CheckedWrapper(TUnsafe function, 
            CheckedWrapper<TSafe, TUnsafe, R> prev) {
        this.function = function;
        this.reduceFunction = prev.reduceFunction;
        this.root = prev.root;
        prev.next = this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next) {
        return new CheckedWrapper<>(next, this);
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handle(
            Consumer<Throwable> handler) {
        handlers = handlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handle(Class<E> exceptionType, Consumer<E> handler) {
        handlers = handlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Consumer<Throwable> handler) {
        lastHandlers = lastHandlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handleLast(Class<E> exceptionType, Consumer<? super E> handler) {
        lastHandlers = lastHandlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public TSafe unsafe() {
        return root.reduce(ex -> Try.throwAsUnchecked(ex));
    }

    @Override
    public TSafe rethrow(Function<Throwable, Exception> transformer) {
        return root.reduce(ex -> Try.throwAsUnchecked(transformer.apply(ex)));
    }

    @Override public TSafe suppress() {
        return root.reduce(ex -> null);
    }

    @Override public TSafe orElse(R value) {
        return root.reduce(ex -> value);
    }

    @Override public TSafe orElseGet(Supplier<R> valueProvider) {
        Objects.requireNonNull(valueProvider);
        return root.reduce(ex -> valueProvider.get());
    }

    private TSafe reduce(Function<Throwable, R> orResult) {
        return reduceFunction.wrap(function, 
                Optional.ofNullable(next).map(p -> p.reduce(orResult)), 
                this::handle, orResult);
    }

    private void handle(Throwable ex) {
        for (CheckedWrapper<TSafe, TUnsafe, R> current = this; 
                current != null; 
                current = current.next) {
            current.handlers.accept(ex);
        }
        lastHandlers.accept(ex);
    }
}

3

TL; DR Lombokを使用するだけ@SneakyThrowsです。

Christian Hujerは、Javaの制限により、ストリームからチェック例外をスローすることが厳密には不可能である理由をすでに詳しく説明しています。

いくつかの他の回答には、言語の限界を周りを取得するためのトリックを説明したが、まだ投げの要件満たすことができることしている「チェック例外自体を、ストリームに醜いのtry /キャッチを追加せず」、そのうちのいくつかは、追加の行数十を必要としますボイラープレートの。

これを行うための別のオプションとして、IMHOが他のすべてのオプションよりもはるかにクリーンであるLombokを強調します@SneakyThrows。それは他の答えで通り過ぎて言及されましたが、多くの不必要な詳細の下に少し埋められました。

結果のコードは次のように単純です。

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes =
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                .map(className -> getClass(className))
                .collect(Collectors.toList());
    return classes;
}

@SneakyThrows                                 // <= this is the only new code
private Class<?> getClass(String className) {
    return Class.forName(className);
}

必要なのは、1つのExtract Methodリファクタリング(IDEによって行われる)と、1つの追加の行だけです@SneakyThrows。アノテーションはすべてのボイラープレートの追加を処理して、チェックされた例外をラップせずに、RuntimeException明示的に宣言する必要なくチェック例外をスローできるようにします。


4
ロンボクの使用はお勧めできません。
Dragas

2

チェックされていない例外をラップするラッパーメソッドを記述したり、別の機能インターフェイス(同じ戻り値型Rを持つ)を表す追加パラメーターでラッパーを拡張したりすることもできます。この場合、例外の場合に実行されて返される関数を渡すことができます。以下の例をご覧ください。

private void run() {
    List<String> list = Stream.of(1, 2, 3, 4).map(wrapper(i ->
            String.valueOf(++i / 0), i -> String.valueOf(++i))).collect(Collectors.toList());
    System.out.println(list.toString());
}

private <T, R, E extends Exception> Function<T, R> wrapper(ThrowingFunction<T, R, E> function, 
Function<T, R> onException) {
    return i -> {
        try {
            return function.apply(i);
        } catch (ArithmeticException e) {
            System.out.println("Exception: " + i);
            return onException.apply(i);
        } catch (Exception e) {
            System.out.println("Other: " + i);
            return onException.apply(i);
        }
    };
}

@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
    R apply(T t) throws E;
}

2

これは、元の問題に対する別のビューまたは解決策です。ここで、例外がスローされた場合にケースを検出して処理するオプションを使用して、値の有効なサブセットのみを処理するコードを作成するオプションがあることを示します。

    @Test
    public void getClasses() {

        String[] classNames = {"java.lang.Object", "java.lang.Integer", "java.lang.Foo"};
        List<Class> classes =
                Stream.of(classNames)
                        .map(className -> {
                            try {
                                return Class.forName(className);
                            } catch (ClassNotFoundException e) {
                                // log the error
                                return null;
                            }
                        })
                        .filter(c -> c != null)
                        .collect(Collectors.toList());

        if (classes.size() != classNames.length) {
            // add your error handling here if needed or process only the resulting list
            System.out.println("Did not process all class names");
        }

        classes.forEach(System.out::println);
    }

1

上記のコメントに同意します。Stream.mapを使用する場合、例外をスローしない関数の実装に限定されます。

ただし、以下のようにスローする独自のFunctionalInterfaceを作成できます。

@FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
  void accept(T instance) throws X;
}

次に、次に示すように、ラムダまたは参照を使用して実装します。

import java.io.FileWriter;
import java.io.IOException;

//lambda expressions and the execute around method (EAM) pattern to
//manage resources

public class FileWriterEAM  {
  private final FileWriter writer;

  private FileWriterEAM(final String fileName) throws IOException {
    writer = new FileWriter(fileName);
  }
  private void close() throws IOException {
    System.out.println("close called automatically...");
    writer.close();
  }
  public void writeStuff(final String message) throws IOException {
    writer.write(message);
  }
  //...

  public static void use(final String fileName, final UseInstance<FileWriterEAM, IOException> block) throws IOException {

    final FileWriterEAM writerEAM = new FileWriterEAM(fileName);    
    try {
      block.accept(writerEAM);
    } finally {
      writerEAM.close();
    }
  }

  public static void main(final String[] args) throws IOException {

    FileWriterEAM.use("eam.txt", writerEAM -> writerEAM.writeStuff("sweet"));

    FileWriterEAM.use("eam2.txt", writerEAM -> {
        writerEAM.writeStuff("how");
        writerEAM.writeStuff("sweet");      
      });

    FileWriterEAM.use("eam3.txt", FileWriterEAM::writeIt);     

  }


 void writeIt() throws IOException{
     this.writeStuff("How ");
     this.writeStuff("sweet ");
     this.writeStuff("it is");

 }

}

1

map操作によってスローされる可能性があるチェック済み例外を処理する組み込みの方法は、それらを内にカプセル化することだけCompletableFutureです。(Optional例外を保存する必要がない場合、An はより簡単な代替手段です。)これらのクラスは、機能的な方法で条件付き操作を表すことができるようにすることを目的としています。

いくつかの重要なヘルパーメソッドが必要ですが、比較的簡潔なコードにたどり着くことができますが、ストリームの結果がmap操作が正常に完了したことを条件とすることは明らかです。これは次のようになります。

    CompletableFuture<List<Class<?>>> classes =
            Stream.of("java.lang.String", "java.lang.Integer", "java.lang.Double")
                  .map(MonadUtils.applyOrDie(Class::forName))
                  .map(cfc -> cfc.thenApply(Class::getSuperclass))
                  .collect(MonadUtils.cfCollector(ArrayList::new,
                                                  List::add,
                                                  (List<Class<?>> l1, List<Class<?>> l2) -> { l1.addAll(l2); return l1; },
                                                  x -> x));
    classes.thenAccept(System.out::println)
           .exceptionally(t -> { System.out.println("unable to get class: " + t); return null; });

これにより、次の出力が生成されます。

[class java.lang.Object, class java.lang.Number, class java.lang.Number]

このapplyOrDieメソッドはFunction、例外をスローするを受け取り、それを、Function既に完了したCompletableFuture-元の関数の結果で正常に完了するか、スローされた例外で例外的に完了するかを返すに変換します。

2番目のmap操作はStream<CompletableFuture<T>>、単なるの代わりにを取得したことを示していますStream<T>CompletableFutureアップストリーム操作が成功した場合にのみ、この操作の実行を処理します。APIはこれを明白にしますが、比較的簡単です。

あなたがcollectフェーズに到達するまで、それはです。ここで、かなり重要なヘルパーメソッドが必要になります。我々は(この場合は、「リフト」に、通常の収集作業をしたいtoList())「内部」CompletableFuture- cfCollector()私たちが使用していることを行うことができますsupplieraccumulatorcombiner、をし、finisherそのことについて全く何も知る必要はありませんCompletableFuture

ヘルパーメソッドは、私のMonadUtilsクラスのGitHubにあります。これは、まだ作業中です。


1

おそらく、より優れた、より機能的な方法は、例外をラップして、ストリーム内でさらに伝播することです。たとえば、VavrTryタイプを見てください

例:

interface CheckedFunction<I, O> {
    O apply(I i) throws Exception; }

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return i -> {
        try {
            return f.apply(i);
        } catch(Exception ex) {

            throw new RuntimeException(ex);
        }
    } }

fileNamesToRead.map(unchecked(file -> Files.readAllLines(file)))

または

@SuppressWarnings("unchecked")
private static <T, E extends Exception> T throwUnchecked(Exception e) throws E {
    throw (E) e;
}

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return arg -> {
        try {
            return f.apply(arg);
        } catch(Exception ex) {
            return throwUnchecked(ex);
        }
    };
}

2番目の実装では、例外をRuntimeExceptionthrowUncheckedほとんどすべての一般的な例外はJavaではチェックされていないものとして扱われるため、機能します。


1

私はこの種のラッピング例外を使用しています:

public class CheckedExceptionWrapper extends RuntimeException {
    ...
    public <T extends Exception> CheckedExceptionWrapper rethrow() throws T {
        throw (T) getCause();
    }
}

これらの例外を静的に処理する必要があります。

void method() throws IOException, ServletException {
    try { 
        list.stream().forEach(object -> {
            ...
            throw new CheckedExceptionWrapper(e);
            ...            
        });
    } catch (CheckedExceptionWrapper e){
        e.<IOException>rethrow();
        e.<ServletExcepion>rethrow();
    }
}

オンラインでお試しください!

例外はとにかく最初のrethrow()呼び出し中に再スローされますが(ああ、Javaジェネリック...)、この方法により、可能性のある例外の厳密な静的定義を取得できます(それらをで宣言する必要がありますthrows)。そして、instanceof何も必要ありません。


-1

私はこのアプローチが正しいと思います:

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes;
    try {
        classes = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String").map(className -> {
            try {
                return Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new UndeclaredThrowableException(e);
            }
        }).collect(Collectors.toList());
    } catch (UndeclaredThrowableException e) {
        if (e.getCause() instanceof ClassNotFoundException) {
            throw (ClassNotFoundException) e.getCause();
        } else {
            // this should never happen
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
    return classes;
}

内部のチェック例外をラップCallableUndeclaredThrowableException(この例外のユースケースをだ)し、外にそれをアンラップ。

はい、それは醜いと思います。並列ストリームで作業していて並列化がコードの読みやすさを正当化する客観的な利点をもたらさない限り、私はこの場合ラムダを使用しないようにアドバイスし、古き良きループにフォールバックします。

他の多くの人が指摘したように、この状況には解決策があり、そのうちの1つがJavaの将来のバージョンになることを願っています。


1
(1)このような例を示す回答がすでにいくつかあるので、あなたの回答は、まだカバーされていないQ&Aに何を追加しますか?このような重複した回答を投稿すると、サイトが煩雑になります。(2)OPは、具体的にはこれを行いたくないと述べています。「チェック済み例外をランタイム例外内にラップせず、ラップされた未チェック例外を代わりにスローしたくないことに注意してください。」
Radiodef
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.