我正在使用Java 8开展项目,并找到了一个我无法理解的情况。
我有这样的代码:
void deleteEntity(Node node) throws SomeException {
for (ChildNode child: node.getChildren()) {
deleteChild(child);
}
}
void deleteChild(Object child) throws SomeException {
//some code
}
此代码工作正常,但我可以使用方法引用重写它:
void deleteEntity(Node node) throws SomeException {
node.getChildren().forEach(this::deleteChild);
}
此代码无法编译,错误为Incompatible thrown types *SomeException* in method reference
。
IDEA也给了我错误unhandled exception
。
所以,我的问题是为什么?为什么代码为每个循环编译并且不用lambda编译?
答案 0 :(得分:64)
如果查看Consumer<T>
界面,accept
方法(您的方法引用将有效使用的方法)不会被声明为抛出任何已检查的异常 - 因此您可以&# 39;使用声明 的方法引用来抛出已检查的异常。增强的for循环是可以的,因为您总是在可以抛出SomeException
的上下文中。
您可能会创建一个包装器,将已检查的异常转换为未经检查的异常,然后抛出该异常。或者,您可以用accept()
方法声明自己的函数接口, 抛出一个已检查的异常(可能参数化该异常的接口),然后编写自己的forEach
将该功能接口作为输入的方法。
答案 1 :(得分:16)
你可以试试这个:
void deleteEntity(Node node) throws SomeException { node.getChildren().forEach(UtilException.rethrowConsumer(this::deleteChild));
}
下面的UtilException
帮助程序类允许您在Java流中使用任何已检查的异常。请注意,上面的流也会抛出this::deleteChild
引发的原始检查异常,而不是某些包装未经检查的异常。
public final class UtilException {
@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; }
}
关于如何使用它的许多其他示例(在静态导入UtilException
之后):
@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");
}
但在了解以下优点,缺点和限制之前不要使用它:
•如果调用代码要处理已检查的异常,则必须将其添加到包含该流的方法的throws子句中。 编译器不会强迫你再添加它,因此更容易忘记它。
•如果调用代码已经处理了已检查的异常,编译器将提醒您将throws子句添加到方法声明中 包含流(如果你不这样做会说:异常永远不会在相应的try语句的主体中抛出)。
•在任何情况下,您都无法围绕流本身来捕获已检查的异常INSIDE包含该流的方法 (如果你尝试,编译器会说:异常永远不会在相应的try语句的主体中抛出)。
•如果您正在调用一个字面上永远不会抛出它声明的异常的方法,那么您不应该包含throws子句。 例如:new String(byteArr,“UTF-8”)抛出UnsupportedEncodingException,但Java规范保证UTF-8始终存在。 在这里,投掷声明是令人讨厌的,任何解决方案,以最小的样板沉默它是受欢迎的。
•如果你讨厌检查异常,并认为永远不应该将它们添加到Java语言中(越来越多的人这样认为, 并且我不是其中之一),然后只是不要将已检查的异常添加到包含流的方法的throws子句中。检查 然后,异常将表现得像UNchecked异常。
•如果您正在实现一个严格的接口,在那里您没有添加throws声明的选项,但是抛出异常是 完全合适,然后包装异常只是为了获得抛出它的特权导致带有虚假异常的堆栈跟踪 没有提供有关实际出错的信息。一个很好的例子是Runnable.run(),它不会抛出任何已检查的异常。 在这种情况下,您可以决定不将已检查的异常添加到包含该流的方法的throws子句中。
•在任何情况下,如果您决定不将(但忘记添加)已检查的异常添加到包含流的方法的throws子句中, 注意抛出CHECKED异常的这两个后果:
1)调用代码将无法按名称捕获它(如果你尝试,编译器会说:异常永远不会抛出相应的试验体内 声明)。它会冒泡并可能在主程序循环中被一些“捕获异常”或“捕获Throwable”捕获,这可能是你的 无论如何都要。
2)它违反了最少惊喜的原则:捕获RuntimeException以便能够保证全部捕获它将不再足够 可能的例外。出于这个原因,我认为这不应该在框架代码中完成,而只能在您完全控制的业务代码中完成。
总之:我认为这里的限制并不严重,UtilException
类可以毫无畏惧地使用。但是,这取决于你!
答案 2 :(得分:1)
请注意,尽管抛出异常,但并行流将继续执行元素。
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;
public class ThrowingConsumerTest {
public static void main(String[] args) throws IOException {
List<Integer> myIntegerList = new ArrayList<>();
myIntegerList.add(1);
myIntegerList.add(2);
myIntegerList.add(3);
myIntegerList.add(null);
myIntegerList.add(4);
myIntegerList.add(5);
myIntegerList.add(6);
myIntegerList.add(7);
myIntegerList.add(8);
myIntegerList.add(9);
myIntegerList.add(10);
myIntegerList.add(11);
myIntegerList.add(12);
myIntegerList.add(13);
myIntegerList.add(14);
myIntegerList.add(15);
myIntegerList.add(16);
myIntegerList.add(17);
myIntegerList.add(18);
myIntegerList.add(19);
forEach(myIntegerList.stream(), ThrowingConsumerTest::exceptionThrowingConsumerCode);
}
/**
* Wrapper that converts Checked Exception to Runtime Exception
*/
static <T, E extends Exception> Consumer<T> unchecked(ThrowingConsumer<T, E> consumer) {
return (t) -> {
try {
consumer.accept(t);
} catch (Throwable e) {
//Lambda can return only RuntimeException.
RuntimeException ex = new RuntimeException();
ex.addSuppressed(e);
throw ex;
}
};
}
/**
* Wrapper that converts Runtime Exception to Checked Exception
* Custom forEach; to accept the exception throwing consumer.
*/
@SuppressWarnings("unchecked")
static <T, E extends Exception> void forEach(Stream<T> s, ThrowingConsumer<T, E> consumer) throws E {
try {
s.parallel().forEach(unchecked(t -> consumer.accept(t)));
} catch (RuntimeException e) {
//Checked Exception can be return from here
throw (E) e.getSuppressed()[0];
}
}
/*
* Consumer that throws Exception
*/
@FunctionalInterface
public interface ThrowingConsumer<T, E extends Exception> {
void accept(T t) throws E;
}
static void exceptionThrowingConsumerCode(Object i) throws IOException {
if (i == null) {
throw new IOException();
} else {
System.out.println(i);
}
}
}
答案 3 :(得分:0)
您也可以声明someException
,使其延伸RuntimeException
而不是Exception
。以下示例代码将编译:
public class Test {
public static void main(String[] args){
// TODO Auto-generated method stub
List<String> test = new ArrayList<String>();
test.add("foo");
test.add(null);
test.add("bar");
test.forEach(x -> print(x));
}
public static class SomeException extends RuntimeException{
}
public static void print(String s) throws SomeException{
if (s==null) throw new SomeException();
System.out.println(s);
}
}
输出将是:
foo
Exception in thread "main" simpleTextLayout.Test$SomeException
at simpleTextLayout.Test.print(Test.java:22)
at simpleTextLayout.Test.lambda$0(Test.java:14)
at java.util.ArrayList.forEach(ArrayList.java:1249)
at simpleTextLayout.Test.main(Test.java:14)
您可以在try/catch
语句周围添加forEach
块,但是一旦抛出异常,forEach
语句的执行将被中断。在上面的示例中,不会打印列表的"bar"
元素。此外,通过这样做,您将无法跟踪IDE中抛出的异常。
答案 4 :(得分:0)
**如果您不想编写自己的消费者界面并使用它。您可以轻松使用自定义异常,如下所示。您可以执行以下操作。 **
list.stream().forEach(x->{
try{
System.out.println(x/0);
}catch(ArithmeticException e){
throw new RuntimeException(new MyCustomException(FirstArgument,SecondArgument));
});