抛出异常的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.java
和Errors.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
,还有IntStream
, LongStream
和DoubleStream
)。
而且,由于每一个扩展它们的非抛出对应部分,你也可以直接在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; };
并在调用方法中添加throws
或try/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(); } }