调用类构造函数和使用Class.forName()。newInstance之间的区别

时间:2014-09-22 05:52:56

标签: java reflection

我一直在尝试理解使用new实例化对象与使用Class.forName("A").newInstance();之间的区别。

我为一个简单的类A运行了以下代码,其显示使用Class.forname("A").newInstance()比仅使用new A()慢70-100倍。

我很想知道为什么会有这么大的时间差异,但无法弄清楚。请有人帮我理解原因。

public class Main4test {

    public Main4test() {
    }

    static int turns = 9999999;

    public static void main(String[] args) {
        new Main4test().run();
    }

    public void run() {
        System.out.println("method1: " + method1() + "");
        System.out.println("method2:" + method2() + "");
    }

    public long method2() {
        long t = System.currentTimeMillis();
        for (int i = 0; i < turns; i++) {
            try {
                A a = (A) Class.forName("A").newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return System.currentTimeMillis() - t;
    }

    public long method1() {
        long t = System.currentTimeMillis();
        for (int i = 0; i < turns; i++) {
            try {
                A a = new A();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return System.currentTimeMillis() - t;
    }

}

public class A {
    int a;
    public A() {
    a=0;
    }
}

5 个答案:

答案 0 :(得分:25)

A a = new A();

直接调用new运算符和A的构造函数,其中源文本中提到A,因此已加载并初始化。

A a = (A) Class.forName("A").newInstance();
  • 查看是否已加载A
  • 必要时加载
  • 必要时初始化
  • 通过反射找到no-args构造函数
  • 通过反射
  • 调用new运算符和no-args构造函数
  • 将结果强制转换为A

答案 1 :(得分:5)

使用反射Class.forName("A").newInstance();不仅是一项代价高昂的操作(因为正确的类加载器需要在运行时期间委派给并加载类),但也会使代码更加困难调试并失去了类型安全的所有优点(在编译期间发生)。

结论:
除非必须使用它(例如,如果您正在编写面向方面的插件/库),否则请避免反射

答案 2 :(得分:4)

下面列出了new运算符和newInstance方法之间的差异:

  • new运算符可以通过传递构造函数接受的任意数量的参数与该类的任何构造函数一起使用。 newInstance方法要求在已调用它的类中存在no-arg构造函数。如果你想使用带有newInstance的构造函数,那么你需要为任何构造函数获取一个Constructor类的实例,然后调用newInstance,如:

    Class class = Class.forName("A");  
    Constructor const = class.getConstructor(A.class);  
    A a = null;  
    const.newInstance(a);
  • 使用new运算符不需要显式类加载,因为它由JVM内部处理。 对于newInstance()方法,需要该类的Class对象的实例(Class.forName(“A”)。newInstance();或者如上所示)。引用底层类的Class对象是通过调用forName方法获得的。

  • 当在编译时知道类的名称时,建议使用new运算符。 由于newInstance使用反射来创建类的对象,因此建议在编译时不知道类但在运行时确定类时使用。

  • 由于在new运算符中没有与forName等方法调用相关的额外处理,因此它比newInstance更快。 使用newInstance会导致部分JVM(类型检查,安全检查)的额外处理,因此不建议将其用于性能下降的原因。(至少在使用newInstance创建数千个实例时)

    < / LI>
  • 所有Java开发人员都应该知道新操作符,因为它是初学者级别的基本概念,所以没有什么特别的东西可以学习它。 并非所有在应用程序上工作的开发人员都知道反射,因此初学者使用newInstance方法处理代码时会有一个学习曲线。

  • 您可以在任何普通的Java程序中看到新的运算符。 newInstance正在Java中的多个地方使用,特别是在服务器端,如加载和实例化servlet,applet,JNDI存根/骨架,JDBC数据库驱动程序。

  • 使用new运算符,类加载和对象创建由JDK的默认类加载器完成。但是使用newInstance方法,可以显式指定用于加载类和对象实例化的类加载器。

  • 使用new运算符的运行时异常的可能性非常小。只有极少数情况是在编译期间类存在但在运行时在类路径中不可用。 即使作为forName方法的参数传递的类名无效,newInstance方法与Class.forName(String ...)的使用也可能导致运行时异常。

  • 使用new运算符会导致在.class文件中生成相应的字节代码。 当使用newInstance时,没有为类中的对象创建生成额外的字节代码,因为对象创建是动态处理的。

  • 对于new运算符,存在固有类型检查,如果该类不存在,则会显示编译器错误。 由于类名作为参数传递给Class.forName方法作为字符串,因此没有编译类型检查,并且通常导致运行时异常,如前面的一点所述。

参考:http://www.javaexperience.com/difference-between-new-operator-and-class-forname-newinstance/

答案 3 :(得分:2)

传统newnewInstance之间的主要区别在于newInstance允许灵活地实例化您在运行时不知道的类,并使您的代码更具动态性。如果直到运行时才知道该类,那么应该使用反射这是一个有效的情况。

Javadoc,调用Class.forName(String)返回与具有给定字符串名称的类或接口关联的Class对象,即返回A类

所以A a = (A) Class.forName(“A”).newInstance()分解为:

  • Class.forName(“A”)
    返回Class类的A类。

  • Class.forName(“A”).newInstance() 创建由此Class对象表示的类的新实例,以便获得类型A的实例。该类实例化为具有空参数列表的新表达式。如果尚未初始化,则初始化该类。这实际上相当于一个新的A()并返回一个新的实例。

    重要:使用此方法可以有效地绕过编译时异常检查,否则将由编译器执行。

参考:

答案 4 :(得分:-2)

用于测量速度的测试不是有效测试。 Java性能非常复杂,它涉及热点vm,智能编译器和垃圾收集器。

在方法1中,java通常足够聪明,只能在内存中创建一个A实例,并在每次迭代时重用它

在方法2中,您强制VM使用反射和类加载器来创建Object。 本身已经慢了