当前位置: 首页 > 知识库问答 >
问题:

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

夏长卿
2023-03-14

如何从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/catches

共有2个答案

邵奇
2023-03-14

这个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:上面的LambdaExceptionUtil类的Rethrow方法可以放心使用,并且在任何情况下都可以使用。非常感谢用户@paoloc,他帮助解决了上一个问题:现在编译器将要求您添加抛出子句,所有的一切就好像您可以在Java 8流上原生抛出检查过的异常一样。

注意2:上面的LambdaExceptionUtil类的uncheck方法是额外的方法,如果您不想使用它们,可以将它们安全地从类中删除。如果您确实使用过它们,那么在了解以下用例、优点/缺点和限制之前不要使用它们:

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

•如果您正在实现一个严格接口,其中您没有添加抛出声明的选项,但抛出异常是完全合适的,那么您可以使用uncheck方法。包装一个异常只是为了获得抛出它的特权,会导致一个带有虚假异常的stacktrace,这些错误不会提供关于实际发生了什么的信息。一个很好的例子是runnable.run(),它不抛出任何检查异常。

•在任何情况下,如果您决定使用uncheck方法,请注意在不使用throws子句的情况下抛出已检查异常的以下两个后果:1)调用代码将无法按名称捕获它(如果您尝试,编译器将说:异常从不在相应try语句的正文中抛出)。它会冒泡,很可能被某个“catch exception”或“catch throwable”捕获到主程序循环中,这可能是您无论如何都想要的。2)违反了最小意外的原则:捕获runtimeexception将不再足以能够保证捕获所有可能的异常。出于这个原因,我相信这不应该在框架代码中进行,而应该只在您完全控制的业务代码中进行。

  • 引用:
    • http://www.philandstuff.com/2012/04/28/sneakily-sprowly-checked-exceptions.html
    • http://www.mail-archive.com/javaposse@googlegroups.com/msg05984.html
    • 龙目岛项目注释:@sneakythrows
    • Brian Goetz反对意见:如何从Java 8流内部抛出检查异常?
    • https://softwareengineering.stackexchange.com/questions/225931/workaround-for-Java-checked-exceptions?newreg=ddf0dd15e8174af8ba52e091cf85688e*

司马建柏
2023-03-14

对你的问题的简单回答是:你不能,至少不能直接。也不是你的错。甲骨文搞砸了。他们坚持检查异常的概念,但在设计函数接口、流、lambda等时却不一致地忘记了检查异常。这是像Robert C.Martin这样的专家们所说的“失败的实验”。

在我看来,这是API中的一个巨大bug和语言规范中的一个小bug。

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

缺少的是用于传递检查异常的类型参数的声明。下面的代码显示了这种传递类型参数实际上是如何使用当前语法声明的。除了标记行中的特殊情况(这是下面讨论的一个限制)外,此代码按照预期进行编译和行为。

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声明一样具体,而不是单一的catch-all超级类型。

坏消息是,这意味着甲骨文搞砸了。当然,它们不会破坏user-land代码,但是向现有的函数接口引入异常类型参数会破坏所有显式使用这些接口的user-land代码的编译。他们必须发明一些新的语法糖来解决这个问题。

更糟糕的消息是,Brian Goetz已经在2010https://blogs.Oracle.com/briangoetz/entry/exception_transparency_in_java(新链接:http://mail.openjdk.java.net/pipermail/lambda-dev/2010-june/001484.html)中讨论过这个话题,但我被告知,这个调查最终没有成功,而且据我所知Oracle目前没有任何工作可以减轻检查的异常和lambda之间的交互。

 类似资料:
  • 我试图在lambda内部抛出一个异常,但它总是给我一个错误,表示未处理的IOException。 有人能告诉我我做错了什么吗?

  • Java 8并行流在consuming子句中抛出异常时如何表现,例如在处理中?例如,以下代码: 它是否立即停止处理的元素?它是否等待已启动的元素完成?它是否等待所有的流完成?它是否在抛出异常后开始处理流元素? 什么时候回来?在异常之后立即?消费者处理完所有/部分元素后? 在并行流引发异常后,是否继续处理元素?(找到了发生这种情况的案例)。 这里有一般规则吗? 编辑(15-11-2016) 试图确定

  • 我怎样才能用Java8做到这一点呢?

  • 假设您有一个第三方库,它公开了下一个接口。 代码无法编译,因为Function不知道如何处理由domap声明的CheckedException。我想出了两个可能的解决方案。 解决方案#1-包装调用 解决方案#2-编写实用程序方法

  • 问题内容: 包含多个有关将检查的异常与混合使用的问题。 虽然一些答案暗示使用其方法会导致难以阅读的用户代码。 我将使用此空间来提供可提高可读性的替代解决方案。 请注意,此问题特定于CompletableFuture。 这使我们能够提供更广泛地不扩展到lambda表达式的解决方案。 问题答案: 给定实用程序类(下面提供),用户可以无缝地抛出检查异常: 由lambda引发的任何异常(是否经过检查)都将

  • 假设我想在收到特定异常时恢复某个值,否则返回失败的未来。我希望是这样的: 如果函数会抛出检查过的异常,我想在链式方法中处理它。我尝试过和,但都无法编译。是否为这种情况提供了任何解决方案?我知道接口是方法的参数,它不会抛出任何异常——在这种情况下,我只想返回已经失败的未来。我想找到使用Java8的解决方案。