如何从Java 8流中抛出CHECKED异常?

Mar*_*rcG 270 java lambda checked-exceptions java-8 java-stream

如何从Java 8流/ lambdas中抛出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;
    }
Run Code Online (Sandbox Code Playgroud)

此代码无法编译,因为Class.forName()上面的方法抛出ClassNotFoundException,检查.

请注意我不希望将已检查的异常包装在运行时异常中,而是抛出包装的未经检查的异常.我想抛出已检查的异常本身,而不是向流添加丑陋的try/ catches.

Chr*_*jer 237

你的问题的简单答案是:你不能,至少不是直接的.这不是你的错.甲骨文搞砸了.他们坚持检查异常的概念,但在设计功能接口,流,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);
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,它给出了:

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
Run Code Online (Sandbox Code Playgroud)

其中功能接口当前定义的方式防止编译器转发例外-有没有宣言,告诉Stream.map(),如果Function.apply() throws E,Stream.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();
    }   
}   
Run Code Online (Sandbox Code Playgroud)

throwSomeMore我们希望IOException被遗漏的情况下,但实际上错过了Exception.

这并不完美,因为类型推断似乎在寻找单一类型,即使在异常情况下也是如此.因为类型推断需要一个单一类型的,E需要解决一个共同superClassNotFoundExceptionIOException,这是Exception.

需要对类型推断的定义进行调整,以便如果在允许类型列表(throws子句)的情况下使用类型参数,编译器将查找多个类型.然后,编译器报告的异常类型将throws与引用方法的已检查异常的原始声明一样具体,而不是单个catch-all超类型.

坏消息是,这意味着Oracle搞砸了它.当然它们不会破坏用户域代码,但是将异常类型参数引入现有的功能接口会破坏显式使用这些接口的所有用户域代码的编译.他们必须发明一些新的语法糖来解决这个问题.

更糟糕的消息是Brian Goetz在2010年已经讨论过这个话题https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java(新链接:http://mail.openjdk.java.net/pipermail/lambda -dev/2010-June/001484.html)似乎这个问题被忽略了,所以我想知道Oracle在做什么.

  • @ChristianHujer"异常透明度"探索就是这样 - 探索(源自BGGA提案的探索).经过深入分析,我们发现它提供了价值和复杂性之间的不平衡,并且它存在一些严重的问题(导致不可判断的推理问题,并且"捕捉X"不健全,等等.)语言理念极为常见似乎很有希望 - 甚至"显而易见" - 但经过深入探索后,结果证明是有缺陷的.这是其中一个案例. (24认同)
  • 有趣.我相信有些人会欣赏使用简单并行代码的流,而其他人则可以使用更清晰的代码.Brian Goetz显然更关心并行性(因为他在实践中编写了Java Concurrency),而Robert Martin更关心干净的代码(因为他撰写了清洁代码书).Boilerplate try/catches是支付并行性的一个小代价,因此难怪Brian Goetz对使用流内检查异常的问题并不感到震惊.同样难怪罗伯特·马丁讨厌检查异常,因为它们增加了混乱. (15认同)
  • @BrianGoetz有没有关于你提到的不可判断的推理问题的公开信息?我好奇,想了解它. (12认同)
  • @Unihedro问题仍然是功能接口不转发异常.我需要在lambda里面的`try-catch`块*,这根本没有任何意义.只要在lambda中以某种方式使用`Class.forName`,例如在`names.forEach(Class :: forName)`中,就会出现问题.基本上,抛出已检查异常的方法已被排除在直接参与函数式编程之外,通过(差!)设计. (9认同)
  • 我预测,在几年内,在流中处理受检异常的困难将导致以下两种结果之一:人们将停止使用受检异常,或者每个人都将开始使用一些非常像我发布的黑客我的 UtilException 答案。我敢打赌 Java-8 流是检查异常棺材上的最后一颗钉子,不是因为检查异常是 JDK 的一部分。尽管我喜欢并在业务代码中使用检查异常(对于某些特定用例),但我更喜欢所有常见的 JDK 异常扩展运行时。 (5认同)
  • 我想知道为什么Oracle认为创建与其他重要旧功能根本不兼容的新功能是可以的,并且通过说:只需添加try/catches来回应批评!也许(阴谋理论警告!!!)他们还认为他们是一个失败的实验,并且正在创建问题,以便应该弃用已检查的共识构建.然后,在几年内,他们可能只是删除强制我们添加throws子句的语法糖. (5认同)
  • "流"操作被懒惰地评估不是问题.我不希望`map`操作抛出异常,我希望`Stream`在它发生时抛出异常.为此,需要通过接口传播异常.使用未经检查的异常,它会以这种方式工作,因为传播是非常有用的,因为隐式`throws RuntimeException,Error`.对于已检查的异常,传播将需要一个异常类型参数,并且只是对Java 7 multi-catch引入的智能的一点扩展. (3认同)
  • 由于`Stream`中间操作被懒惰地评估(当执行终端操作时),重新抛出lambda异常的`map`操作的命题将不起作用:`map`不会立即调用`lambda`.避免这种情况的唯一方法是在"Stream"上为异常设置第二个类型参数,并让所有终端操作抛出此异常类型.但我认为在大多数情况下,这只会使API混乱而没有太大的附加价值.也许一个`Stream`包装器可以做到这一点?(具有特定的功能接口) (2认同)
  • @MarcG 关于你建议的两个结果,现在还有第三种可能:人们转向不存在这些问题的语言。就像科特林一样。 (2认同)

Mar*_*rcG 167

这个LambdaExceptionUtil帮助器类允许您在Java流中使用任何已检查的异常,如下所示:

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

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

}
Run Code Online (Sandbox Code Playgroud)

