当您的方法签名不允许抛出异常时如何抛出异常?

Posted

技术标签:

【中文标题】当您的方法签名不允许抛出异常时如何抛出异常?【英文标题】:How to throw an Exception when your method signature doesn't allow to throw Exception? 【发布时间】:2012-08-10 04:37:12 【问题描述】:

我有这样的方法:

public void getSomething()
...

我想在getSomething() 中抛出一个Exception。编译器不允许我这样做,因为我的方法不允许将Exception 扔在那里。但是我需要为我的测试抛出一个 Exception 的子类(我不能抛出 Unchecked Exception。这显然是一个 hack,但我需要它来进行测试。我尝试了 EasyMock,但它也不允许我这样做。任何想法如何做到这一点?

谢谢, 肖恩·阮

【问题讨论】:

您正在测试一些不可能发生的事情。为什么? 我尝试使用 LogicalHandler 模拟肥皂响应。 handleMessage(LogicalMessageContext context) 没有声明抛出异常。但是肥皂端点抛出的所有异常都是异常的子类。我需要抛出这些异常来模拟我的服务响应。 如果您有兴趣,请查看我的编辑。 为什么不能抛出未经检查的异常?我不明白你想做什么。你使用什么样的测试框架?您可以在测试中正常捕获未经检查的异常。 【参考方案1】:

方法一:

This post by Alexey Ragozin 描述了如何使用泛型技巧来引发未声明的检查异常。从那个帖子:

public class AnyThrow 

    public static void throwUnchecked(Throwable e) 
        AnyThrow.<RuntimeException>throwAny(e);
    

    @SuppressWarnings("unchecked")
    private static <E extends Throwable> void throwAny(Throwable e) throws E 
        throw (E)e;
    

这个技巧依赖于 throwUnchecked 向编译器“撒谎”类型 ERuntimeException 并调用 throwAny。由于throwAny 被声明为throws E,编译器认为特定的调用可以直接抛出RuntimeException。当然,这个技巧是通过throwAny 任意声明E 并盲目地转换为它来实现的,允许调用者决定它的参数被转换为什么 - 理智编码时的糟糕设计。在运行时,E 是 erased,没有任何意义。

正如你所说,做这样的事情是一个巨大的黑客,你应该很好地记录它的使用。


方法二:

您也可以为此使用sun.misc.Unsafe。首先,您必须实现一个使用反射来返回该类的实例的方法:

private static Unsafe getUnsafe() 
    try 
        Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafeField.setAccessible(true);
        return (Unsafe)theUnsafeField.get(null);
    
    catch (NoSuchFieldException e) 
        throw new RuntimeException(e);
    
    catch (IllegalAccessException e) 
        throw new RuntimeException(e);
    

这是必要的,因为调用 Unsafe.getUnsafe() 通常会抛出 SecurityException。一旦你有了Unsafe 的实例,你就可以使用它的可怕功能:

Unsafe unsafe = getUnsafe();
unsafe.throwException(new Exception());

致谢 this answer 在帖子 https://***.com/questions/5574241/interesting-uses-of-sun-misc-unsafe。我想我会为了完整性而提到这一点,但最好只使用上面的技巧而不是允许 Unsafe 进入你的代码。


方法三:

在有关使用 Unsafe、@bestsss points out 的链接答案的 cmets 中,使用已弃用的方法 Thread.stop(Throwable) 是一个更简单的技巧:

Thread.currentThread().stop(new Exception());

在这种情况下,您将使用@SuppressWarnings("deprecation") 并再次非常激烈地记录。同样,我更喜欢第一个技巧,因为它的(相对)清洁度。

【讨论】:

@PaulBellora 你总是可以使用 Class::newInstance() 方法,它可以抛出任何 ctor 所做的事情 @PaulBellora 为什么第一个示例在运行时没有ClassCastException @Demetri (E)e 是未经检查的强制转换,这意味着如果错误,JVM 不能快速失败。类型擦除后,该方法与 private static void throwAny(Throwable e) throws Throwable throw e; 无法区分。 Unsafe 就好了 @Enerccio 直到 Java 9【参考方案2】:

Java 有两种异常,已检查和未检查。您必须声明已检查的异常,但您不必声明未检查的异常。

RuntimeException 是基本的未经检查的异常,因此您可以在不声明的情况下抛出它。

public void getSomething()
   throw new RuntimeException("I don't have to be declared in the method header!");

附带说明一下,您可能不想抛出原始的 RuntimeException,而是将其子类化为更符合您需求的东西。 RuntimeException 的任何子类也将被取消选中。

【讨论】:

很抱歉不清楚。我需要抛出 java.lang.Exception 的子类(检查异常)。 @SeanNguyen RuntimeException 是异常的子类。这只是 Java 中的一种特殊情况,您不需要声明它会被抛出。 对不起,又不是很清楚。我需要抛出作为异常子类的已检查异常。我不能抛出 RuntimeException。 @SeanNguyen:请告诉我们为什么需要抛出一个检查异常。【参考方案3】:

我不知道你想做什么。您可以在方法中抛出未经检查的异常,然后使用 JUnit 进行测试,如下面的示例所示。

public void getSomething() 
    throw new RuntimeException();

JUnit 4 测试示例:

@Test
public void testGetSomething() 
    try 
        getSomething();
        fail("Expecting RuntimeException!");
     catch (Exception e) 
        Logger.getLogger("test").info("Exception was caught: " + e.getClass());
    

【讨论】:

以上是关于当您的方法签名不允许抛出异常时如何抛出异常?的主要内容,如果未能解决你的问题,请参考以下文章

java-异常处理和线程的一些简单方法及使用

在锁 c#2 中抛出异常

解析Java-throw抛出异常详细过程

异常-笔记-01

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

如何在 lambda 表达式中重新抛出异常? [复制]