throws x extends Exception方法签名
阅读Optional
的JavaDoc,我遇到了一个奇怪的方法签名; 我一生中从未见过:
public T orElseThrow(Supplier exceptionSupplier) throws X extends Throwable
乍一看,我想知道如何通用exception是可能的,因为你不能这样做( 这里和这里 )。 在第二个想法,这开始有意义,因为它只是绑定
Supplier
……但供应商本身确切地知道它应该是什么类型,在generics之前。
但第二行击中了我:
-
throws X
是一个完整的genericsexception类型。
接着:
-
X extends Throwable
, 这意味着什么?-
X
已经绑定在方法签名中。
-
- 这会以任何方式解决一般的exception限制吗?
- 为什么不
throws Throwable
,其余的将被类型擦除删除?
一,不是直接相关的问题:
- 是否需要将此方法捕获为
catch(Throwable t)
或提供的Supplier
类型; 因为它无法在运行时检查?
像你读过的任何其他通用代码一样对待它。
这是我在Java 8的源代码中看到的正式签名:
public T orElseThrow(Supplier extends X> exceptionSupplier) throws X
-
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)子句中?
链中的某些东西必须处理exception,即try...catch
块或JVM本身。 理想情况下,人们可能希望创建一个与最能满足其需求的例外相关的Supplier
。 您不必(也可能不应该catch(Throwable t)
为此案例创建一个catch(Throwable t)
; 如果您的Supplier
的类型绑定到您需要处理的特定exception,那么最好将其用作后续链中的catch
。
Javadoc的方法签名与源代码不同。 根据b132来源 :
public 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 void doSomething() throws X { } }
这是由此产生的Javadoc:
双重确认: 关于这个类的Javadoc有一个开放的错误
由于擦除,Java无法捕获通用exception类型
catch(FooException e) --analogy-> o instanceof List
catch
和instanceof
取决于运行时类型信息; 擦除废墟。
但是,禁止像FooException
这样的exception类型的generics声明有点过于苛刻。 Java可以允许它; 只需要在catch
只允许原始类型和通配符类型
catch(FooException e) --analogy-> o instanceof List catch(FooException> e) o instanceof List>
可以提出一个论点,exception的类型主要是对catch子句感兴趣; 如果我们无法捕获通用exception类型,那么首先没有必要使用它们。 好。
现在,我们也不能这样做
catch(X e) // X is a type variable
那为什么Java允许X
首先被抛出?
... foo() throws X
正如我们稍后将看到的,这种构造在擦除的帮助下实际上可以颠覆类型系统。
嗯,这个function非常有用 – 至少在概念上。 我们编写generics方法,以便我们可以为未知输入和返回类型编写模板代码; 投掷类型的逻辑相同! 例如
void logAndThrow(X ex) throws X ... throw ex; ... (IOException e){ logAndThrow(e); // throws IOException
在抽象代码块时,我们需要能够通常抛出exception透明度。 另一个例子,说我们厌倦了反复编写这种样板文件
lock.lock(); try{ CODE }finally{ lock.unlock(); }
我们想要一个包装器方法,为CODE接受一个lambda
Util.withLock(lock, ()->{ CODE });
问题是,CODE可以抛出任何exception类型; 最初的样板抛出任何CODE抛出; 并且我们希望writeLock()
表达式执行相同的操作,即exception透明度。 方案 –
interface Code { void run() throws X; } void withLock(Lock lock, Code code) throws X ... code.run(); // throws X ... withLock(lock, ()->{ throws new FooException(); }); // throws FooException
这仅适用于已检查的exception。 未经检查的exception无论如何都可以自由传播。
throws X
方法的一个严重缺陷是它不能很好地处理2个或更多exception类型。
好吧,那真的很好。 但是我们在新的JDK API中没有看到任何这样的用法。 任何方法参数的函数类型都不能抛出任何已检查的exception。 如果你执行Stream.map(x->{ BODY })
, BODY
不能抛出任何已检查的exception。 他们真的很讨厌用lambdas检查exception。
另一个例子是CompletableFuture
,其中例外是整个概念的核心部分; 但是,在lambda体中也不允许检查exception。
如果你是一个梦想家,你可能会相信,在Java的未来版本中,将会发明一种优雅的lambdaexception透明机制,就像这个丑陋的throws X
hack; 因此,我们暂时不需要用
污染我们的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()
。 它用于“偷偷摸摸”,即代码可以抛出一个已检查的exception,而编译器和运行时不知道它 –
void foo() // no checked exception declared in throws { throw sneakyThrow( new IOExcepiton() ); }
这里, IOException
在运行时从foo()
抛出; 然而编译器和运行时无法阻止它。 擦除主要是责任。
public static RuntimeException sneakyThrow(Throwable t) { throw Util.sneakyThrow0(t); } @SuppressWarnings("unchecked") private static T sneakyThrow0(Throwable t) throws T { throw (T)t; }
generics类型和generics变量/参数之间存在差异。
generics类型是声明generics参数的类型。 例如
class Generic {}
不允许的是generics类型(如上所述),它也扩展了Throwable
。
class Generic extends Throwable {} // nono
这在Java语言规范中表达
如果generics类是
Throwable
(第11.1.1节)的直接或间接子类,则是编译时错误。
但是,通用变量本身可以具有任何绑定,通用或其他方式,只要它是引用类型即可。
这个
X extends Throwable
表示在编译时绑定(不具有边界) X
任何类型必须是Throwable
的子类型。
X已经绑定在方法签名中。
X
没有约束。 该方法声明了它的边界。
这会以任何方式解决一般的exception限制吗?
没有。
为什么不
extends Throwable
,其余的将被类型擦除删除?
generics是一个编译时function。 编译后发生擦除。 我相信他们可以简单地使用
Supplier extends Throwable>
作为参数类型。 但是你会丢失用于throws
子句的类型变量。
后期编辑:
为什么不
throws Throwable
,其余的将被类型擦除删除?
您希望throws
使用Supplier
提供的相同Exception
类型。
是否需要将此方法捕获到
catch(Throwable t)
子句中?
否/依赖。 如果将已检查的exception绑定到X
,则需要以某种方式处理它。 否则,“不需要” catch
。 但最终会有/应该处理它,即使它是JVM本身。