关于如何使用它的许多其他示例(静态导入后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");
    }    
Run Code Online (Sandbox Code Playgroud)

注1:rethrow上述LambdaExceptionUtil类的方法可以毫无顾虑地使用,并且可以在任何情况下使用.非常感谢帮助解决最后一个问题的用户@PaoloC:现在编译器会要求你添加throw子句和所有东西,好像你可以在Java 8流上本地抛出已检查的异常.


注2:uncheck上述LambdaExceptionUtil类的方法是奖励方法,如果您不想使用它们,可以安全地从课程中删除它们.如果您确实使用过它们,请小心操作,而不是在了解以下用例,优点/缺点和限制之前:

uncheck如果要调用的方法实际上永远不会抛出它声明的异常,则可以使用这些方法.例如:new String(byteArr,"UTF-8")抛出UnsupportedEncodingException,但Java规范保证UTF-8始终存在.在这里,投掷声明是一个麻烦,任何解决方案,以最小的样板沉默它是受欢迎的:String text = uncheck(() -> new String(byteArr, "UTF-8"));

uncheck如果您实现的是严格的接口,而您没有添加throws声明的选项,则可以使用这些方法,但抛出异常是完全合适的.为了获得抛出它的特权而包装异常会导致带有虚假异常的堆栈跟踪,这些异常不会导致实际出错的信息.一个很好的例子是Runnable.run(),它不会抛出任何已检查的异常.

