throws x extends Exception方法签名

时间:2015-06-10 14:42:58

标签: java exception generics

阅读Optional的JavaDoc,我遇到了一个奇怪的方法签名;我一生中从未见过:

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
                                throws X extends Throwable

乍一看,我想知道通用异常<X extends Throwable>是如何实现的,因为你不能这样做(herehere)。在第二个想法,这开始有意义,因为它只是绑定Supplier ...但供应商本身确切知道它应该是什么类型,在泛型之前。

但第二行击中了我:

  • throws X是一个完整的通用异常类型。

然后:

  • X extends Throwable这个的意思是什么?
    • X已绑定在方法签名中。
  • 这会以任何方式解决一般的异常限制吗?
  • 为什么不只是throws Throwable,其余部分将被类型删除删除?

一个,不是直接相关的问题:

  • 此方法是否需要以catch(Throwable t)或提供的Supplier类型捕获;因为在运行时无法检查?

4 个答案:

答案 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 ThrowableX保证在最高边界绑定到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:

Javadoc Screenshot

双重确认:有一个open bug about the Javadoc for this class

答案 2 :(得分:8)

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

    catch(FooException<Bar> e)        --analogy->        o instanceof List<String>

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

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