阅读Optional
的JavaDoc,我遇到了一个奇怪的方法签名;我一生中从未见过:
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
throws X extends Throwable
乍一看,我想知道通用异常<X extends Throwable>
是如何实现的,因为你不能这样做(here和here)。在第二个想法,这开始有意义,因为它只是绑定Supplier
...但供应商本身确切知道它应该是什么类型,在泛型之前。
但第二行击中了我:
throws X
是一个完整的通用异常类型。然后:
X extends Throwable
,这个的意思是什么?
X
已绑定在方法签名中。throws Throwable
,其余部分将被类型删除删除?一个,不是直接相关的问题:
catch(Throwable t)
或提供的Supplier
类型捕获;因为在运行时无法检查?答案 0 :(得分:16)
像对待您阅读的任何其他通用代码一样对待它。
这是我在Java 8's source code中看到的正式签名:
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X
X
的上限为Throwable
。这将在以后发挥重要作用。T
Optional
T
类型
Supplier
的通配符上限为X
X
(这是有效的,因为X
的上限为Throwable
)。这在JLS 8.4.6中指定;只要X
被视为Throwable
的子类型,其声明在此处有效且合法。There is an open bug关于Javadoc的误导。在这种情况下,最好信任源代码而不是文档,直到声明bug被修复为止。
至于为什么我们使用throws X
代替throws Throwable
:X
保证在最高边界绑定到Throwable
。如果您想要更具体的Throwable
(运行时,已检查或Error
),那么仅仅投掷Throwable
就不会给您灵活性。
到你的上一个问题:
是否需要将此方法捕获到catch(Throwable t)子句中?
链中的某些必须处理异常,无论是try...catch
块还是JVM本身。理想情况下,人们可能希望创建一个Supplier
绑定到最能满足其需求的异常。您不必(也可能 不 )为此案例创建catch(Throwable t)
;如果您的Supplier
类型绑定到您需要处理的特定例外,那么最好将其作为您catch
以后的链中使用。
答案 1 :(得分:11)
Javadoc的方法签名与源代码不同。根据b132 source:
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();
}
}
已确认:这是Javadoc生成的问题。作为测试,我创建了一个新项目并为以下类生成了Javadoc:
package com.stackoverflow.test;
public class TestJavadoc {
public static <X extends Throwable> void doSomething() throws X {
}
}
这是生成的Javadoc:
答案 2 :(得分:8)
由于擦除,Java无法捕获通用异常类型
catch(FooException<Bar> e) --analogy-> o instanceof List<String>
catch
和instanceof
取决于运行时类型信息;擦除废墟。
但是,禁止像FooException<T>
这样的异常类型的泛型声明有点过于苛刻。 Java可以允许它;只要求catch
catch(FooException e) --analogy-> o instanceof List
catch(FooException<?> e) o instanceof List<?>
可以认为,异常的类型主要是对catch子句感兴趣;如果我们无法捕获通用异常类型,那么首先没有必要使用它们。行。
现在,我们不能这样做
catch(X e) // X is a type variable
那么为什么Java首先允许X
被抛出?
... foo() throws X
正如我们稍后会看到的,这种构造在擦除的帮助下,实际上可能会破坏类型系统。
嗯,这个功能非常有用 - 至少在概念上是这样。我们编写泛型方法,以便我们可以编写未知输入和返回类型的模板代码;投掷类型的逻辑相同!例如
<X extends Exception> void logAndThrow(X ex) throws X
...
throw ex;
...
(IOException e){
logAndThrow(e); // throws IOException
我们需要能够在抽象代码块时一般性地抛出异常透明度。另一个例子,说我们厌倦了反复编写这种样板文件
lock.lock();
try{
CODE
}finally{
lock.unlock();
}
我们想要一个包装器方法,为CODE接受lambda
Util.withLock(lock, ()->{ CODE });
问题是,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
这仅适用于已检查的例外情况。未经检查的异常无论如何都可以自由传播。
throws X
方法的一个严重缺陷是它不能很好地处理2种或更多异常类型。
好的,这真的很棒。但是我们在新的JDK API中没有看到任何这样的用法。任何方法参数的函数类型都不能抛出任何已检查的异常。如果您执行Stream.map(x->{ BODY })
,则BODY
无法抛出任何已检查的异常。他们真的很讨厌用lambdas检查异常。
另一个例子是CompletableFuture
,其中例外是整个概念的核心部分;但是,在lambda体中也不允许检查异常。
如果你是一个梦想家,你可能会相信,在Java的未来版本中,将会发明一种优雅的lambda异常透明机制,就像这个丑陋的throws X
黑客一样;因此,我们现在不需要用<X>
污染我们的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; } );
除了orElseThrow
之外,JDK中throws X
是非公开ForkJoinTask.uncheckedThrow()
的唯一其他方法。它用于&#34;偷偷摸摸&#34;,即代码可以抛出一个已检查的异常,而编译器和运行时不知道它 -
void foo() // no checked exception declared in throws
{
throw sneakyThrow( new IOExcepiton() );
}
这里,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;
}
答案 3 :(得分:3)
泛型类型和通用变量/参数之间存在差异。
泛型类型是声明泛型参数的类型。例如
class Generic<T> {}
不允许的是泛型类型(如上所述),它也扩展了Throwable
。
class Generic <T> extends Throwable {} // nono
这在Java语言规范中表达,here
如果泛型类是直接类或间接类,则是编译时错误
Throwable
的子类(第11.1.1节)。
然而,通用变量本身可以具有任何绑定,泛型或其他,只要它是引用类型。
此
X extends Throwable
表示在编译时绑定(不具有边界)到X
的任何类型必须是Throwable
的子类型。
X已绑定在方法签名中。
X
没有约束力。该方法声明了它的边界。
这会以任何方式解决一般的异常限制吗?
没有
为什么不只是
extends Throwable
,其余部分将被类型删除删除?
泛型是一个编译时功能。编译后发生擦除。我相信他们可以简单地使用
Supplier<? extends Throwable>
作为参数类型。但是你会失去在throws
子句中使用的类型变量。
后编辑:
为什么不只是
throws Throwable
,其余部分将被类型删除删除?
您希望throws
使用Exception
提供的相同Supplier
类型。
是否需要将此方法捕获到
catch(Throwable t)
子句中?
没有/依赖。如果将已检查的异常绑定到X
,那么您需要以某种方式处理它。否则,“{1}}不是”必需的“。但最终会有/应该处理它,即使它是JVM本身。