如何不抛出一般指定的异常?
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,然后<?>
是Throwable
并被编译器视为这样,迫使您创建一个try-catch 块。
@GhostCat,(不是答案,只是评论风格)在您的原始代码中,考虑做JDK所做的事情,并首先指定输入的类型参数,然后是输出。看java.util.function.Function<T,R>
,这是一个P1 => P2
函数,而不是像你的P2 => P1
。
@Eugene 这适用于“声明”部分,并传递方法引用。但是实际的调用然后给了我“Throws Throwable”。
啊!确实,对不起,我不能放过这个……还在考虑中
【参考方案1】:
你不能在 Java 中做到这一点。唯一的办法就是创建一个子接口。
public interface DefaultExceptionFactory<R, T>
extends Factory<R, T, RuntimeException>
【讨论】:
@Eugene,我不知道他是否需要永远不要拼出类型是什么。他可以创建一个包装方法并在其上保证其参数中的 lambda 永远不会抛出。然后他可以将X
拼写为AssertionError
(用于输入lambda 不符合约定的情况)。但这仍然需要在使用站点中拼出类型。
您还可以提供一个包装器实现,将任何Factory<R, T, ? extends RuntimeException>
升级为DefaultExceptionFactory<R, T>
【参考方案2】:
实现它的唯一方法是子类化——但我敢打赌你知道这一点。为了使我的论点更有说服力,请查看扩展了 BiFunction
的 BinaryOperator
。
【讨论】:
【参考方案3】:您必须将异常设为通用吗?为什么不将接口定义为
@FunctionalInterface
public interface Factory<R, T>
public R newInstanceFor(T t) throws Throwable;
如果需要,您可以随时在调用函数中捕获异常并检查类型。
【讨论】:
异常作为类型参数限制了实现代码的异常类型。 确实如此。但在这种情况下,异常类型是“X extends Throwable”。 是的,但是使用Factory
的实际代码可以说accept(Factory<R, T, ? extends IOException> 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<String,String> 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:如果图像大小超过可用空间,如何获取图像大小而不抛出异常?