193

Java 8 の Lambda 式を試す際に問題があります。通常は問題なく動作しますが、現在は をスローするメソッドがありますIOException。次のコードを見るのが最善です。

class Bank{
    ....
    public Set<String> getActiveAccountNumbers() throws IOException {
        Stream<Account> s =  accounts.values().stream();
        s = s.filter(a -> a.isActive());
        Stream<String> ss = s.map(a -> a.getNumber());
        return ss.collect(Collectors.toSet());
    }
    ....
}

interface Account{
    ....
    boolean isActive() throws IOException;
    String getNumber() throws IOException;
    ....
}

問題は、isActive メソッドと getNumber メソッドの可能な例外をキャッチする必要があるため、コンパイルできないことです。しかし、以下のように明示的に try-catch-Block を使用しても、例外をキャッチしないためコンパイルされません。JDK にバグがあるか、これらの例外をキャッチする方法がわかりません。

class Bank{
    ....
    //Doesn't compile either
    public Set<String> getActiveAccountNumbers() throws IOException {
        try{
            Stream<Account> s =  accounts.values().stream();
            s = s.filter(a -> a.isActive());
            Stream<String> ss = s.map(a -> a.getNumber());
            return ss.collect(Collectors.toSet());
        }catch(IOException ex){
        }
    }
    ....
}

どうすれば機能しますか?誰かが正しい解決策を教えてくれますか?

4

15 に答える 15

228

ラムダをエスケープするに、例外をキャッチする必要があります。

s = s.filter(a -> {
    try {
        return a.isActive();
    } catch (IOException e) {
        throw new UncheckedIOException(e);
    }
});

ラムダは記述した場所ではなく、JDK クラス内のまったく関係のない場所で評価されるという事実を考慮してください。そのため、チェックされた例外がスローされるポイントになり、その場所では宣言されません。

チェックされた例外をチェックされていない例外に変換するラムダのラッパーを使用して、これに対処できます。

public static <T> T uncheckCall(Callable<T> callable) {
    try {
        return callable.call();
    } catch (RuntimeException e) {
        throw e;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

あなたの例は次のように書かれます

return s.filter(a -> uncheckCall(a::isActive))
        .map(Account::getNumber)
        .collect(toSet());

私のプロジェクトでは、ラップせずにこの問題に対処しています。代わりに、コンパイラの例外チェックを効果的に無効にする方法を使用します。言うまでもなく、これは慎重に処理する必要があり、プロジェクトの全員が、チェックされた例外が宣言されていない場所に現れる可能性があることに注意する必要があります。これは配管コードです:

public static <T> T uncheckCall(Callable<T> callable) {
    try {
        return callable.call();
    } catch (Exception e) {
        return sneakyThrow(e);
    }
}

public static void uncheckRun(RunnableExc r) {
    try {
        r.run();
    } catch (Exception e) {
        sneakyThrow(e);
    }
}

public interface RunnableExc {
    void run() throws Exception;
}

@SuppressWarnings("unchecked")
private static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
    throw (T) t;
}

宣言していなくIOExceptionても、顔に投げられることを期待できます。collectすべてではありませんが、ほとんどの場合、とにかく例外を再スローし、一般的なエラーとして処理する必要があります。これらすべての場合において、明快さや正確さにおいて失われるものは何もありません。実際にその場で例外に反応したい他のケースに注意してください。開発者は、そこにキャッチする必要があることをコンパイラーによって認識されません。IOExceptionまた、コンパイラーは、そのような例外をスローできないと信じ込ませているため、キャッチしようとすると実際に文句を言います。

于 2013-11-03T20:04:24.007 に答える
33

ラムダを使用して静的な痛みを伝播することもできるため、全体が読みやすくなります。

s.filter(a -> propagate(a::isActive))

propagatehere はjava.util.concurrent.Callableパラメーターとして受け取り、呼び出し中にキャッチされた例外を に変換しますRuntimeException。Guava にも同様の変換メソッドThrowables#propagate(Throwable)があります。

このメソッドは、ラムダ メソッド チェーンに不可欠なようです。そのため、いつの日か、人気のあるライブラリの 1 つに追加されるか、この伝播動作がデフォルトになることを願っています。

public class PropagateExceptionsSample {
    // a simplified version of Throwables#propagate
    public static RuntimeException runtime(Throwable e) {
        if (e instanceof RuntimeException) {
            return (RuntimeException)e;
        }

        return new RuntimeException(e);
    }

    // this is a new one, n/a in public libs
    // Callable just suits as a functional interface in JDK throwing Exception 
    public static <V> V propagate(Callable<V> callable){
        try {
            return callable.call();
        } catch (Exception e) {
            throw runtime(e);
        }
    }

    public static void main(String[] args) {
        class Account{
            String name;    
            Account(String name) { this.name = name;}

            public boolean isActive() throws IOException {
                return name.startsWith("a");
            }
        }


        List<Account> accounts = new ArrayList<>(Arrays.asList(new Account("andrey"), new Account("angela"), new Account("pamela")));

        Stream<Account> s = accounts.stream();

        s
          .filter(a -> propagate(a::isActive))
          .map(a -> a.name)
          .forEach(System.out::println);
    }
}
于 2013-11-03T23:47:50.687 に答える
22

このUtilExceptionヘルパー クラスを使用すると、次のように 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 UtilException {

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

}

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

@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");
    }

