Java 8 のラムダ/ストリーム内からチェック例外をスローするにはどうすればいいですか? 質問する

Java 8 のラムダ/ストリーム内からチェック例外をスローするにはどうすればいいですか? 質問する

たとえば、ストリーム内で使用される Java 8 ラムダ内からチェック例外をスローするにはどうすればよいでしょうか?

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

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/を追加せずに、チェック例外自体をスローします。catch

ベストアンサー1

あなたの質問に対する簡単な答えは、少なくとも直接的にはできないということです。そしてそれはあなたのせいではありません。Oracleが失敗したのです。彼らはチェック例外の概念に固執していますが、関数インターフェイス、ストリーム、ラムダなどを設計する際にチェック例外を考慮することを一貫して忘れていました。これはすべて、チェック例外を失敗した実験と呼ぶ Robert C. Martin のような専門家にとっては格好の材料です。

私の意見では、これは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 EforEach()reduce()

不足しているのは、関数の引数からストリーム パイプラインを経由してターミナル操作にチェック例外を渡すための型パラメータの宣言です。次のコードは、このようなパススルー型パラメータを現在の構文で実際に宣言する方法を示しています (簡単にするために、遅延のない例です)。マークされた行の特殊なケース (以下で説明する制限) を除き、このコードはコンパイルされ、期待どおりに動作します。

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共通値、つまり に解決される必要があります。superClassNotFoundExceptionIOExceptionException

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

残念なことに、これは Oracle が失敗したことを意味します。確かに、ユーザー ランドのコードは壊れませんが、既存の機能インターフェイスに例外型パラメータを導入すると、これらのインターフェイスを明示的に使用するすべてのユーザー ランド コードのコンパイルが壊れます。これを修正するには、新しい構文シュガーを発明する必要があります。

さらに悪いニュースは、このトピックがすでに2010年にブライアン・ゲッツによって議論されていたことです(https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java参考文献) ですが、この調査は結局は成果が上がらず、私が知る限り、Oracle では現在、チェック例外とラムダの相互作用を軽減する取り組みは行われていないと聞いています。

おすすめ記事