为什么Runnable的run()方法不能抛出已检查异常?

21
根据JCIP的6.3.2节:
Runnable是一个相当受限的抽象;run不能返回值或抛出已检查的异常。
run()无法返回值,因为其返回类型为void,但为什么不能抛出已检查的异常?

如果您需要返回一个值,最好在ExecutorService中使用Callable接口。 - Alexey A.
3
哎,因为它被声明为不这样做了? - user207421
1
可能是Java线程:run方法无法抛出已检查异常的重复问题。 - Ciro Santilli OurBigBook.com
6个回答

31

这个方法无法抛出已检查异常,因为在第一个版本中它没有声明抛出已检查异常,而改变它太危险了。

最初Runnable仅用于包装的Thread中,假设开发人员想要捕获所有已检查的异常并处理它们,而不是将它们记录到System.err中。

当你可以将单独的任务添加到Executor中并在Future中捕获结果和任何抛出的异常时,就会添加Callable

Callable现在允许您返回值并且可选地声明已检查异常。

顺便说一下:您可以使用类似以下方式来表达不想从可调用对象中返回或抛出已检查异常的意思

Callable<Void> callable = new Callable<Void>() {
    public Void call() {
        // do something
        return null;
    }
};

1
"太危险了" == 会破坏数十万现有程序的兼容性。这是一个大大的“不行”! - Stephen C
+1,非常好的答案。正是我需要的:一个不返回值的可调用对象。 - Bathsheba
嗨Peter,感谢你的好回复!请你详细说明一下这个问题:“最初Runnable只在包装线程中使用,并且假定开发人员希望捕获所有已检查的异常并处理它们,而不是将它们记录到System.err。” - lowLatency
@PeterLawrey 谢谢,我同意你的观点。但是我想知道背后的思考过程(为什么会设计成这样)。 - lowLatency
@Naroji 检查异常旨在由调用者处理。对于 Java 1.0 中的 Runnable,没有一种通用的方式来处理 run() 抛出的异常。在 Java 5.0 中,有一种方法可以提交任务并使其抛出已检查的异常,并且您可以在获取结果时处理它。 - Peter Lawrey
显示剩余7条评论

5
run() 方法不能抛出已检查异常,因为其没有声明会抛出此类异常。未经声明的情况下,你不能抛出已检查异常。
此外,当某个方法覆盖或实现另一个不抛出该异常的方法时,也不能在该方法上声明已检查异常。因此,实现 Runnable 接口的类不能简单地为其 run() 方法添加 throws 子句。

1
特别令人困惑的是,当前关于invokeAndWait的文档讨论了“如果Runnable.run方法抛出未捕获的异常会发生什么”。我相信很多人已经阅读过这篇文章并尝试为他们重写的run()方法声明异常子句。 - Ti Strga

5

这不是回答问题的答案,而是对 Lukas Eder 的答案 的跟进,展示了另一种将已检查异常走私到不允许静态使用的地方的方法。这取决于一个事实:如果使用 newInstance 调用一个无参数构造函数,则它抛出的任何已检查异常都会向上逃逸。

public class Thrower {

    private static final ThreadLocal<Exception> toThrow = new ThreadLocal<Exception>();

    public static void throwUnsafely(Exception e) {
        try {
            toThrow.set(e);
            Thrower.class.newInstance();
        } catch (InstantiationException f) {
            throw new RuntimeException("unexpected exception while throwing expected exception", f);
        } catch (IllegalAccessException f) {
            throw new RuntimeException("unexpected exception while throwing expected exception", f);
        } finally {
            toThrow.remove();
        }
    }

    private Thrower() throws Exception {
        throw toThrow.get();
    }

}

这是A级别的真正古老的黑帽Java巫术。永远不要这样做,除非在聚会上想给人留下印象。


3
我认为在Runnable中保留signature void run()的动机是它不像其他方法那样被调用,而是设计成由CPU线程调度程序调用。如果是这样,谁将接收其返回值,谁将处理抛出的已检查异常。Java 5.0引入了UncaughtExceptionHandler来处理线程抛出的未捕获异常。Executor Framework将返回值或抛出的异常(封装在ExecutionException中)保存为一些对象(如Outer类实例)跨线程共享的状态,并将其传递给Future的调用者(在其他线程中运行)get()。

3
确切地说,正如其他人所说,字面上的答案显然是“run()没有声明抛出任何异常”,但我认为真正的问题是为什么会这样。答案是,run()被设计成由线程调用,在run()返回后终止或返回到线程池,那么谁会捕获它呢?因此,抛出异常从来没有用过。 - Chad N B

2
如果您查看Runnable接口,会发现void run()方法没有声明抛出任何已检查异常,而您的Thread类实现了Runnable接口。 JLS指出,如果在接口/超类中没有声明,方法m1将不能抛出异常。

2

你可以随时不安全地抛出已检查的异常:

import java.lang.reflect.Field;
import sun.misc.Unsafe;

public class UnsafeSample {
    public void methodWithNoDeclaredExceptions( ) {
        Unsafe unsafe = getUnsafe();
        unsafe.throwException( new Exception( "this should be checked" ) );
    }

    private Unsafe getUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main( String[] args ) {
        new UnsafeSample().methodWithNoDeclaredExceptions();
    }
}

请查看完整文章:

http://java.dzone.com/articles/throwing-undeclared-checked.

另一个选择:
public class Test {
    public static void main(String[] args) {
        doThrow(new SQLException());
    }

    public static void doThrow(Exception e) {
        Test.<RuntimeException> doThrow0(e);
    }

    @SuppressWarnings("unchecked")
    public static <E extends Exception> void doThrow0(Exception e) throws E {
        throw (E) e;
    }
}

这是在这里展示的:

http://java.dzone.com/articles/throw-checked-exceptions

话虽如此,不要这样做! ;-)


1
你甚至不需要使用 Unsafe。从无参构造函数抛出的已检查异常会逃逸一个 newInstance 调用。我会添加一个答案... - Tom Anderson
啊,那篇文章已经涵盖了这个问题。现在我看起来像个笨蛋。 - Tom Anderson
@TomAnderson:无论如何,这也值得一提。 - Lukas Eder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接