如何从Java 8流中引发CHECKED异常?


286

如何从Java 8流/ lambda中抛出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年

34
@Brian,我不需要其他人告诉我如何作弊,我知道如何欺骗自己,并在下面的答案中张贴了我的作弊方式,您对此表示反对。我知道您参与了Java讨论,该讨论决定了没有一种好的方法来处理Streams中的检查异常,因此,您发现我的这个问题感到非常惊奇,但是您的回答却让我感到失望,它的回答是:“这是不好”,则没有给出原因,然后再次尝试添加/尝试添加。
MarcG 2014年

21
@Brian,坦率地说,在实践中,当人们尝试重构遗留的语句时,其中一半被转换为流,而另一半则放弃重构,因为没人愿意添加这些try / catch。它们使代码更难阅读,当然比原始的for语句还要难。在上面的代码示例中,只要您保持“ throws ClassNotFoundException”,我就看不到外部代码有什么区别。您能否给我一些现实生活中的例子,这些例子违反了有关例外的规定?
MarcG 2014年

10
编写包装为未检查的异常的包装器方法可解决“代码混乱”的异议,并且不会破坏类型系统。这里求助于被检查异常的“偷偷摸摸”的答案确实破坏了类型系统,因为调用代码将不会(也不允许捕获)被检查异常。
Brian Goetz 2014年

14
它不能解决代码混乱的异议,因为这样一来,您需要在流中进行第二次try / catch,以解开并重新抛出原始异常。相反,如果引发了检查的异常,则只需要在throws ClassNotFoundException包含流的方法声明中保留,以便调用代码可以期望并允许捕获检查的异常。
MarcG 2014年

Answers:


250

您问题的简单答案是:您不能,至少不能直接这样做。这不是你的错。甲骨文搞砸了。他们坚持使用检查异常的概念,但是在设计功能接口,流,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() 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需要决心共同superClassNotFoundExceptionIOException,这是Exception

需要对类型推断的定义进行一些调整,以便在允许使用类型列表的地方使用type参数时,编译器将查找多个类型(throws子句)的。然后,编译器报告的异常类型将与原始异常一样具体。throws与所引用方法的已检查异常声明,而不是单个“包罗万象”超级类型。

坏消息是,这意味着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目前有什么工作可以减轻检查的异常与lambda之间的相互作用。


16
有趣。我相信有些人喜欢使用流来简化并行代码,而另一些人则喜欢使用更简洁的代码。Brian Goetz显然更在意并行性(因为他在实践中编写了Java Concurrency),而Robert Martin在乎更干净的代码(因为他编写了Clean Code本书)。Boilerplate的try / catches是为并行性付出的一个小代价,因此,难怪Brian Goetz不会对在流中使用检查异常的问题感到震惊。同样也难怪罗伯特·马丁(Robert Martin)讨厌检查异常,因为它们增加了混乱。
MarcG 2014年

5
我预测,在几年内,流中处理受检查的异常的困难将导致以下两种结果之一:人们将停止使用受检查的异常,或者每个人都将开始使用某些黑客程序,就像我在其中发布的一样。我的UtilException答案。我敢打赌Java-8流是检查异常的棺材上的最后钉子,不是因为检查异常是JDK的一部分。尽管我喜欢并在业务代码中使用检查过的异常(对于某些特定的用例),但我还是希望所有常见的JDK异常扩展运行时。
MarcG 2014年

9
@Unihedro问题仍然在于功能接口不转发异常。我需要在lambda 内放置try-catch块,而这根本没有任何意义。一旦在lambda中以某种方式使用了(例如在中),问题就出现了。基本上,抛出(抛出“异常!”设计)将抛出检查异常的方法直接作为功能接口参与了功能编程。Class.forNamenames.forEach(Class::forName)
Christian Hujer 2014年

26
@ChristianHujer“异常透明性”探索就是这样-一项探索(起源于BGGA提案)。经过更深入的分析,我们发现它无法在价值和复杂性之间找到平衡,并且它存在一些严重的问题(导致无法确定的推理问题,以及“ catch X”不健全的问题。)语言思想非常普遍。似乎很有希望-甚至是“显而易见的”-但经过更深入的探索,结果发现它是有缺陷的。这就是其中一种情况。
Brian Goetz

13
@BrianGoetz是否有一些有关您提到的不确定的推理问题的公共信息?我很好奇,想了解它。
Christian Hujer

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:可以使用上述类的rethrow方法LambdaExceptionUtil而不必担心,在任何情况下可以使用。非常感谢帮助解决最后一个问题的用户@PaoloC:现在,编译器将要求您添加throw子句,一切都好像您可以在Java 8流中本机抛出检查异常一样。


