Mar*_*rcG 270 java lambda checked-exceptions java-8 java-stream
如何从Java 8流/ lambdas中抛出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;
}
Run Code Online (Sandbox Code Playgroud)
此代码无法编译,因为Class.forName()
上面的方法抛出ClassNotFoundException
,检查.
请注意我不希望将已检查的异常包装在运行时异常中,而是抛出包装的未经检查的异常.我想抛出已检查的异常本身,而不是向流添加丑陋的try
/ catches
.
Chr*_*jer 237
你的问题的简单答案是:你不能,至少不是直接的.这不是你的错.甲骨文搞砸了.他们坚持检查异常的概念,但在设计功能接口,流,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);
}
}
Run Code Online (Sandbox Code Playgroud)
但是,它给出了:
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
Run Code Online (Sandbox Code Playgroud)
其中功能接口当前定义的方式防止编译器转发例外-有没有宣言,告诉Stream.map()
,如果Function.apply() throws E
,Stream.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();
}
}
Run Code Online (Sandbox Code Playgroud)
在throwSomeMore
我们希望IOException
被遗漏的情况下,但实际上错过了Exception
.
这并不完美,因为类型推断似乎在寻找单一类型,即使在异常情况下也是如此.因为类型推断需要一个单一类型的,E
需要解决一个共同super
的ClassNotFoundException
和IOException
,这是Exception
.
需要对类型推断的定义进行调整,以便如果在允许类型列表(throws
子句)的情况下使用类型参数,编译器将查找多个类型.然后,编译器报告的异常类型将throws
与引用方法的已检查异常的原始声明一样具体,而不是单个catch-all超类型.
坏消息是,这意味着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在做什么.
Mar*_*rcG 167
这个LambdaExceptionUtil
帮助器类允许您在Java流中使用任何已检查的异常,如下所示:
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(rethrowFunction(Class::forName))
.collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)
注意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; }
}
Run Code Online (Sandbox Code Playgroud)
关于如何使用它的许多其他示例(静态导入后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");
}
Run Code Online (Sandbox Code Playgroud)
注1:rethrow
上述LambdaExceptionUtil
类的方法可以毫无顾虑地使用,并且可以在任何情况下使用.非常感谢帮助解决最后一个问题的用户@PaoloC:现在编译器会要求你添加throw子句和所有东西,好像你可以在Java 8流上本地抛出已检查的异常.
注2:uncheck
上述LambdaExceptionUtil
类的方法是奖励方法,如果您不想使用它们,可以安全地从课程中删除它们.如果您确实使用过它们,请小心操作,而不是在了解以下用例,优点/缺点和限制之前:
• uncheck
如果要调用的方法实际上永远不会抛出它声明的异常,则可以使用这些方法.例如:new String(byteArr,"UTF-8")抛出UnsupportedEncodingException,但Java规范保证UTF-8始终存在.在这里,投掷声明是一个麻烦,任何解决方案,以最小的样板沉默它是受欢迎的:String text = uncheck(() -> new String(byteArr, "UTF-8"));
• uncheck
如果您实现的是严格的接口,而您没有添加throws声明的选项,则可以使用这些方法,但抛出异常是完全合适的.为了获得抛出它的特权而包装异常会导致带有虚假异常的堆栈跟踪,这些异常不会导致实际出错的信息.一个很好的例子是Runnable.run(),它不会抛出任何已检查的异常.
•在任何情况下,如果您决定使用这些uncheck
方法,请注意在没有throws子句的情况下抛出CHECKED异常的这两个后果:1)调用代码将无法按名称捕获它(如果您尝试,编译器会说:异常永远不会抛出相应的try语句的主体).它会冒泡并且可能会被主程序循环中的一些"捕获异常"或"捕获Throwable"捕获,这可能是你想要的.2)它违反了最不惊讶的原则:它将不再足以捕获RuntimeException
以保证捕获所有可能的异常.出于这个原因,我认为这不应该在框架代码中完成,而只能在您完全控制的业务代码中完成.
Bri*_*etz 25
你不能安全地做到这一点.你可以作弊,但是你的程序被打破了,这将不可避免地回来咬人(应该是你,但我们的作弊常常会打击别人.)
这是一个稍微安全的方法(但我仍然不建议这样做.)
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;
}
Run Code Online (Sandbox Code Playgroud)
在这里,您正在做的是捕获lambda中的异常,从流管道中抛出一个信号,指示计算异常失败,捕获信号,并对该信号进行操作以抛出基础异常.关键是你总是捕获合成异常,而不是在不声明抛出异常的情况下允许检查异常泄漏.
Pao*_*loC 22
您可以!
扩展@marcg UtilException
并throw 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;
}
}
Run Code Online (Sandbox Code Playgroud)
一些测试显示用法和行为:
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());
}
}
Run Code Online (Sandbox Code Playgroud)
Rob*_*žan 12
只需使用NoException(我的项目),jOOλ的Unchecked,throw -lambdas,Throwable接口或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));
Run Code Online (Sandbox Code Playgroud)
我编写了一个扩展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());
}
Run Code Online (Sandbox Code Playgroud)
小智 6
这个答案类似于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
}
Run Code Online (Sandbox Code Playgroud)
小智 6
您可以使用 apache commons-lang3 库来做到这一点。
public List<Class> getClasses() throws ClassNotFoundException {
List<Class> classes =
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(Failable.asFunction(Class::forName))
.collect(Collectors.toList());
return classes;
}
Run Code Online (Sandbox Code Playgroud)
你不能.
但是,您可能想看看我的一个项目,它可以让您更轻松地操纵这样的"抛出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());
Run Code Online (Sandbox Code Playgroud)
并抓住MyException
.
这是一个例子.另一个例子是你可以使用.orReturn()
一些默认值.
请注意,这仍然是一项正在进行的工作,更多的是即将到来.更好的名字,更多功能等
TL;DR 只需使用 Lombok 的@SneakyThrows
.
Christian Hujer 已经详细解释了为什么从流中抛出受检异常,严格来说,由于 Java 的限制是不可能的。
其他一些答案已经解释了绕过语言限制的技巧,但仍然能够满足抛出“已检查异常本身,并且不会向流中添加丑陋的尝试/捕获”的要求,其中一些需要数十行额外的行样板。
我将强调这样做的另一个选项,恕我直言,它比所有其他选项都干净得多:Lombok 的@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);
}
Run Code Online (Sandbox Code Playgroud)
我们只需要一个Extract Method
重构(由IDE完成)和一个额外的线路@SneakyThrows
。注释负责添加所有样板文件,以确保您可以抛出已检查的异常,而无需将其包装在 a 中RuntimeException
,也无需显式声明它。
归档时间: |
|
查看次数: |
106120 次 |
最近记录: |