•在任何情况下,如果您决定使用这些uncheck方法,请注意在没有throws子句的情况下抛出CHECKED异常的这两个后果:1)调用代码将无法按名称捕获它(如果您尝试,编译器会说:异常永远不会抛出相应的try语句的主体).它会冒泡并且可能会被主程序循环中的一些"捕获异常"或"捕获Throwable"捕获,这可能是你想要的.2)它违反了最不惊讶的原则:它将不再足以捕获RuntimeException以保证捕获所有可能的异常.出于这个原因,我认为这不应该在框架代码中完成,而只能在您完全控制的业务代码中完成.

  • 我只是因为鼓励[不可维护的代码](https://www.thc.org/root/phun/unmaintain.html)而被投票.这是一个丑陋的黑客,虽然是一个聪明的黑客,我永远不会觉得这个答案有用.这又是另一种"不使用"的语言. (14认同)
  • @Unihedro但为什么它变得不可维护?我不明白为什么.任何例子? (11认同)
  • 我觉得这个答案被不公平地贬低了.代码有效.应该抛出或处理被检查的异常.如果要抛出它们,只需在包含流的方法中保留"throws子句"即可.但是如果你想通过简单的包装和重新抛出来处理它们,我想我更喜欢使用上面的代码来"解开"异常并让它们自己冒泡.我所知道的唯一区别是冒泡异常不会扩展RuntimeException.我知道纯粹主义者不会喜欢这样,但这会"不可避免地回来咬人"吗?似乎不太可能. (4认同)
  • @Christian Hujer,老实说,在我添加"优点,缺点和限制"解释之前,他还没有使用过先前的版本.所以也许它当时是值得的.你不能教别人如何违反规则,至少不试图理解和解释后果.我发布这个问题的主要原因是为了得到我的答案的缺点的反馈.我最终得到的反馈不在这里,而是来自programmers.stackexchange中的另一个问题.然后我回到这里并更新了我的答案. (4认同)
  • 特别是,它没有给出任何理由而被投票:( (3认同)
  • 在我看来,`@SuppressWarnings ("unchecked")` 编译器欺骗是完全不能接受的。 (2认同)
  • @ThorbjørnRavnAndersen和@Unihedron请注意,`@ SuppressWarning`仅限于一个util类,剩下的代码比不使用任何技巧更清晰,更易于维护/开发,你在哪里看到混乱/邪恶?甚至单元测试也更有效(请看下面的`LambdaExceptionUtilTest`).我们正试图在这里找到一个良好而干净的妥协.那么,你们有没有比MarcG + mine更好的解决方案,或者你只是说"在这些情况下不要使用lambda"?谢谢 (2认同)
  • @PaoloC 即使您只在一处违反规则,您仍然会违反它们。当上帝的话语是“不要那样做,而要编写包装器”时,我认为确保长期可维护性的最佳方法就是这样做。如果您的代码在被替换或丢弃之前不会存活很长时间,您可能会有不同的关注点,但我不能忽视这一点。 (2认同)
  • @ThorbjørnRavnAndersen 感谢您的回复,但我没有看到您的上帝;我认为 Oracle&amp;Community 可能会提供从 jdk 到 jdk 的更好或更差的解决方案,但这可以随着时间的推移而修复(来自世界各地的开发人员的想法和贡献......参见 Joda Time 或 Scala)。与此同时,我的代码必须是长期的,这就是为什么我更喜欢使用 Lambdas&amp;FunctionalInterfaces 来欺骗 1 个类并缩短/清理大量代码(保持相同的合同,其中包括抛出异常),而不是让我的团队负担维护大量的java7代码。 (2认同)
  • 此外,我们会这样做,直到Oracle开发一个等价物(我真的怀疑即将到来,这周围有太多线程); 然后,我们将内联`LambdaExceptionUtil`再次使用_native_ clean代码.不同之处在于中间:几年后,您将要么捕获 - 检查 - 并且重新抛出 - 运行时(这是一个噩梦,你知道)或只是解决简单的代码而不是抛出异常(不断增加你的技术)债务).我的2c. (2认同)
  • @ThorbjørnRavnAndersen我总是尊重伟大的开发人员作为Brian,但是他在下面的答案中提出的代码显然是不可接受的,以便彻底解决这个问题,这只是一个**解决方法来避免警告,肯定不是一个令人鼓舞的使用方式检查流内的异常.每次你需要抛出异常时,我敢于你编写这么多代码. (2认同)
  • @PaoloC:很抱歉我花时间来复习这个.我已相应更新了我的答案.我相信现在甚至Brian Goetz对"破坏类型系统"的抱怨也不再适用. (2认同)
  • 现在它确实起作用了,现在它起作用了,我明白它为什么起作用了。我个人对此不太满意,因为注 1 完全不正确;这不是普遍安全的,因为它要求您处理函数_created_ 处的异常,而不是_invoked_ 处的异常。当它们在同一个地方时,就像在这些示例中一样,这很好,但是没有什么能阻止开发人员将函数包装在重新抛出器中,在那里处理异常(它甚至永远不会被抛出),并传递危险的未经检查的 -但应该检查其他代码段的功能。 (2认同)
  • 传递问题在https://blog.codefx.org/java/repackaging-exceptions-streams/中解释. (2认同)

Bri*_*etz 25

你不能安全地做到这一点.你可以作弊,但是你的程序被打破了,这将不可避免地回来咬人(应该是你,但我们的作弊常常会打击别人.)

这是一个稍微安全的方法(但我仍然不建议这样做.)

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;
}
Run Code Online (Sandbox Code Playgroud)