注意2:uncheck上述LambdaExceptionUtil类的方法是奖励方法,如果您不想使用它们,可以将其安全地从类中删除。如果您确实使用过它们,请谨慎操作,而不要先了解以下用例,优点/缺点和限制:

•您可以使用 uncheck如果要调用一个从字面上永远不会引发其声明的异常方法,则方法。例如:new String(byteArr,“ UTF-8”)引发UnsupportedEncodingException,但是Java规范保证UTF-8始终存在。在这里,throws声明是个麻烦事,欢迎使用最小的样板来使它静音的任何解决方案:String text = uncheck(() -> new String(byteArr, "UTF-8"));

uncheck如果要实现一个严格的接口,而该接口没有添加throws声明的选项,但是抛出异常是完全适当的,则可以使用这些方法。包装异常只是为了获得抛出异常的特权,会导致产生带有虚假异常的堆栈跟踪,这些异常不会提供有关实际出了什么问题的信息。一个很好的例子是Runnable.run(),它不会引发任何检查的异常。

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


4
我觉得这个答案是不公正的。该代码有效。被检查的异常应该被抛出或处理。如果要抛出它们,只需在包含流的方法中保留“ throws子句”。但是,如果您想通过简单地包装和重新抛出来处理它们,我想我更喜欢使用上面的代码来“解开”异常并使它们自己冒泡。我知道的唯一区别是冒泡异常不会扩展RuntimeException。我知道纯粹主义者不会那样,但这会“不可避免地回来咬人”吗?似乎不太可能。
MarcG 2014年

4
@Christian Hujer,坦白地说,在我添加“优点,缺点和限制”说明之前,他对以前的版本进行了投票。因此,也许当时应得的。您不能教别人如何违反规则,而至少要设法理解和解释后果。我发布此问题的主要原因是为了获得针对我的回答缺点的反馈。我最终没有在这里得到这个反馈,而是从programmers.stackexchange中的另一个问题得到的。然后我回到这里并更新了答案。
MarcG 2014年

16
我之所以投票,是因为这会鼓励代码无法维护。这是一个丑陋的技巧,尽管很聪明,但我永远也找不到这个答案。同样,这是该语言的另一种“不使用”。
Unihedron 2014年

12
@Unihedro但是为什么它变得难以维护?我不明白为什么。有什么例子吗?
MarcG 2014年

2
在我看来,@SuppressWarnings ("unchecked")编译器的欺骗是完全不能接受的。
托尔比约恩Ravn的安徒生

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

在这里,您正在做的是在lambda中捕获异常,从流管道中抛出一个信号,该信号指示计算异常,捕获该信号,并对该信号进行操作以引发基础异常。关键是您始终要捕获综合异常,而不是在不声明引发异常的情况下允许检查的异常泄漏出去。


18
就一个问题; 是什么设计决定导致lambda无法将受检查的异常传播到其上下文之外?请注意,我的确了解诸如Functionetc 之类的功能接口没有throws任何作用。我只是好奇。
fge

4
throw w.cause;不会让编译器抱怨该方法不抛出也不捕获Throwable吗?因此,很可能IOException在那里需要强制转换为。此外,如果lambda抛出了多种类型的已检查异常,则通过某些instanceof检查(或其他目的类似的验证)来验证抛出了哪个已检查异常,捕获的主体将变得有些丑陋。
维克多·斯塔夫萨

10
@schatten一个原因是您可能会忘记捕获WE,然后一个奇怪的异常(没人知道如何处理)会泄漏出去。(您可能会说“但是您捕获了异常,因此很安全。”在这个玩具示例中。但是,每当我看到一个代码库采用这种方法时,最终都会有人忘记。忽略异常的诱惑是无止境的。)另一个风险安全使用特定于特定的(使用网站,例外)组合。它不能很好地扩展到多个异常或非本地使用。
Brian Goetz

2
@hoodaticus我同意你的看法。就是说,您是喜欢越来越多地包装(如上所示,增加“遗忘”的风险)还是仅创建4个巧妙的接口并使用不带包装的lambda,如stackoverflow.com/a/30974991/2365724所示?谢谢
PaoloC

10
坦白说,这种解决方案是完全不可行的。我认为流的目的是减少样板,而不是增加样板。
wvdz

24

您可以!

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

}

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

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

1
解决上述问题的方法:声明类似Consumer <ThingType> expression = rethrowConsumer(((ThingType ThingType)-> something.clone()))的变量;然后在内部foreach中使用该表达式。
Skychan

