Mor*_*hai 22 java generics exception
阅读JavaDoc Optional
,我遇到了一个奇怪的方法签名; 我一生中从未见过:
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
throws X extends Throwable
Run Code Online (Sandbox Code Playgroud)
乍一看,我想知道通用异常<X extends Throwable>
是如何可能的,因为你不能这样做(这里和这里).第二个想法,这开始有意义,因为它只是绑定Supplier
...但供应商本身确切知道它应该是什么类型,在泛型之前.
但第二行击中了我:
throws X
是一个完整的通用异常类型.然后:
X extends Throwable
,这是什么意思?
X
已经绑定在方法签名中.throws Throwable
,其余的将被类型擦除删除?一,不是直接相关的问题:
catch(Throwable t)
,或者被提供Supplier
的类型; 因为它无法在运行时检查?Mak*_*oto 16
像你读过的任何其他通用代码一样对待它.
这是我在Java 8的源代码中看到的正式签名:
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X
Run Code Online (Sandbox Code Playgroud)
X
有一个上限Throwable
.这在以后很重要.T
,其势必会Optional
的T
Supplier
具有通配符上限的X
X
(这是有效的,因为它X
有一个上限Throwable
).这在JLS 8.4.6中规定; 只要X
被视为子类型Throwable
,其声明在此处有效且合法.有一个关于Javadoc误导的漏洞.在这种情况下,最好信任源代码而不是文档,直到声明bug被修复为止.
至于为什么我们使用throws X
而不是throws Throwable
: X
保证Throwable
在最高限度内被束缚.如果你想要一个更具体的Throwable
(运行时,检查,或Error
),那么仅仅投掷Throwable
不会给你这种灵活性.
至于你的上一个问题:
是否需要将此方法捕获到catch(Throwable t)子句中?
链中的某些东西必须处理异常,无论是try...catch
块还是JVM本身.理想情况下,人们希望创建一个Supplier
最能满足其需求的异常的约束.你不必(并且可能应该不是)创建一个catch(Throwable t)
针对这种情况; 如果您Supplier
的类型绑定到您需要处理的特定异常,那么最好将其用作catch
后来的链中.
dur*_*597 11
Javadoc的方法签名与源代码不同.根据b132来源:
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
throws X { // Added by me: See? No X extends Throwable
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
Run Code Online (Sandbox Code Playgroud)
确认:这是Javadoc生成的一个问题.作为测试,我创建了一个新项目并为以下类生成了Javadoc:
package com.stackoverflow.test;
public class TestJavadoc {
public static <X extends Throwable> void doSomething() throws X {
}
}
Run Code Online (Sandbox Code Playgroud)
这是由此产生的Javadoc:
双重确认:关于这个类的Javadoc有一个开放的错误
由于擦除,Java无法捕获通用异常类型
catch(FooException<Bar> e) --analogy-> o instanceof List<String>
Run Code Online (Sandbox Code Playgroud)
catch
并instanceof
依赖于运行时类型信息; 擦除废墟.
但是,禁止像例程这样的异常类型的泛型声明有点过于苛刻FooException<T>
.Java可以允许它; 只需要允许只允许原始类型和通配符类型catch
catch(FooException e) --analogy-> o instanceof List
catch(FooException<?> e) o instanceof List<?>
Run Code Online (Sandbox Code Playgroud)
可以提出一个论点,一个例外的类型主要是对catch子句感兴趣; 如果我们无法捕获通用异常类型,那么首先没有必要使用它们.好.
现在,我们也不能这样做
catch(X e) // X is a type variable
Run Code Online (Sandbox Code Playgroud)
那为什么Java允许X
首先被抛出?
... foo() throws X
Run Code Online (Sandbox Code Playgroud)
正如我们稍后将看到的,这种构造在擦除的帮助下实际上可以颠覆类型系统.
嗯,这个功能非常有用 - 至少在概念上.我们编写泛型方法,以便我们可以编写未知输入和返回类型的模板代码; 投掷类型的逻辑相同!例如
<X extends Exception> void logAndThrow(X ex) throws X
...
throw ex;
...
(IOException e){
logAndThrow(e); // throws IOException
Run Code Online (Sandbox Code Playgroud)
我们需要能够在抽象代码块时一般性地抛出异常透明度.另一个例子,说我们厌倦了反复写这种样板
lock.lock();
try{
CODE
}finally{
lock.unlock();
}
Run Code Online (Sandbox Code Playgroud)
我们想要一个包装器方法,为CODE接受一个lambda
Util.withLock(lock, ()->{ CODE });
Run Code Online (Sandbox Code Playgroud)
问题是,CODE可以抛出任何异常类型; 最初的样板抛出任何CODE抛出的东西; 我们希望writeLock()
表达式也这样做,即异常透明度.方案 -
interface Code<X extends Throwable>
{
void run() throws X;
}
<X extends Throwable> void withLock(Lock lock, Code<X> code) throws X
...
code.run(); // throws X
...
withLock(lock, ()->{ throws new FooException(); }); // throws FooException
Run Code Online (Sandbox Code Playgroud)
这仅适用于已检查的例外情况.未经检查的异常无论如何都可以自由传播.
该throws X
方法的一个严重缺陷是它不适用于2种或更多异常类型.
好吧,那真的很好.但是我们在新的JDK API中没有看到任何这样的用法.任何方法参数的函数类型都不能抛出任何已检查的异常.如果这样做Stream.map(x->{ BODY })
,BODY
则不能抛出任何已检查的异常.他们真的很讨厌用lambdas检查异常.
另一个例子是CompletableFuture
,例外是整个概念的核心部分; 但是,在lambda体中不允许检查异常.
如果你是一个梦想家,你可能会相信在Java的未来版本中,将会发明一种优雅的lambda异常透明机制,就像这个丑陋的throws X
黑客一样; 因此,我们现在不需要用<X>
s 污染我们的API .是的,梦想,伙计.
那么,throws X
反正使用了多少?
在整个JDK源代码中,唯一能做到这一点的公共API Optional.orElseThrow()
(及其堂兄弟OptionalInt/Long/Double
).而已.
(设计有一个缺点orElseGet/orElseThrow
- 分支的决定不能在lambda体中完成.我们可以设计一种更通用的方法
T orElse( lambda ) throws X
optional.orElse( ()->{ return x; } );
optional.orElse( ()->{ throw ex; } );
optional.orElse( ()->{ if(..)return x; else throw ex; } );
Run Code Online (Sandbox Code Playgroud)
此外orElseThrow
,JDK中唯一的另一种throws X
非公共方法ForkJoinTask.uncheckedThrow()
.它用于"偷偷摸摸",即代码可以抛出一个已检查的异常,而编译器和运行时不知道它 -
void foo() // no checked exception declared in throws
{
throw sneakyThrow( new IOExcepiton() );
}
Run Code Online (Sandbox Code Playgroud)
在这里,IOException
从foo()
运行时抛出; 然而编译器和运行时无法阻止它.擦除主要是责任.
public static RuntimeException sneakyThrow(Throwable t)
{
throw Util.<RuntimeException>sneakyThrow0(t);
}
@SuppressWarnings("unchecked")
private static <T extends Throwable> T sneakyThrow0(Throwable t) throws T
{
throw (T)t;
}
Run Code Online (Sandbox Code Playgroud)