在这里,您正在做的是捕获lambda中的异常,从流管道中抛出一个信号,指示计算异常失败,捕获信号,并对该信号进行操作以抛出基础异常.关键是你总是捕获合成异常,而不是在不声明抛出异常的情况下允许检查异常泄漏.

  • 就一个问题; 是什么设计决定导致lambdas无法将检查的异常传播出他们的背景?请注意,我确实理解像`Function`等功能接口不会"抛出"任何东西; 我只是好奇. (18认同)
  • @schatten一个原因是你可能忘记捕获WE,然后一个奇怪的异常(没有人知道如何处理)会漏掉.(你可能会说"但你抓住了异常,所以它是安全的."在这个玩具示例中.但每次我看到代码库采用这种方法时,最终会有人忘记.忽略异常的诱惑无所不知.)另一种风险安全使用它是特定于特定(使用站点,例外)组合.它不能很好地扩展到多个异常或非同类用途. (10认同)
  • 坦率地说,这个解决方案完全不可行.我认为溪流的目的是减少样板,而不是增加它. (9认同)
  • 那个`throw w.cause;`不会让编译器抱怨该方法不抛出也不会捕获`Throwable`?因此,可能需要在那里进行"IOException"的转换.此外,如果lambda抛出多种类型的已检查异常,则catch的主体会因某些`instanceof`检查(或具有类似目的的其他内容)而变得有些丑陋,以验证抛出哪个已检查异常. (4认同)
  • @hoodaticus我同意你的看法.说,你喜欢越来越多的包装(如上所示,增加"遗忘"的风险)或只是创建4个聪明的界面和使用lambdas w/o包装,如http://stackoverflow.com/a/所示二百三十六万五千七百二十四分之三千〇九十七万四千九百九十一?谢谢 (2认同)

Pao*_*loC 22

您可以!

扩展@marcg UtilExceptionthrow 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;
    }

}
Run Code Online (Sandbox Code Playgroud)

一些测试显示用法和行为:

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

}
Run Code Online (Sandbox Code Playgroud)

  • 非常好.唯一不幸的是,对于抛出多个已检查异常的方法,它无法正常工作.在这种情况下,编译器将使您捕获一个常见的超类型,例如`Exception`. (3认同)

Rob*_*žan 12

只需使用NoException(我的项目),jOOλ的Unchecked,throw -lambdas,Throwable接口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));
Run Code Online (Sandbox Code Playgroud)


Jef*_*rey 6

我编写了一个扩展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());
}
Run Code Online (Sandbox Code Playgroud)


小智 6

这个答案类似于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
        }
Run Code Online (Sandbox Code Playgroud)

  • 这正是 Op 不想要的:在 lambda 中尝试块。此外,只要 try 块之外没有其他代码将 IOException 包装在 RuntimeException 中,它就会按预期工作。为了避免这种情况,可以使用自定义包装器运行时异常(定义为私有内部类)。 (2认同)

小智 6

您可以使用 apache commons-lang3 库来做到这一点。

https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/function/Failable.html

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes =
            Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                    .map(Failable.asFunction(Class::forName))
                    .collect(Collectors.toList());
    return classes;
}
Run Code Online (Sandbox Code Playgroud)


fge*_*fge 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());
Run Code Online (Sandbox Code Playgroud)

并抓住MyException.

这是一个例子.另一个例子是你可以使用.orReturn()一些默认值.

请注意,这仍然是一项正在进行的工作,更多的是即将到来.更好的名字,更多功能等

  • 但是,如果你想抛出原始的检查异常,你将不得不在流周围添加try/catch来打开它,这仍然是可怕的!如果你愿意,我喜欢你可以抛出一个未经检查的异常的想法,并且如果你愿意,你可以向流返回一个默认值,但我也认为你应该在你的项目中添加一些`.orThrowChecked()`方法抛出已检查的异常本身.请查看此页面中的"UtilException"答案,看看您是否喜欢将第三种可能性添加到项目中. (2认同)

ser*_*gut 5

TL;DR 只需使用 Lombok 的@SneakyThrows.

Christian Hujer 已经详细解释了为什么从流中抛出受检异常,严格来说,由于 Java 的限制是不可能的。

其他一些答案已经解释了绕过语言限制的技巧,但仍然能够满足抛出“已检查异常本身,并且不会向流中添加丑陋的尝试/捕获”的要求,其中一些需要数十行额外的行样板。

我将强调这样做的另一个选项,恕我直言,它比所有其他选项都干净得多: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);
}
Run Code Online (Sandbox Code Playgroud)

我们只需要一个Extract Method重构(由IDE完成)和一个额外的线路@SneakyThrows。注释负责添加所有样板文件,以确保您可以抛出已检查的异常,而无需将其包装在 a 中RuntimeException,也无需显式声明它。

  • 不鼓励使用 lombok。 (4认同)