为什么Class.newInstance()方法被称为“邪恶”?

100

Ryan Delucchi这里在第三个评论中向Tom Hawtin提问:

为什么Class.newInstance()是“邪恶的”?

这是对代码示例的回应:

// Avoid Class.newInstance, for it is evil.
Constructor<? extends Runnable> ctor = runClass.getConstructor();
Runnable doRun = ctor.newInstance();

那么,为什么它会是邪恶的?


11
实际上,针对这个问题的答案,人们可以说这一点适用于各种使用反射的情况,而不仅仅是Class.newInstance()。因此,这实际上是一个一般性的观察,“反射打败了编译时检查”,这也经常是使用反射的目的所在。 - Ryan Delucchi
26
这几天的孩子们啊,喔,他们满口喊着“邪恶”,可他们连COBOL或FORTRAN程序都没见过!你想了解什么是“邪恶”?看看一个20年前的FORTRAN程序吧,它被那些有模拟背景但没有计算机科学影响的修补工程师从一个项目传到另一个项目!那才是真正的“邪恶”! - NoMoreZealots
请参见http://stackoverflow.com/q/36272566/3888450 - Stefan Dollase
3个回答

86
Java API文档解释了为什么可以绕过编译时异常检查(http://java.sun.com/javase/6/docs/api/java/lang/Class.html#newInstance()):

请注意,此方法会传播由无参构造方法抛出的任何异常,包括已检查异常。使用此方法有效地绕过了编译器将执行的编译时异常检查。 Constructor.newInstance 方法通过在(已检查的)InvocationTargetException中封装构造函数抛出的任何异常来避免此问题。

换句话说,它可以绕过已检查的异常系统。

13
这就是反射的一般本质...与Constructor.newInstance()没有任何特定关系。 - Ryan Delucchi
30
@Ryan:那不是真的;所有其他基于反射的调用方法都会抛出一个叫做“InvocationTargetException”的已检查异常,它包装了被调用方法抛出的任何throwable。 Class.newInstance 不会这样做 --- 它会直接抛出已检查异常。缺点是 javac 也不允许你尝试捕获这些异常,因为 Class.newInstance 没有声明抛出它们。 - C. K. Young

22

另一个原因:

现代IDE允许您查找类的用法-这在重构期间很有帮助,如果您和您的IDE知道使用要更改的类的代码。

当您不显式使用构造函数,而是使用Class.newInstance()时,您可能会冒着在重构期间无法找到该使用情况的风险,并且在编译时不会表现出此问题。


23
使用反射时需要注意的一个常见陷阱。 - Ryan Delucchi

17
我不知道为什么没有人提供一个基于实例的简单例子来解释这个问题,例如相对于Constructor::newInstance,因为自从 Java 9 开始,Class::newInstance 已经被弃用了。
假设您有这个非常简单的类(它是坏的并不重要):
static class Foo {
    public Foo() throws IOException {
        throw new IOException();
    }
}

当您尝试通过反射创建它的实例时。首先使用 Class::newInstance

    Class<Foo> clazz = ...

    try {
        clazz.newInstance();
    } catch (InstantiationException e) {
        // handle 1
    } catch (IllegalAccessException e) {
        // handle 2
    }
调用此方法将导致引发 IOException - 问题在于您的代码未处理它,既不会被handle 1也不会被handle 2捕获。
相比之下,如果通过Constructor执行,则:
    Constructor<Foo> constructor = null;
    try {
        constructor = clazz.getConstructor();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    }

    try {
        Foo foo = constructor.newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        System.out.println("handle 3 called");
        e.printStackTrace();
    }

那么处理3将被调用,因此您将处理它。

实际上,Class::newInstance绕过了异常处理 - 这确实不是您想要的。


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