1
@Skychan:由于在此修改后的新版本中,您不再抑制任何异常,因此推理系统可能会更加困难。布莱恩·格茨(Brian Goetz)在下面的一些评论中谈到了导致“无法确定的推理问题”的“异常透明性”。
MarcG

3
非常好。唯一不幸的是,它不能与引发多个已检查异常的方法完美配合。在这种情况下,编译器会让您捕获一个通用的超类型,例如Exception
wvdz

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

我编写了一个库该库扩展了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
        }

1
这是一个简单有效的解决方案。
林W

2
这正是Op所不想要的:尝试使用lambda中的块。此外,只要try块之外的其他代码都没有在RuntimeException中包装IOException,它就只能按预期工作。为了避免这种情况,可以使用自定义wrapper-RuntimeException(定义为私有内部类)。
马尔特·哈特维格

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 2014年

“但是,如果要抛出原始的已检查异常,则必须在流中添加try / catch来解开它,这仍然很糟糕!” <-是的,但是您别无选择。Lambda 无法从其上下文中传播已检查的异常,这是设计的“决定”(我个人认为这是一个缺陷,但还是可以)
fge 2014年

关于你的想法,对不起,我不太了解。毕竟,您仍然会不受限制地抛出错误,那么这与我的操作有何不同?(除了我有一个不同的界面)
fge 2014年

无论如何,欢迎您为该项目做出贡献!另外,您是否注意到该Stream工具AutoCloseable
fge

让我问您一个问题:您的MyException上述内容是否需要成为未经检查的例外?
MarcG 2014年

3

总结上述高级解决方案中的注释,是对带有API之类构建器的未经检查的函数使用特殊的包装,该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类是客户端代码的端点。对于每种函数类型,安全方法可能具有唯一的名称。 CheckedConsumerCheckedSupplierCheckedFunction是lib函数的已检查类似物,可以独立于Try使用

CheckedBuilder是用于处理某些选中函数中的异常的接口。如果上一个失败,则orTry允许执行另一个相同类型的函数。handle提供异常处理,包括异常类型过滤。处理程序的顺序很重要。减少方法不安全,然后重新抛出,重新抛出执行链中的最后一个异常。如果所有函数均失败,则Reduce方法的orElseorElseGet返回替代值,例如Optional。也有方法抑制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 / catching”的要求。的要求,其中一些还需要几十行样板。

我要强调的另一种选择是恕我直言,它比其他所有方法都要干净:龙目岛 @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);
}

我们只需要一个Extract Method重构(由IDE完成)和的另一@SneakyThrows。注释负责添加所有样板文件,以确保您可以抛出已检查的异常而无需将其包装在a中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时,您仅限于实现不抛出异常的Function。

但是,您可以创建自己的FunctionalInterface,其抛出如下。

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

然后使用Lambdas或引用实现它,如下所示。

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如果不需要保留异常,则是一种更简单的选择。)这些类旨在允许您以功能性方式表示或有操作。

需要几个非平凡的辅助方法,但是您可以得到相对简洁的代码,同时仍然可以清楚地看到流的结果取决于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-要么以原始函数的结果正常完成,要么以抛出的异常完成。

第二个map操作说明您现在有了一个Stream<CompletableFuture<T>>而不是一个Stream<T>CompletableFuture仅在上游操作成功的情况下执行此操作。API使这一过程变得清晰,但相对而言却很轻松。

在进入collect阶段之前,就是这样。这是我们需要非常重要的辅助方法的地方。我们要“取消”正常的收款操作(在本例中为toList())“ CompletableFuture-” cfCollector()让我们做到这一点使用supplieraccumulatorcombiner,和finisher那些不需要知道在所有的任何东西CompletableFuture

可以在GitHub中找到帮助方法 MonadUtils类的,但仍在进行中。


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

第二种实现方式避免将异常包装在中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(这是此异常的用例),然后外部。

是的,我觉得这很丑陋,并且我建议不要在这种情况下使用lambda,而应退回到一个良好的旧循环,除非您使用并行流并且并行化带来了客观的好处,这证明了代码的不可读取性。

正如许多其他人指出的那样,有针对这种情况的解决方案,我希望其中一种将使它成为Java的未来版本。


1
(1)已经有几个答案显示了这样的示例,那么您的答案在尚未涵盖的问答中又增加了什么?这样发布重复的答案只会使网站混乱。(2)OP明确表示,他们希望这样做。“请注意,我不想将已检查的异常包装在运行时异常中,而是抛出已包装的未检查的异常。”
Radiodef
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.