ただし、次の利点、欠点、および制限を理解する前に使用しないでください

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

• 呼び出し元のコードが既にチェック済みの例外を処理している場合、コンパイラは、ストリームを含むメソッド宣言に throws 句を追加するよう通知します (そうしない場合、対応する try ステートメントの本体で例外がスローされることはありません)。 )。

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

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

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

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

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

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

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

結論として、ここでの制限は深刻ではなく、UtilExceptionクラスは恐れることなく使用できると思います。しかし、それはあなた次第です!

于 2014-12-26T20:15:33.507 に答える
11

Streamラムダをラップしてチェックされていない例外をスローし、後でターミナル操作でそのチェックされていない例外をアンラップすることで、独自のバリアントをロールできる可能性があります。

@FunctionalInterface
public interface ThrowingPredicate<T, X extends Throwable> {
    public boolean test(T t) throws X;
}

@FunctionalInterface
public interface ThrowingFunction<T, R, X extends Throwable> {
    public R apply(T t) throws X;
}

@FunctionalInterface
public interface ThrowingSupplier<R, X extends Throwable> {
    public R get() throws X;
}

public interface ThrowingStream<T, X extends Throwable> {
    public ThrowingStream<T, X> filter(
            ThrowingPredicate<? super T, ? extends X> predicate);

    public <R> ThrowingStream<T, R> map(
            ThrowingFunction<? super T, ? extends R, ? extends X> mapper);

    public <A, R> R collect(Collector<? super T, A, R> collector) throws X;

    // etc
}

class StreamAdapter<T, X extends Throwable> implements ThrowingStream<T, X> {
    private static class AdapterException extends RuntimeException {
        public AdapterException(Throwable cause) {
            super(cause);
        }
    }

    private final Stream<T> delegate;
    private final Class<X> x;

    StreamAdapter(Stream<T> delegate, Class<X> x) {
        this.delegate = delegate;
        this.x = x;
    }

    private <R> R maskException(ThrowingSupplier<R, X> method) {
        try {
            return method.get();
        } catch (Throwable t) {
            if (x.isInstance(t)) {
                throw new AdapterException(t);
            } else {
                throw t;
            }
        }
    }

    @Override
    public ThrowingStream<T, X> filter(ThrowingPredicate<T, X> predicate) {
        return new StreamAdapter<>(
                delegate.filter(t -> maskException(() -> predicate.test(t))), x);
    }

    @Override
    public <R> ThrowingStream<R, X> map(ThrowingFunction<T, R, X> mapper) {
        return new StreamAdapter<>(
                delegate.map(t -> maskException(() -> mapper.apply(t))), x);
    }

    private <R> R unmaskException(Supplier<R> method) throws X {
        try {
            return method.get();
        } catch (AdapterException e) {
            throw x.cast(e.getCause());
        }
    }

    @Override
    public <A, R> R collect(Collector<T, A, R> collector) throws X {
        return unmaskException(() -> delegate.collect(collector));
    }
}

次に、これを a とまったく同じ方法で使用できますStream

Stream<Account> s = accounts.values().stream();
ThrowingStream<Account, IOException> ts = new StreamAdapter<>(s, IOException.class);
return ts.filter(Account::isActive).map(Account::getNumber).collect(toSet());

このソリューションにはかなりのボイラープレートが必要になるため、クラス全体 (およびそれ以上) についてここで説明したこととまったく同じことを行う、既に作成したライブラリを確認することをお勧めします。Stream

于 2014-04-20T23:15:44.603 に答える
3

@marcg ソリューションを拡張すると、通常、Streams でチェック例外をスローしてキャッチできます。つまり、コンパイラは、ストリームの外にいるので、キャッチ/再スローするように求めます!!

@FunctionalInterface
public interface Predicate_WithExceptions<T, E extends Exception> {
    boolean test(T t) throws E;
}

