如何在Java 8的lambda表达式/流中抛出已检查异常?

367
如何在Java 8的lambda表达式中抛出已检查异常,例如在流中使用?
换句话说,我想让这样的代码编译通过:
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块。
18个回答

347
简单回答你的问题是:你不能直接这样做。这不是你的错。Oracle搞砸了。他们坚持使用了检查异常的概念,但在设计函数接口、流、lambda等时却不一致地忘记了处理检查异常。这正是像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()抛出EStream.map()也会抛出E(或者更具体地说,由于流是惰性管道,任何类似forEach()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需要解析为ClassNotFoundExceptionIOException的公共super类型,即Exception
需要对类型推断的定义进行微调,以便编译器在类型参数在允许使用类型列表的地方(throws子句)使用时寻找多个类型。然后,编译器报告的异常类型将与引用方法的已检查异常的原始throws声明一样具体,而不是一个通用的超级类型。
坏消息是,这意味着Oracle搞砸了。当然,他们不会破坏用户代码,但是将异常类型参数引入现有的函数接口将破坏所有明确使用这些接口的用户代码的编译。他们将不得不发明一些新的语法糖来解决这个问题。
更糟糕的消息是,Brian Goetz在2010年就已经讨论过这个话题(https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_javahttp://mail.openjdk.java.net/pipermail/lambda-dev/2010-June/001484.html),但我被告知这项调查最终没有取得成果,而且我所知道的Oracle目前也没有在解决受检异常和Lambda之间的交互问题上进行任何工作。

22
有趣。我相信有些人喜欢使用流是因为它可以更容易地编写并行代码,而有些人则喜欢使用流是因为它可以使代码更加简洁。 Brian Goetz 显然更注重并行性(因为他撰写了《Java并发实战》),而Robert Martin 更注重清晰的代码(因为他撰写了《代码整洁之道》)。 虽然添加样板式的 try/catch 语句会稍稍增加代码量,但为了实现并行化而付出这样的代价是很正常的,所以不难理解为什么 Brian Goetz 并不对在流中使用 checked 异常而感到震惊。同样地,也不难理解为什么 Robert Martin 讨厌 checked 异常,因为它们会增加代码的冗余。 - MarcG
7
我预测,在未来的几年里,处理流中的已检异常的困难将导致以下两种结果之一:人们将停止使用已检异常,或者每个人都将开始使用与我在UtilException答案中发布的非常相似的某些黑客技巧。如果不是因为已检异常是JDK的一部分,我会打赌Java 8的流是已检异常的最后一根稻草。虽然我喜欢并且在业务代码中使用已检异常(对于一些特定用例),但我更喜欢所有常见的JDK异常都扩展自Runtime。 - MarcG
11
问题在于函数式接口不会传递异常。我需要在 lambda 表达式内部使用 try-catch 块,但这根本没有任何意义。一旦 lambda 中以某种方式使用了 Class.forName,例如在 names.forEach(Class::forName) 中,问题就出现了。基本上,抛出已检查异常的方法因为其设计不当直接被排除在函数式编程作为函数式接口的参与者之外。 - Christian Hujer
32
“异常透明度”探索只是一次探索(起源于BGGA提案)。经过深入分析,我们发现它在价值和复杂性之间的平衡较差,并且存在一些严重问题(导致无法判断的推断问题,“catch X”是不可靠的等等)。通常情况下,某个语言想法似乎很有前途,甚至“显而易见”,但经过深入探索后发现其存在缺陷。这就是这种情况之一。 - Brian Goetz
14
@BrianGoetz,您提到的不可判定推断问题有一些公共信息可供查阅吗?我很好奇并且想了解一下。 - Christian Hujer
显示剩余10条评论

207
这个 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");
    }    

2015年11月更新 代码已经得到@PaoloC的帮助改进,请查看他下面的回答并给予赞同。他帮助解决了最后一个问题:现在编译器会要求您添加throw子句,一切都像您可以在Java 8流中本地抛出已检查的异常。

注1

上面LambdaExceptionUtil类的rethrow方法可以毫无顾虑地使用,并且可在任何情况下使用

注2

上面LambdaExceptionUtil类的uncheck方法是额外的方法,如果您不想使用它们,则可以安全地将它们从类中删除。如果您确实使用了它们,请小心使用,并在理解以下用例、优缺点和限制之前不要使用:

  • You may use the uncheck methods if you are calling a method which literally can never throw the exception that it declares. For example: new String(byteArr, "UTF-8") throws UnsupportedEncodingException, but UTF-8 is guaranteed by the Java spec to always be present. Here, the throws declaration is a nuisance and any solution to silence it with minimal boilerplate is welcome:

    String text = uncheck(() -> new String(byteArr, "UTF-8"));
    
  • You may use the uncheck methods if you are implementing a strict interface where you don't have the option for adding a throws declaration, and yet throwing an exception is entirely appropriate. Wrapping an exception just to gain the privilege of throwing it results in a stacktrace with spurious exceptions which contribute no information about what actually went wrong. A good example is Runnable.run(), which does not throw any checked exceptions.

  • In any case, if you decide to use the uncheck methods, be aware of these two consequences of throwing CHECKED exceptions without a throws clause:

    1. The calling-code won't be able to catch it by name (if you try, the compiler will say: "Exception is never thrown in body of corresponding try statement"). It will bubble and probably be caught in the main program loop by some catch Exception or catch Throwable, which may be what you want anyway.
    2. It violates the principle of least surprise: it will no longer be enough to catch RuntimeException to be able to guarantee catching all possible exceptions. For this reason, I believe this should not be done in framework code, but only in business code that you completely control.

参考文献


5
我认为这个答案遭到了不公正的负评。这段代码是可行的。检查异常应该被抛出或处理。如果你想要抛出它们,只需在包含数据流的方法中保留“throws子句”。但如果你想通过简单地包装和重新抛出来处理它们,我更喜欢使用上面的代码来“取消检查”异常并让它们自己冒泡。我所知道的唯一区别是冒泡异常不会扩展RuntimeException。我知道纯粹主义者不会喜欢这样做,但这会“必然会回来咬某个人吗”? 看起来不太可能。 - MarcG
5
@Christian Hujer,坦白说,那个投票者在我添加“优点、缺点和限制”说明之前就对先前的版本进行了下投票。所以也许当时他的投票是合理的。你不能教会别人如何打破规则而不至少尝试理解和解释后果。我发布这个问题的主要原因是为了获取对我的答案劣势的反馈。最终我收到了来自程序员stackexchange中另一个问题的反馈,然后我回到这里更新了我的答案。 - MarcG
14
@Unihedro 但为什么会变得难以维护呢?我看不出来为什么。有任何例子吗? - MarcG
2
在我看来,@SuppressWarnings("unchecked") 这种编译器的把戏完全是不可接受的。 - Thorbjørn Ravn Andersen
3
@PaoloC:抱歉让你等了这么长时间,我已经相应地更新了我的回答。我认为现在即使是Brian Goetz所提出的“破坏类型系统”的问题也不再存在了。 - MarcG
显示剩余23条评论

40

你可以!

扩展 @marcg 的 UtilException 并在必要的地方添加 throw E 语句:这样,编译器将要求您添加 throw 子句,就好像您可以在 Java 8 的流中本地抛出已检查的异常。

说明:只需将 LambdaExceptionUtil 复制/粘贴到您的 IDE 中,然后按照下面 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,你是对的,只需在 map 前面添加 <Integer>。实际上,Java 编译器无法推断出 Integer 的返回类型。其他部分应该是正确的。 - PaoloC
1
这对我很有效。它通过强制处理异常使MarcG的答案变得完美。 - Skystrider
1
解决上述问题的方法:像这样声明变量 Consumer<ThingType> expression = rethrowConsumer((ThingType thing) -> thing.clone()); 然后在内部 foreach 中使用该表达式。 - Skystrider
1
@Skychan:由于在这个修改后的新版本中你不再抑制任何异常,因此对推理系统来说可能会更加困难。在Brian Goetz下面的一些评论中,他谈到“异常透明度”导致了“不可决的推理问题”。 - MarcG
3
非常好。唯一不幸的是,它不能完美地处理抛出多个已检查异常的方法。在这种情况下,编译器会要求你捕获一个通用的超类型,例如 Exception - wvdz
显示剩余11条评论

28

你不能安全地这样做。你可能会作弊,但那样你的程序会出现故障,这最终会对某个人产生不良影响(应该是你,但往往我们的欺骗会对其他人造成不好的影响。)

这里有一种稍微安全的方法来解决问题(但我仍然不建议这样做。)

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

在这里,您所做的是捕获lambda中的异常,从流管道中抛出一个信号,表示计算异常失败,捕获该信号,并对该信号采取行动以抛出基础异常。关键是始终捕获合成异常,而不允许未声明抛出该异常的已检查异常泄漏。


24
只是一个问题;是什么设计决策导致Lambda表达式无法将检查异常传播到其上下文之外?请注意,我确实了解像Function等函数式接口不会throws任何异常;我只是好奇。 - fge
4
“throw w.cause;” 这句话不会让编译器抱怨方法没有抛出也没有捕获 Throwable。所以,在那里可能需要一个将其转换为 IOException 的强制类型转换。此外,如果 lambda 抛出了多种类型的已检查异常,catch 块的主体将变得有些丑陋,需要使用一些 instanceOf 检查(或具有类似目的的其他内容)来验证抛出了哪个已检查异常。 - Victor Stafusa - BozoNaCadeia
11
一个原因是你可能会忘记捕获WE异常,然后一个奇怪的异常(没有人知道如何处理)会泄露出来。你可能会说“但你已经捕获了异常,所以很安全。”在这个玩具例子中确实是这样。但是每当我看到一个代码库采用这种方法时,最终总会有人忘记。忽略异常的诱惑无边无际。另一个风险是使用它的安全性特定于特定的(使用场景、异常)组合。它无法很好地扩展到多个异常或不同的用途。 - Brian Goetz
2
@hoodaticus 我同意你的看法。话说回来,你是喜欢越来越多的包装(如上所示,增加了“遗忘”的风险),还是只创建4个聪明的接口,并使用lambda而不进行包装,就像https://dev59.com/KV4c5IYBdhLWcg3wscDI#30974991中所示?谢谢。 - PaoloC
15
坦白说,这个解决方案根本行不通。我认为流的要点是减少样板代码,而不是增加它。 - wvdz
显示剩余6条评论

15

只需使用我的项目NoExceptionjOOλ 的 Uncheckedthrowing-lambdasThrowable interfacesFaux 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));

