如何不抛出一般指定的异常?

Posted

技术标签:

【中文标题】如何不抛出一般指定的异常?【英文标题】:How to not throw a generically specified exception? 【发布时间】:2019-01-07 18:48:03 【问题描述】:

我创建了一个“生产者”接口(与方法引用一起使用,分别便于单元测试模拟):

@FunctionalInterface
public interface Factory<R, T, X extends Throwable> 
    public R newInstanceFor(T t) throws X;

我是这样创建的,因为我的第一个用例实际上必须抛出一些检查过的WhateverException

但我的第二个用例没有要抛出的 X。

我能想出的让编译器满意的最好办法是:

Factory<SomeResultClass, SomeParameterClass, RuntimeException> factory;

编译,并做我需要的,但仍然丑陋。有没有办法保留该单一接口,但在声明特定实例时不提供 X?

【问题讨论】:

为什么X是界面的一部分? @daniu,然后&lt;?&gt;Throwable 并被编译器视为这样,迫使您创建一个try-catch 块。 @GhostCat,(不是答案,只是评论风格)在您的原始代码中,考虑做JDK所做的事情,并首先指定输入的类型参数,然后是输出。看java.util.function.Function&lt;T,R&gt;,这是一个P1 =&gt; P2函数,而不是像你的P2 =&gt; P1 @Eugene 这适用于“声明”部分,并传递方法引用。但是实际的调用然后给了我“Throws Throwable”。 啊!确实,对不起,我不能放过这个……还在考虑中 【参考方案1】:

你不能在 Java 中做到这一点。唯一的办法就是创建一个子接口。

public interface DefaultExceptionFactory<R, T>
        extends Factory<R, T, RuntimeException>

【讨论】:

@Eugene,我不知道他是否需要永远不要拼出类型是什么。他可以创建一个包装方法并在其上保证其参数中的 lambda 永远不会抛出。然后他可以将X 拼写为AssertionError(用于输入lambda 不符合约定的情况)。但这仍然需要在使用站点中拼出类型。 您还可以提供一个包装器实现,将任何Factory&lt;R, T, ? extends RuntimeException&gt; 升级为DefaultExceptionFactory&lt;R, T&gt;【参考方案2】:

实现它的唯一方法是子类化——但我敢打赌你知道这一点。为了使我的论点更有说服力,请查看扩展了 BiFunctionBinaryOperator

【讨论】:

【参考方案3】:

您必须将异常设为通用吗?为什么不将接口定义为

@FunctionalInterface
public interface Factory<R, T> 
    public R newInstanceFor(T t) throws Throwable;

如果需要,您可以随时在调用函数中捕获异常并检查类型。

【讨论】:

异常作为类型参数限制了实现代码的异常类型。 确实如此。但在这种情况下,异常类型是“X extends Throwable”。 是的,但是使用Factory 的实际代码可以说accept(Factory&lt;R, T, ? extends IOException&gt; factory) 啊,错过了。同意。谢谢。【参考方案4】:

如果可能的话,您可以像下面的代码一样将方法定义为通用方法:

@FunctionalInterface
public interface Factory<R, T> 
    public <X extends Throwable> R newInstanceFor(T t) throws X;

【讨论】:

这看起来很有趣,但我还没有设法编译它。您是否尝试过创建抛出 IOException 的实例? 它为我编译,但不幸的是,它破坏了我的第一个用例。当使用 SomeClassThatReallyThrows::new 之类的引用时......现在要求处理“真正抛出”的异常。太糟糕了,这看起来真不错。 更具体地说,我收到此 lambda 表单的使用站点的编译错误,即“Illegal lambda expression: Method newInstanceFor of type Fiddle.Factory&lt;String,String&gt; is generic”。 @GhostCat,lambda SAM 类型本身也可以为我编译。使用网站却没有。 @BenoArakelyan,老实说,我不明白这个例子如何适用。你有具体的类,而这个问题更多的是关于功能接口。当我使用 lambda 表达式实现 Factory 时,我还没有设法使用您的代码编译示例。如果您有示例,请将其添加到您的答案中。【参考方案5】:

这更像是一个“社会工程”的答案:我们在 lambda 表单上放置了一个不会抛出任何东西的合约:

public interface Factory<T, R, X> 

    public R newInstanceFor(T arg) throws X;

    public static Factory<R, U, AssertionError> neverThrows(Factory<U, V, ?> input) 
        return u -> 
            try 
                return input.newInstanceFor(u);
            
            catch(Throwable t) 
                throw new AssertionError("Broken contract: exception thrown", t);
            
        ;
    

用法是这样的,或者类似的东西:

class MyClass 
    Factory<MyInput, MyOtherClass, AssertionError> factory;

    MyClass(Factory<MyInput, MyOtherClass, ?> factory) 
        this.factory = Factory.neverThrows(factory);
    

    public void do() 
      factory.newInstanceFor(new MyInput()).do();
    

这种方法的缺点:你不能在类型签名中真正指定契约,契约是一个实现细节。如果你想在类型签名中有这个,你需要第二个子接口。

【讨论】:

【参考方案6】:

你可以使用Project Lombok的@SneakyThrows注解:

@FunctionalInterface
public interface Factory<R, T> 

    @SneakyThrows
    R newInstanceFor(T t);

这允许您抛出任何异常(选中或未选中)。但请阅读文档,因为必须小心处理此功能。

【讨论】:

这个偷偷摸摸的投掷功能不是特定于 lombok 的,而是特定于 jls 的。您是否根据 OP 要求尝试过您的代码?

以上是关于如何不抛出一般指定的异常?的主要内容,如果未能解决你的问题,请参考以下文章

IMAPI:如果图像大小超过可用空间,如何获取图像大小而不抛出异常?

如何找出PHP代码块可能抛出的所有错误?

停止不抛出异常Android的线程

为啥不抛出异常的代码允许捕获已检查的异常?

为啥设置 DataSource 时 ComboBox 不抛出异常?

为啥 JPA 重复持久方法不抛出异常?