/**
 * .filter(rethrowPredicate(t -> t.isActive()))
 */
public static <T, E extends Exception> Predicate<T> rethrowPredicate(Predicate_WithExceptions<T, E> predicate) throws E {
    return t -> {
        try {
            return predicate.test(t);
        } catch (Exception exception) {
            return throwActualException(exception);
        }
    };
}

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

次に、例は次のように記述されます (より明確に示すためにテストを追加します)。

@Test
public void testPredicate() throws MyTestException {
    List<String> nonEmptyStrings = Stream.of("ciao", "")
            .filter(rethrowPredicate(s -> notEmpty(s)))
            .collect(toList());
    assertEquals(1, nonEmptyStrings.size());
    assertEquals("ciao", nonEmptyStrings.get(0));
}

private class MyTestException extends Exception { }

private boolean notEmpty(String value) throws MyTestException {
    if(value==null) {
        throw new MyTestException();
    }
    return !value.isEmpty();
}

@Test
public void testPredicateRaisingException() throws MyTestException {
    try {
        Stream.of("ciao", null)
                .filter(rethrowPredicate(s -> notEmpty(s)))
                .collect(toList());
        fail();
    } catch (MyTestException e) {
        //OK
    }
}
于 2015-06-26T11:00:24.043 に答える
3

AbacusUtilでStreamTryを使用した以下の簡単なコードで解決できます。

Stream.of(accounts).filter(a -> Try.call(a::isActive)).map(a -> Try.call(a::getNumber)).toSet();

開示: 私は の開発者ですAbacusUtil

于 2016-12-02T01:45:02.127 に答える
2

IOException (RuntimeException に) 処理コードを適切に追加するには、メソッドは次のようになります。

Stream<Account> s =  accounts.values().stream();

s = s.filter(a -> { try { return a.isActive(); } 
  catch (IOException e) { throw new RuntimeException(e); }});

Stream<String> ss = s.map(a -> { try { return a.getNumber() }
  catch (IOException e) { throw new RuntimeException(e); }});

return ss.collect(Collectors.toSet());

問題は、IOExceptionを としてキャプチャしRuntimeException、 -- に戻す必要がありIOException、上記のメソッドにさらに多くのコードが追加されることです。

Streamこのように実行できる場合に使用する理由-そしてメソッドがスローIOExceptionされるため、追加のコードも必要ありません。

Set<String> set = new HashSet<>();
for(Account a: accounts.values()){
  if(a.isActive()){
     set.add(a.getNumber());
  } 
}
return set;
于 2013-11-04T02:10:18.290 に答える
1

あなたの例は次のように書くことができます:

import utils.stream.Unthrow;

class Bank{
   ....
   public Set<String> getActiveAccountNumbers() {
       return accounts.values().stream()
           .filter(a -> Unthrow.wrap(() -> a.isActive()))
           .map(a -> Unthrow.wrap(() -> a.getNumber()))
           .collect(Collectors.toSet());
   }
   ....
}

Unthrowクラスはここで取得できますhttps://github.com/SeregaLBN/StreamUnthrower

于 2016-03-04T13:18:01.827 に答える
1

より高いレベルで例外をスローするために、外部の (ストリームへの) エラー インジケーターを使用することもできます。

List<String> errorMessages = new ArrayList<>(); // error indicator
//..
errorMessages.clear();

List<String> names = new ArrayList<>(Arrays.asList("andrey", "angela", "pamela"));

names.stream()
.map(name -> {
    if (name != "pamela") {
      errorMessages.add(name + " is wrong here!"); 
      return null; // triggering the indicator
    }
    return name;
} )
.filter(elem -> (elem != null)) // bypassing propagation of only current unwanted data
//.filter(elem -> (errorMessages.size() == 0)) // or blocking any propagation once unwanted data detected
.forEach(System.out::println);

if (errorMessages.size() > 0) { // handling the indicator
  throw  new RuntimeException(String,join(", ", errorMessages));
}
于 2021-09-06T10:55:28.110 に答える
1

この問題を念頭に置いて、チェック例外とラムダを処理するための小さなライブラリを開発しました。カスタム アダプターを使用すると、既存の機能タイプと統合できます。

stream().map(unchecked(URI::new)) //with a static import

https://github.com/TouK/ThrowingFunction/

于 2016-02-19T18:22:41.387 に答える
0

サードパーティのライブラリを使用してもかまわない場合は、AOL のCyclops-reactライブラリ、disclosure::I am a contributor に、ここで役立つExceptionSoftenerクラスがあります。

 s.filter(softenPredicate(a->a.isActive()));
于 2016-02-24T17:36:20.110 に答える