throws x extends Exception方法签名

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,其势必会OptionalT
  • 我们期望一个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后来的链中.

  • 小心**关于在问题中添加多个问题.我已经解决了之前的问题,我很乐意审查修订后的子弹,但第二个问题是过度使用.我稍后会添加该部分. (2认同)
  • 理解两件事:"X"决定了我们要抛出的东西,"供应商"?扩展X>`指定*什么*特定的东西是我们要扔的.如果您的Supplier实例提供了特定类型的异常,那么就应该捕获它而不是`Throwable`. (2认同)

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截图

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


Zho*_*gYu 8

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

    catch(FooException<Bar> e)        --analogy->        o instanceof List<String>
Run Code Online (Sandbox Code Playgroud)

catchinstanceof依赖于运行时类型信息; 擦除废墟.

但是,禁止像例程这样的异常类型的泛型声明有点过于苛刻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)

在这里,IOExceptionfoo()运行时抛出; 然而编译器和运行时无法阻止它.擦除主要是责任.

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)