10

我写了一个,它扩展了Stream API以允许您抛出已检查的异常。它使用了Brian Goetz的技巧。

你的代码将变成:

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
        }

2
这正是 Op 不想要的:在 lambda 中使用 try 块。此外,只有在没有其他代码将 IOException 包装在 RuntimeException 中时,它才能按预期工作。为了避免这种情况,可以使用自定义包装器-RuntimeException(定义为私有内部类)。 - Malte Hartwig

6

谢谢,一直在寻找广泛使用的库实现。 - officer

5
你做不到。
但是,你可能想看看我的一个项目,它可以让你更轻松地操作这样的“抛出lambda”。
在你的情况下,你将能够这样做:
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答案,并查看是否喜欢向项目添加此第三种可能性的想法。 - MarcG
但是,如果您想抛出原始的已检查异常,则必须在流周围添加try/catch以解包它,这仍然很糟糕! <-- 是的,但你别无选择。Lambda无法将已检查的异常传播到其上下文之外,这是一种设计“决策”(我个人认为这是一个缺陷,但没办法)。 - fge
关于你的想法,我不太明白它的作用,抱歉;毕竟你仍然会抛出未经检查的异常,那么这与我所做的有什么不同呢?(除了我有一个不同的接口) - fge
让我问你一下:你上面的 MyException 需要是一个未经检查的异常吗? - MarcG
好的。我的代码的作用是“取消选中”已检查的异常,这样就可以在流中使用了。它不会将已检查的异常包装在未检查的异常中,只是抛出已检查的异常本身。所以,如果您想要,您可以将此选项添加到您的项目中。它有一些限制,这也是为什么Brian Goetz说它并不完全安全的原因。但我更新了我的答案来解释这些限制,在我看来这些限制并不严重,然后由程序员决定是否打破规则。 - MarcG
显示剩余5条评论

