抛出异常的Java 8 Lambda函数?

我知道如何创建一个具有String参数的方法的引用,并返回一个int,它是:

Function<String, Integer> 

但是,如果函数抛出一个异常,就不能工作,比如定义为:

 Integer myMethod(String s) throws IOException 

我将如何定义这个参考?

您需要执行以下任一操作。

  • 如果是你的代码,那么定义你自己的函数接口来声明检查的异常

     @FunctionalInterface public interface CheckedFunction<T, R> { R apply(T t) throws IOException; } 

    并使用它

     void foo (CheckedFunction f) { ... } 
  • 否则,将Integer myMethod(String s)包装在未声明检查的异常的方法中:

     public Integer myWrappedMethod(String s) { try { return myMethod(s); } catch(IOException e) { throw new UncheckedIOException(e); } } 

    接着

     Function<String, Integer> f = (String t) -> myWrappedMethod(t); 

    要么

     Function<String, Integer> f = (String t) -> { try { return myMethod(t); } catch(IOException e) { throw new UncheckedIOException(e); } }; 

实际上,您可以用一个处理异常的新接口来扩展Consumer (和Function等) – 使用Java 8的默认方法

考虑这个接口(扩展Consumer ):

 @FunctionalInterface public interface ThrowingConsumer<T> extends Consumer<T> { @Override default void accept(final T elem) { try { acceptThrows(elem); } catch (final Exception e) { // Implement your own exception handling logic here.. // For example: System.out.println("handling an exception..."); // Or ... throw new RuntimeException(e); } } void acceptThrows(T elem) throws Exception; } 

那么,例如,如果你有一个列表:

 final List<String> list = Arrays.asList("A", "B", "C"); 

如果你想用一些抛出异常的代码来使用它(比如forEach ),你通常会设置一个try / catch块:

 final Consumer<String> consumer = aps -> { try { // maybe some other code here... throw new Exception("asdas"); } catch (final Exception ex) { System.out.println("handling an exception..."); } }; list.forEach(consumer); 

但是用这个新的接口,你可以用lambda表达式来实例化它,编译器不会抱怨:

 final ThrowingConsumer<String> throwingConsumer = aps -> { // maybe some other code here... throw new Exception("asdas"); }; list.forEach(throwingConsumer); 

或者甚至只是把它简化一下!

 list.forEach((ThrowingConsumer<String>) aps -> { // maybe some other code here... throw new Exception("asda"); }); 

更新 :看起来像榴莲的一个非常好的实用程序库部分称为错误 ,可以用来解决这个问题有更多的灵活性。 例如,在我上面的实现中,我明确地定义了错误处理策略( System.out...throw RuntimeException ),而榴莲的错误允许您通过一大套实用程序方法实时应用策略。 感谢分享 ,@NedTwigg!

示例用法:

 list.forEach(Errors.rethrow().wrap(c -> somethingThatThrows(c))); 

我认为榴莲的Errors类结合了上述各种建议的许多优点。

  • 将一个抛出函数包装到一个标准的Java 8功能接口中。
  • 轻松指定各种处理错误的策略
  • 当包装一个返回值的方法时,指定一个默认值或重新抛出一个RuntimeException有一个重要的区别 。
  • 抛出 Java 8的功能接口版本
    • 类似于fge的答案
  • 用于抛出特定异常的标准接口
    • 这就解决了Zoltán的担忧

要将榴莲纳入您的项目,您可以:

  • 从jcenter或maven central拿到它在com.diffplug.durian:durian:3.3.0
  • 或者只是将两个小类粘贴到代码中: Throwing.javaErrors.java

这不是特定于Java 8.您正试图编译相当于:

 interface I { void m(); } class C implements I { public void m() throws Exception {} //can't compile } 

免责声明:我还没有使用Java 8,只读过它。

Function<String, Integer>不抛出IOException ,所以你不能放任何throws IOException代码。 如果你正在调用一个期望Function<String, Integer> ,那么传递给该方法的lambda不能抛出IOException ,句点。 你可以写这样的lambda(我认为这是lambda语法,不知道):

 (String s) -> { try { return myMethod(s); } catch (IOException ex) { throw new RuntimeException(ex); // (Or do something else with it...) } } 

或者,如果传递lambda的方法是自己写的,那么可以定义一个新的函数接口,并将其用作参数类型而不是Function<String, Integer>

 public interface FunctionThatThrowsIOException<I, O> { O apply(I input) throws IOException; } 

你可以使用unthrow包装

 Function<String, Integer> func1 = s -> Unthrow.wrap(() -> myMethod(s)); 

要么

 Function<String, Integer> func2 = s1 -> Unthrow.wrap((s2) -> myMethod(s2), s1); 

如果你不介意使用第三方库( Vavr ),你可以写

 CheckedFunction1<String, Integer> f = this::myMethod; 

它也有所谓的Try monad,它处理错误:

 Try(() -> f.apply("test")) // results in a Success(Integer) or Failure(Throwable) .map(i -> ...) // only executed on Success ... 

请在这里阅读更多。

免责声明:我是Vavr的创造者。

另一个使用函数包装器的解决方案是返回结果包装的一个实例,如成功,如果一切顺利的话,或者是失败的一个实例。

一些代码来澄清事情:

 public interface ThrowableFunction<A, B> { B apply(A a) throws Exception; } public abstract class Try<A> { public static boolean isSuccess(Try tryy) { return tryy instanceof Success; } public static <A, B> Function<A, Try<B>> tryOf(ThrowableFunction<A, B> function) { return a -> { try { B result = function.apply(a); return new Success<B>(result); } catch (Exception e) { return new Failure<>(e); } }; } public abstract boolean isSuccess(); public boolean isError() { return !isSuccess(); } public abstract A getResult(); public abstract Exception getError(); } public class Success<A> extends Try<A> { private final A result; public Success(A result) { this.result = result; } @Override public boolean isSuccess() { return true; } @Override public A getResult() { return result; } @Override public Exception getError() { return new UnsupportedOperationException(); } @Override public boolean equals(Object that) { if(!(that instanceof Success)) { return false; } return Objects.equal(result, ((Success) that).getResult()); } } public class Failure<A> extends Try<A> { private final Exception exception; public Failure(Exception exception) { this.exception = exception; } @Override public boolean isSuccess() { return false; } @Override public A getResult() { throw new UnsupportedOperationException(); } @Override public Exception getError() { return exception; } } 

一个简单的用例:

 List<Try<Integer>> result = Lists.newArrayList(1, 2, 3).stream(). map(Try.<Integer, Integer>tryOf(i -> someMethodThrowingAnException(i))). collect(Collectors.toList()); 

这个问题一直困扰着我; 这就是为什么我创建了这个项目 。

有了它,你可以做到:

 final ThrowingFunction<String, Integer> f = yourMethodReferenceHere; 

JDK定义了39个接口,这些接口具有这样的Throwing等价物; 这些都是在流中使用的@FunctionalInterface (基本Stream ,还有IntStreamLongStreamDoubleStream )。

而且,由于每一个扩展它们的非抛出对应部分,你也可以直接在lambdas中使用它们:

 myStringStream.map(f) // <-- works 

默认行为是,当抛出的lambda抛出一个检查的异常时,引发一个ThrownByLambdaException异常作为原因。 因此,您可以捕捉并获得原因。

其他功能也可以使用。

我有一个lambda的Class.forName和Class.newInstance这个问题,所以我只是:

 public Object uncheckedNewInstanceForName (String name) { try { return Class.forName(name).newInstance(); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } 

在lambda内部,而不是调用Class.forName(“myClass”)。newInstance()我只是调用uncheckedNewInstanceForName(“myClass”)

你可以创建自己的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"); } } 

您可以。

如果需要,扩展@marcg的UtilException并添加泛型<E extends Exception> :这样编译器会再次强制你添加throw子句和所有东西,就好像你可以在java 8的流UtilException抛出checked异常。

 public final class LambdaExceptionUtil { @FunctionalInterface public interface Function_WithExceptions<T, R, E extends Exception> { R apply(T t) throws E; } /** * .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 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(); } private static class MyTestException extends Exception { } } 

你可以用ET做这个。 ET是一个用于异常转换/转换的小型Java 8库。

用ET看起来像这样:

 // Do this once ExceptionTranslator et = ET.newConfiguration().done(); ... // if your method returns something Function<String, Integer> f = (t) -> et.withReturningTranslation(() -> myMethod(t)); // if your method returns nothing Consumer<String> c = (t) -> et.withTranslation(() -> myMethod(t)); 

ExceptionTranslator实例是线程安全的,可以由多个组件共享。 你可以配置更具体的异常转换规则(如FooCheckedException -> BarRuntimeException ),如果你喜欢。 如果没有其他规则可用,则检查的异常会自动转换为RuntimeException

(免责声明:我是ET的作者)

创建将传播检查的异常的自定义返回类型。 这是创建一个新接口的替代方法,该接口反映了现有功能接口,并在功能接口的方法上稍微修改了“抛出异常”。

定义

CheckedValueSupplier

 public static interface CheckedValueSupplier<V> { public V get () throws Exception; } 

为CheckedValue

 public class CheckedValue<V> { private final V v; private final Optional<Exception> opt; public Value (V v) { this.v = v; } public Value (Exception e) { this.opt = Optional.of(e); } public V get () throws Exception { if (opt.isPresent()) { throw opt.get(); } return v; } public Optional<Exception> getException () { return opt; } public static <T> CheckedValue<T> returns (T t) { return new CheckedValue<T>(t); } public static <T> CheckedValue<T> rethrows (Exception e) { return new CheckedValue<T>(e); } public static <V> CheckedValue<V> from (CheckedValueSupplier<V> sup) { try { return CheckedValue.returns(sup.get()); } catch (Exception e) { return Result.rethrows(e); } } public static <V> CheckedValue<V> escalates (CheckedValueSupplier<V> sup) { try { return CheckedValue.returns(sup.get()); } catch (Exception e) { throw new RuntimeException(e); } } } 

用法

 // Don't use this pattern with FileReader, it's meant to be an // example. FileReader is a Closeable resource and as such should // be managed in a try-with-resources block or in another safe // manner that will make sure it is closed properly. // This will not compile as the FileReader constructor throws // an IOException. Function<String, FileReader> sToFr = (fn) -> new FileReader(Paths.get(fn).toFile()); // Alternative, this will compile. Function<String, CheckedValue<FileReader>> sToFr = (fn) -> { return CheckedValue.from ( () -> new FileReader(Paths.get("/home/" + f).toFile())); }; // Single record usage // The call to get() will propagate the checked exception if it exists. FileReader readMe = pToFr.apply("/home/README").get(); // List of records usage List<String> paths = ...; //a list of paths to files Collection<CheckedValue<FileReader>> frs = paths.stream().map(pToFr).collect(Collectors.toList()); // Find out if creation of a file reader failed. boolean anyErrors = frs.stream() .filter(f -> f.getException().isPresent()) .findAny().isPresent(); 

这是怎么回事?

在JDK的每个功能接口中添加“抛出异常”将会以非常糟糕的方式违反DRY原则。 为了避免这种情况,创建一个引发检查异常的函数接口( CheckedValueSupplier )。 这将是唯一允许检查异常的功能接口。 所有其他功能接口将利用CheckedValueSupplier来包装引发检查异常的任何代码。

CheckedValue类将保存执行任何引发检查异常的逻辑的结果。 这样可以防止检查异常的传播,直到代码尝试访问CheckedValue实例包含的值时为止。

这种方法的问题。

  • 现在我们正在抛出“异常”,有效地隐藏了最初抛出的特定类型。
  • 我们不知道直到调用CheckedValue#get()时才发生异常。

消费者等

一些功能接口(例如Consumer )必须以不同的方式处理,因为它们不提供返回值。

代替消费者的功能

一种方法是使用函数而不是消费者,这在处理流时适用。

  List<String> lst = Lists.newArrayList(); // won't compile lst.stream().forEach(e -> throwyMethod(e)); // compiles lst.stream() .map(e -> CheckedValueSupplier.from( () -> {throwyMethod(e); return e;})) .filter(v -> v.getException().isPresent()); //this example may not actually run due to lazy stream behavior 

升级

或者,您总是可以升级到RuntimeException 。 还有其他答案涵盖了Consumer内部检查异常的升级。

不要消耗。

只要避免一起使用功能性接口,并使用良好的回路。

这里已经发布了很多很棒的回复。 只是试图以不同的角度来解决问题。 它只是我的2美分,请纠正我,如果我在哪里错了。

FunctionalInterface中的抛出子句不是一个好主意

我认为这可能不是一个强制执行抛出IOException,因为以下原因

  • 这看起来像Stream / Lambda的反模式。 整个想法是调用者将决定提供什么代码以及如何处理异常。 在许多情况下,IOException可能不适用于客户端。 例如,如果客户端从缓存/内存获取值,而不是执行实际的I / O。

  • 另外,流中的异常处理变得非常可怕。 例如,这里是我的代码将看起来像如果我使用您的API

      acceptMyMethod(s -> { try { Integer i = doSomeOperation(s); return i; } catch (IOException e) { // try catch block because of throws clause // in functional method, even though doSomeOperation // might not be throwing any exception at all. e.printStackTrace(); } return null; }); 

    丑陋不是吗? 而且,正如我在第一点中提到的那样,doSomeOperation方法可能会或可能不会抛出IOException(取决于客户端/调用者的实现),但是由于FunctionalInterface方法中的throws子句,我总是必须写试着抓。

如果我真的知道这个API抛出IOException,我该怎么办?

  • 那么可能我们将FunctionalInterface与典型的接口混淆了。 如果你知道这个API会抛出IOException,那么你很可能也会知道一些默认/抽象的行为。 我认为你应该定义一个接口并部署你的库(默认/抽象实现)如下

     public interface MyAmazingAPI { Integer myMethod(String s) throws IOException; } 

    但是,客户端仍然存在try-catch问题。 如果我在流中使用你的API,我仍然需要在可怕的try-catch块中处理IOException。

  • 提供一个默认的流友好的API如下

     public interface MyAmazingAPI { Integer myMethod(String s) throws IOException; default Optional<Integer> myMethod(String s, Consumer<? super Exception> exceptionConsumer) { try { return Optional.ofNullable(this.myMethod(s)); } catch (Exception e) { if (exceptionConsumer != null) { exceptionConsumer.accept(e); } else { e.printStackTrace(); } } return Optional.empty(); } } 

    默认方法将消费者对象作为参数,它将负责处理异常。 现在,从客户的角度来看,代码将如下所示

     strStream.map(str -> amazingAPIs.myMethod(str, Exception::printStackTrace)) .filter(Optional::isPresent) .map(Optional::get).collect(toList()); 

    好吧? 当然,可以使用记录器或其他处理逻辑来代替Exception :: printStackTrace。

  • 您也可以公开一个类似于https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#exceptionally-java.util.function.Function-的方法。; 这意味着你可以暴露另一个方法,它将包含以前方法调用的异常。 缺点是你现在使你的API成为有状态的,这意味着你需要处理线程安全,并且最终会成为一个性能问题。 只是一个选项,但考虑。

如果你不介意使用第三方库,用独眼巨人的反应 ,我贡献的图书馆,你可以使用FluentFunctions API写

  Function<String, Integer> standardFn = FluentFunctions.ofChecked(this::myMethod); 

ofChecked需要JOOλCheckedFunction,并将引用软化为标准(未经检查的)JDK java.util.function.Function。

或者,您可以通过FluentFunctions api继续使用捕获的功能!

例如,执行你的方法,重试5次,并记录它的状态,你可以写

  FluentFunctions.ofChecked(this::myMethod) .log(s->log.debug(s),e->log.error(e,e.getMessage()) .try(5,1000) .apply("my param"); 

默认情况下,Java 8的函数不允许抛出异常,正如在多个答案中提出的有很多方法来实现它,一种方法是:

 @FunctionalInterface public interface FunctionWithException<T, R, E extends Exception> { R apply(T t) throws E; } 

定义为:

 private FunctionWithException<String, Integer, IOException> myMethod = (str) -> { if ("abc".equals(str)) { throw new IOException(); } return 1; }; 

并在调用方法中添加throwstry/catch相同的异常。

偷偷摸摸的成语可以绕过Lambda表达式的CheckedException。 在RuntimeException中包装CheckedException对于严格的错误处理来说是不好的。

它可以用作Java集合中使用的Consumer函数。

这里是一个简单的,改进的臂架答案的版本。

 import static Throwing.rethrow; @Test public void testRethrow() { thrown.expect(IOException.class); thrown.expectMessage("i=3"); Arrays.asList(1, 2, 3).forEach(rethrow(e -> { int i = e.intValue(); if (i == 3) { throw new IOException("i=" + i); } })); } 

只是重新包装lambda。 这会导致在lambda中发生CheckedException。

 public final class Throwing { private Throwing() {} @Nonnull public static <T> Consumer<T> rethrow(@Nonnull final ThrowingConsumer<T> consumer) { return consumer; } /** * The compiler sees the signature with the throws T inferred to a RuntimeException type, so it * allows the unchecked exception to propagate. * * http://www.baeldung.com/java-sneaky-throws */ @SuppressWarnings("unchecked") @Nonnull public static <E extends Throwable> void sneakyThrow(@Nonnull Throwable ex) throws E { throw (E) ex; } } 

https://gist.github.com/myui/9722c1301434a3b69cf898ccd9090ff1#file-throwingtest-java-L40查找完整的代码和单元测试;

我正在做的是让用户在异常的情况下给他想要的价值。 所以我看起来像这样

 public static <T, R> Function<? super T, ? extends R> defaultIfThrows(FunctionThatThrows<? super T, ? extends R> delegate, R defaultValue) { return x -> { try { return delegate.apply(x); } catch (Throwable throwable) { return defaultValue; } }; } @FunctionalInterface public interface FunctionThatThrows<T, R> { R apply(T t) throws Throwable; } 

然后可以这样调用:

 defaultIfThrows(child -> child.getID(), null) 

一些提供的解决方案使用E的泛型参数来传入抛出的异常的类型。

更进一步,而不是传入异常的类型,传入一个消费者的例外类型,如…

 Consumer<E extends Exception> 

您可以创建几个可重用的Consumer<Exception>变体,它将涵盖您的应用程序的常见异常处理需求。

我会做一些通用的:

 public interface Lambda { @FunctionalInterface public interface CheckedFunction<T> { T get() throws Exception; } public static <T> T handle(CheckedFunction<T> supplier) { try { return supplier.get(); } catch (Exception exception) { throw new RuntimeException(exception); } } } 

用法:

  Lambda.handle(() -> method()); 
 public void frankTest() { int pageId= -1; List<Book> users= null; try { //Does Not Compile: Object page=DatabaseConnection.getSpringConnection().queryForObject("SELECT * FROM bookmark_page", (rw, n) -> new Portal(rw.getInt("id"), "", users.parallelStream().filter(uu -> uu.getVbid() == rw.getString("user_id")).findFirst().get(), rw.getString("name"))); //Compiles: Object page= DatabaseConnection.getSpringConnection().queryForObject("SELECT * FROM bookmark_page", (rw, n) -> { try { final Book bk= users.stream().filter(bp -> { String name= null; try { name = rw.getString("name"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return bp.getTitle().equals(name); }).limit(1).collect(Collectors.toList()).get(0); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return new Portal(rw.getInt("id"), "", users.get(0), rw.getString("name")); } ); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }