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 exceptionSupplier) throws X 
  • X的上限为Throwable 。 这在以后很重要。
  • 我们返回一个类型T ,它绑定到OptionalT
  • 我们希望Supplier的通配符上限为X
  • 我们抛出X (这是有效的,因为X有一个Throwable的上限)。 这在JLS 8.4.6中规定; 只要X被视为Throwable的子类型,其声明在此处是有效且合法的。

有一个关于Javadoc误导的漏洞 。 在这种情况下,最好信任源代码而不是文档,直到声明bug被修复为止。

至于为什么我们使用throws X而不是throws ThrowableX保证在最高边界绑定到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 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截图

双重确认: 关于这个类的Javadoc有一个开放的错误

由于擦除,Java无法捕获通用exception类型

  catch(FooException e) --analogy-> o instanceof List 

catchinstanceof取决于运行时类型信息; 擦除废墟。

但是,禁止像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 

作为参数类型。 但是你会丢失用于throws子句的类型变量。

后期编辑:

为什么不throws Throwable ,其余的将被类型擦除删除?

您希望throws使用Supplier提供的相同Exception类型。

是否需要将此方法捕获到catch(Throwable t)子句中?

否/依赖。 如果将已检查的exception绑定到X ,则需要以某种方式处理它。 否则,“不需要” catch 。 但最终会有/应该处理它,即使它是JVM本身。