5

太长不看就直接使用Lombok的@SneakyThrows注解。

Christian Hujer已经详细解释了为什么从流中抛出已检查异常在严格意义上是不可能的,因为Java存在一些限制。

其他答案已经解释了一些技巧,以克服语言的限制,但仍能够实现抛出"检查异常本身,而不会向流添加丑陋的try / catch"的要求,其中一些需要数十行额外的样板代码。

我将突出另一种选项,用它来完成此操作比所有其他选项都更加清洁: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);
}

我们只需要用IDE进行一次提取方法重构,并添加一行@SneakyThrows。该注解会负责添加所有样板代码,以确保您可以抛出已检查异常而无需将其包装在RuntimeException中并且无需显式声明异常。


4
不建议使用lombok。 - Dragas
1
@Dragas 设计一种语言,让人们觉得需要像 Lombok 这样的东西是不应该被鼓励的 ;) - Christian Hujer
啊,是的。你有能力以任何方式定义事物,调整所有旋钮,甚至添加自己的元素。但是你却选择放弃所有这些,使用一些混乱的东西来对编译器进行黑客攻击,生成一些隐式垃圾,除非你已经了解过lombok的内部工作原理以及它所生成的模式,否则读取将会很困难。不,像lombok这样的工具应该被抛弃,而应该采用生成代码的方法。这样至少,我就不需要一些IDE插件才能看到你太懒得使用同一个IDE生成的所有getter方法。 - Dragas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接