如何从 Java 8 流内部抛出 CHECKED 异常?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27644361/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I throw CHECKED exceptions from inside Java 8 streams?
提问by MarcG
How can I throw CHECKED exceptions from inside Java 8 streams/lambdas?
如何从 Java 8 流/lambda 内部抛出 CHECKED 异常?
In other words, I want to make code like this compile:
换句话说,我想让这样的代码编译:
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;
}
This code does not compile, since the Class.forName()
method above throws ClassNotFoundException
, which is checked.
这段代码不会编译,因为Class.forName()
上面的方法会抛出ClassNotFoundException
,这是被检查的。
Please note I do NOT want to wrap the checked exception inside a runtime exception and throw the wrapped unchecked exception instead. I want to throw the checked exception itself, and without adding ugly try
/catches
to the stream.
请注意,我不想将已检查的异常包装在运行时异常中,而是抛出包装的未检查异常。我想抛出已检查的异常本身,并且不向流添加丑陋的try
/ catches
。
采纳答案by MarcG
This LambdaExceptionUtil
helper class lets you use any checked exceptions in Java streams, like this:
这个LambdaExceptionUtil
帮助器类允许您在 Java 流中使用任何已检查的异常,如下所示:
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(rethrowFunction(Class::forName))
.collect(Collectors.toList());
Note Class::forName
throws ClassNotFoundException
, which is checked. The stream itself also throws ClassNotFoundException
, and NOT some wrapping unchecked exception.
注意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; }
}
Many other examples on how to use it (after statically importing LambdaExceptionUtil
):
关于如何使用它的许多其他示例(静态导入后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");
}
NOTE 1:The rethrow
methods of the LambdaExceptionUtil
class above may be used without fear, and are OK to use in any situation. A big thanks to user @PaoloC who helped solve the last problem: Now the compiler will ask you to add throw clauses and everything's as if you could throw checked exceptions natively on Java 8 streams.
注1:rethrow
上述LambdaExceptionUtil
类的方法可以放心使用,在任何情况下都可以使用。非常感谢用户@PaoloC,他帮助解决了最后一个问题:现在编译器会要求你添加 throw 子句,一切就好像你可以在 Java 8 流上本地抛出已检查的异常一样。
NOTE 2:The uncheck
methods of the LambdaExceptionUtil
class above are bonus methods, and may be safely removed them from the class if you don't want to use them. If you do used them, do it with care, and not before understanding the following use cases, advantages/disadvantages and limitations:
注意 2:uncheck
上述LambdaExceptionUtil
类的方法是额外的方法,如果您不想使用它们,可以安全地将它们从类中删除。如果您确实使用过它们,请谨慎使用,并且在了解以下用例、优点/缺点和限制之前不要这样做:
? You may use the uncheck
methods if you are calling a method which literally can never throw the exception that it declares. For example: new String(byteArr, "UTF-8") throws UnsupportedEncodingException, but UTF-8 is guaranteed by the Java spec to always be present. Here, the throws declaration is a nuisance and any solution to silence it with minimal boilerplate is welcome: String text = uncheck(() -> new String(byteArr, "UTF-8"));
? uncheck
如果您调用的方法实际上永远不会抛出它声明的异常,则可以使用这些方法。例如: new String(byteArr, "UTF-8") 抛出 UnsupportedEncodingException,但 Java 规范保证 UTF-8 始终存在。在这里, throws 声明是一个令人讨厌的东西,欢迎任何用最少的样板来使其静音的解决方案:String text = uncheck(() -> new String(byteArr, "UTF-8"));
? You may use the uncheck
methods if you are implementing a strict interface where you don't have the option for adding a throws declaration, and yet throwing an exception is entirely appropriate. Wrapping an exception just to gain the privilege of throwing it results in a stacktrace with spurious exceptions which contribute no information about what actually went wrong. A good example is Runnable.run(), which does not throw any checked exceptions.
? uncheck
如果您正在实现一个严格的接口,而您没有添加 throws 声明的选项,则可以使用这些方法,但抛出异常是完全合适的。包装异常只是为了获得抛出它的特权会导致堆栈跟踪带有虚假异常,这些异常不提供有关实际出错的信息。一个很好的例子是 Runnable.run(),它不会抛出任何已检查的异常。
? In any case, if you decide to use the uncheck
methods,
be aware of these 2 consequences of throwing CHECKED exceptions without a throws clause: 1) The calling-code won't be able to catch it by name (if you try, the compiler will say: Exception is never thrown in body of corresponding try statement). It will bubble and probably be caught in the main program loop by some "catch Exception" or "catch Throwable", which may be what you want anyway. 2) It violates the principle of least surprise: it will no longer be enough to catch RuntimeException
to be able to guarantee catching all possible exceptions. For this reason, I believe this should not be done in framework code, but only in business code that you completely control.
? 在任何情况下,如果您决定使用这些uncheck
方法,请注意在没有 throws 子句的情况下抛出 CHECKED 异常的这两个后果:1) 调用代码将无法按名称捕获它(如果您尝试,编译器会说:在相应的 try 语句的主体中永远不会抛出异常)。它会冒泡,并且可能会被一些“catch Exception”或“catch Throwable”捕获在主程序循环中,无论如何这可能是您想要的。2)它违反了最小惊奇原则:仅仅捕获RuntimeException
就不能保证捕获所有可能的异常。为此,我认为这不应该在框架代码中完成,而只能在您完全控制的业务代码中完成。
- References:
- http://www.philandstuff.com/2012/04/28/sneakily-throwing-checked-exceptions.html
- http://www.mail-archive.com/[email protected]/msg05984.html
- Project Lombok annotation: @SneakyThrows
- Brian Goetz opinion (against) here: How can I throw CHECKED exceptions from inside Java 8 streams?
- https://softwareengineering.stackexchange.com/questions/225931/workaround-for-java-checked-exceptions?newreg=ddf0dd15e8174af8ba52e091cf85688e*
- 参考:
- http://www.philandstuff.com/2012/04/28/sneakily-throwing-checked-exceptions.html
- http://www.mail-archive.com/[email protected]/msg05984.html
- 龙目岛项目注释:@SneakyThrows
- Brian Goetz 的意见(反对)在这里:How can I throw CHECKED exceptions from inside Java 8流?
- https://softwareengineering.stackexchange.com/questions/225931/workaround-for-java-checked-exceptions?newreg=ddf0dd15e8174af8ba52e091cf85688e*
回答by Brian Goetz
You can't do this safely. You can cheat, but then your program is broken and this will inevitably come back to bite someone (it should be you, but often our cheating blows up on someone else.)
你不能安全地做到这一点。你可以作弊,但是你的程序被破坏了,这将不可避免地反过来咬人(应该是你,但我们的作弊往往会在其他人身上爆发。)
Here's a slightly safer way to do it (but I still don't recommend this.)
这是一种稍微安全的方法(但我仍然不推荐这样做。)
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;
}
Here, what you're doing is catching the exception in the lambda, throwing a signal out of the stream pipeline that indicates that the computation failed exceptionally, catching the signal, and acting on that signal to throw the underlying exception. The key is that you are always catching the synthetic exception, rather than allowing a checked exception to leak out without declaring that exception is thrown.
在这里,您正在做的是捕获 lambda 中的异常,从流管道中抛出一个信号,指示计算异常失败,捕获信号,并根据该信号采取行动以抛出底层异常。关键是你总是在捕捉合成异常,而不是允许检查的异常泄漏出去而不声明异常被抛出。
回答by Christian Hujer
The simple answer to your question is: You can't, at least not directly.And it's not your fault. Oracle messed it up.They cling on the concept of checked exceptions, but inconsistently forgot to take care of checked exceptions when designing the functional interfaces, streams, lambda etc. That's all grist to the mill of experts like Robert C. Martin who call checked exceptions a failed experiment.
你的问题的简单答案是:你不能,至少不能直接。这不是你的错。甲骨文搞砸了。他们坚持受检异常的概念,但在设计函数式接口、流、lambda 等时始终没有考虑到受检异常。这对像 Robert C. Martin 这样的专家来说很重要,他们将受检异常称为失败的实验。
In my opinion, this is a huge bugin the APIand a minor bug in the language specification.
在我看来,这是API中的一个巨大错误和语言规范中的一个小错误。
The bug in the API is that it provides no facility for forwarding checked exceptions where this actually would make an awful lot of sense for functional programming. As I will demonstrate below, such a facility would've been easily possible.
API 中的错误在于它没有提供转发已检查异常的工具,而这实际上对函数式编程非常有意义。正如我将在下面演示的那样,这样的设施很容易实现。
The bug in the language specification is that it does not allow a type parameter to infer a list of types instead of a single type as long as the type parameter is only used in situations where a list of types is permissable (throws
clause).
语言规范中的错误在于它不允许类型参数推断类型列表而不是单个类型,只要类型参数仅用于允许类型列表的情况(throws
子句)。
Our expectation as Java programmers is that the following code should compile:
我们作为 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);
}
}
However, it gives:
但是,它给出了:
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
The way in which the functional interfaces are defined currently prevents the Compiler from forwarding the exception - there is no declaration which would tell Stream.map()
that if Function.apply() throws E
, Stream.map() throws E
as well.
其中功能接口的定义方式目前防止编译器转发例外-有没有宣言,告诉Stream.map()
,如果Function.apply() throws E
,Stream.map() throws E
以及。
What's missing is a declaration of a type parameter for passing through checked exceptions. The following code shows how such a pass-through type parameter actually could have been declared with the current syntax. Except for the special case in the marked line, which is a limit discussed below, this code compiles and behaves as expected.
缺少的是用于传递已检查异常的类型参数的声明。以下代码显示了如何使用当前语法实际声明此类传递类型参数。除了标记行中的特殊情况,这是下面讨论的限制,此代码编译并按预期运行。
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();
}
}
In the case of throwSomeMore
we would like to see IOException
being missed, but it actually misses Exception
.
在throwSomeMore
我们希望看到IOException
被错过的情况下,但实际上错过了Exception
。
This is not perfect because type inference seems to be looking for a single type, even in the case of exceptions. Because the type inference needs a single type, E
needs to resolve to a common super
of ClassNotFoundException
and IOException
, which is Exception
.
这并不完美,因为类型推断似乎是在寻找单一类型,即使在出现异常的情况下也是如此。因为类型推断需要一个单一的类型,E
需要决心共同super
的ClassNotFoundException
和IOException
,这是Exception
。
A tweak to the definition of type inference is needed so that the compiler would look for multiple types if the type parameter is used where a list of types is permissible (throws
clause). Then the exception type reported by the compiler would be as specific as the original throws
declaration of the checked exceptions of the referenced method, not a single catch-all super type.
需要对类型推断的定义进行调整,以便在允许类型列表(throws
子句)的情况下使用类型参数时,编译器将查找多种类型。然后编译器报告的异常类型将throws
与引用方法的已检查异常的原始声明一样具体,而不是单个包罗万象的超类型。
The bad news is that this means that Oracle messed it up. Certainly they won't break user-land code, but introducing exception type parameters to the existing functional interfaces would break compilation of all user-land code that uses these interfaces explicitly. They'll have to invent some new syntax sugar to fix this.
坏消息是,这意味着 Oracle 搞砸了。当然,它们不会破坏用户端代码,但是将异常类型参数引入现有的功能接口会破坏显式使用这些接口的所有用户端代码的编译。他们将不得不发明一些新的语法糖来解决这个问题。
The even worse news is that this topic was already discussed by Brian Goetz in 2010 https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java(new link: http://mail.openjdk.java.net/pipermail/lambda-dev/2010-June/001484.html) but I'm informed that this investigation ultimately did not pan out, and that there is no current work at Oracle that I know of to mitigate the interactions between checked exceptions and lambdas.
更糟糕的消息是,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 之间的交互。
回答by fge
You cannot.
你不能。
However, you may want to have a look at one of my projectswhich allows you to more easily manipulate such "throwing lambdas".
但是,您可能想看看我的一个项目,它可以让您更轻松地操作此类“抛出 lambda”。
In your case, you would be able to do that:
在您的情况下,您可以这样做:
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());
and catch MyException
.
并赶上MyException
。
That is one example. Another example is that you could .orReturn()
some default value.
这就是一个例子。另一个例子是你可以使用.orReturn()
一些默认值。
Note that this is STILL a work in progress, more is to come. Better names, more features etc.
请注意,这仍然是一项正在进行的工作,还有更多。更好的名字,更多的功能等。
回答by JohnnyO
I agree with the comments above, in using Stream.map you are limited to implementing Function which doesn't throw Exceptions.
我同意上面的评论,在使用 Stream.map 时,您只能实现不抛出异常的 Function。
You could however create your own FunctionalInterface that throws as below..
但是,您可以创建自己的 FunctionalInterface,如下所示。
@FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
void accept(T instance) throws X;
}
then implement it using Lambdas or references as shown below.
然后使用 Lambda 或引用实现它,如下所示。
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");
}
}
回答by PaoloC
You can!
你可以!
Extending @marcg 's UtilException
and adding throw E
where necessary: this way, the compiler will ask you to add throw clausesand everything's as if you could throw checked exceptions nativelyon java 8's streams.
扩展 @marcgUtilException
并throw E
在必要时添加:这样,编译器会要求您添加 throw 子句,一切就好像您可以在 Java 8 的流上本地抛出已检查的异常一样。
Instructions: just copy/paste LambdaExceptionUtil
in your IDE and then use it as shown in the below LambdaExceptionUtilTest
.
说明:只需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;
}
}
Some test to show usage and behaviour:
一些显示用法和行为的测试:
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());
}
}
回答by Jeffrey
I wrote a librarythat extends the Stream API to allow you to throw checked exceptions. It uses Brian Goetz's trick.
我编写了一个扩展 Stream API的库,允许您抛出已检查的异常。它使用了 Brian Goetz 的技巧。
Your code would become
你的代码会变成
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());
}
回答by Matt McHenry
The only built-in way of handling checked exceptions that can be thrown by a map
operation is to encapsulate them within a CompletableFuture
. (An Optional
is a simpler alternative if you don't need to preserve the exception.) These classes are intended to allow you to represent contingent operations in a functional way.
处理可以由map
操作抛出的已检查异常的唯一内置方法是将它们封装在CompletableFuture
. (Optional
如果您不需要保留异常,An是一种更简单的替代方法。)这些类旨在允许您以函数方式表示可能发生的操作。
A couple of non-trivial helper methods are required, but you can arrive at code that's relatively concise, while still making it apparent that your stream's result is contingent on the map
operation having completed successfully. Here's what it looks like:
需要几个重要的辅助方法,但您可以得到相对简洁的代码,同时仍然明显地表明您的流的结果取决于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; });
This produces the following output:
这会产生以下输出:
[class java.lang.Object, class java.lang.Number, class java.lang.Number]
The applyOrDie
method takes a Function
that throws an exception, and converts it into a Function
that returns an already-completed CompletableFuture
-- either completed normally with the original function's result, or completed exceptionally with the thrown exception.
该applyOrDie
方法采用Function
抛出异常的 a ,并将其转换为Function
返回已完成的 a CompletableFuture
- 要么正常完成原始函数的结果,要么异常完成抛出异常。
The second map
operation illustrates that you've now got a Stream<CompletableFuture<T>>
instead of just a Stream<T>
. CompletableFuture
takes care of only executing this operation if the upstream operation succeeded. The API makes this explict, but relatively painless.
第二个map
操作说明您现在获得了 ,Stream<CompletableFuture<T>>
而不仅仅是Stream<T>
。 CompletableFuture
如果上游操作成功,则只负责执行此操作。API 使这变得明确,但相对轻松。
Until you get to the collect
phase, that is. This is where we require a pretty significant helper method. We want to "lift" a normal collection operation (in this case, toList()
) "inside" the CompletableFuture
-- cfCollector()
lets us do that using a supplier
, accumulator
, combiner
, and finisher
that don't need to know anything at all about CompletableFuture
.
直到你到达那个collect
阶段,也就是说。这是我们需要一个非常重要的辅助方法的地方。我们要“抬”一个正常的集合操作(在这种情况下,toList()
)“内部” CompletableFuture
- cfCollector()
让我们做到这一点使用supplier
,accumulator
,combiner
,和finisher
那些不需要知道在所有的任何东西CompletableFuture
。
The helper methods can be found on GitHub in my MonadUtils
class, which is very much still a work in progress.
可以在我MonadUtils
班级的GitHub 上找到辅助方法,这仍然是一项正在进行的工作。
回答by introspected
Summarizing the comments above the advanced solution is to use a special wrapper for unchecked functions with builder like API which provides recovering, rethrowing and suppresing.
总结高级解决方案上面的评论是使用一个特殊的包装器来处理未检查的函数,像 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());
Code below demonstrates it for Consumer, Supplier and Function interfaces. It can be easly expanded. Some public keywords were removed for this example.
下面的代码为 Consumer、Supplier 和 Function 接口演示了它。它可以很容易地扩展。此示例中删除了一些公共关键字。
Class Tryis the endpoint for client code. Safe methods may have unique name for each function type. CheckedConsumer, CheckedSupplierand CheckedFunctionare checked analogs of lib functions which can be used independently of Try
类Try是客户端代码的端点。对于每个函数类型,安全方法可能具有唯一的名称。 CheckedConsumer、CheckedSupplier和CheckedFunction是 lib 函数的检查类比,可以独立于Try 使用
CheckedBuilderis the interface for handling exceptions in some checked function. orTryallows execute another same type function if previous was failed. handleprovides exception handling including exception type filtering. The order of handlers is important. Reduce methods unsafeand rethrowrethrows last exception in the execution chain. Reduce methods orElseand orElseGetreturn alternate value like Optional ones if all functions failed. Also there is method suppress. CheckedWrapperis the common implementation of CheckedBuilder.
CheckedBuilder是用于处理某些已检查函数中的异常的接口。如果前一个失败,orTry允许执行另一个相同类型的函数。handle提供异常处理,包括异常类型过滤。处理程序的顺序很重要。Reduce 方法不安全,rethrow重新抛出执行链中的最后一个异常。如果所有函数都失败,Reduce 方法orElse和orElseGet 会返回替代值,例如 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);
}
}
回答by Taras
I use this kind of wrapping exception:
我使用这种包装异常:
public class CheckedExceptionWrapper extends RuntimeException {
...
public <T extends Exception> CheckedExceptionWrapper rethrow() throws T {
throw (T) getCause();
}
}
It will require handling these exceptions statically:
它将需要静态处理这些异常:
void method() throws IOException, ServletException {
try {
list.stream().forEach(object -> {
...
throw new CheckedExceptionWrapper(e);
...
});
} catch (CheckedExceptionWrapper e){
e.<IOException>rethrow();
e.<ServletExcepion>rethrow();
}
}
Though exception will be anyway re-thrown during first rethrow()
call (oh, Java generics...), this way allows to get a strict statical definition of possible exceptions (requires to declare them in throws
). And no instanceof
or something is needed.
尽管在第一次rethrow()
调用期间无论如何都会重新抛出异常(哦,Java 泛型......),但这种方式允许获得可能异常的严格静态定义(需要在 中声明它们throws
)。不需要instanceof
或不需要什么。