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



我一直在尝试理解使用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;
    }
}
A a = new A();

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

A a = (A) Class.forName("A").newInstance();
  • 查看是否已加载A
  • 必要时加载
  • 必要时进行初始化
  • 通过反射定位无args构造函数
  • 通过反射调用new运算符和无参数构造函数
  • 将结果类型转换为A

使用反射Class.forName("A").newInstance();不仅是一项成本高昂的操作(因为需要在运行时期间将正确的类加载程序委派给并加载该类),而且还会使代码更难调试,并失去类型安全的所有优势(这在编译期间发生)。

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

new运算符和newInstance方法的区别如下:

  • 通过传递构造函数所接受的任意数量的参数,new运算符可以与类的任何构造函数一起使用。newInstance方法要求在为其调用的类中不存在arg构造函数。如果你想在newInstance中使用构造函数,那么你需要为任何构造函数获取构造函数类的实例,然后调用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创建数千个实例时)

  • 所有的Java开发人员都应该知道这个新操作符,因为它是在初级阶段教授的基本概念,所以没有什么特别的东西可以学习。并非所有开发应用程序的开发人员都知道反射,因此对于使用newInstance方法开发代码的初学者来说,有一条学习曲线。

  • 您可以看到任何普通Java程序中都在使用新的运算符。newInstance在Java内部的多个位置使用,尤其是在服务器端,如加载和实例化servlet、applets、JNDI存根/骨架、JDBC数据库驱动程序。

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

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

  • 使用新运算符会在.class文件中生成相应的字节码。当使用newInstance时,由于对象创建是动态处理的,因此不会在类内为对象创建生成额外的字节代码。

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

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

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

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

因此A a = (A) Class.forName(“A”).newInstance()分解为:

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

  • Class.forName(“A”).newInstance()创建由该class对象表示的类的一个新实例,这样您就得到了一个类型a的实例。该类被实例化,就像是由一个带有空参数列表的新表达式实例化一样。如果该类尚未初始化,则会对其进行初始化。这实际上相当于一个新的a(),它返回a的一个新实例。

    重要信息:使用此方法可以有效地绕过编译器执行的编译时异常检查。

参考:

  • 类的Javadoc

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

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

在方法2中,您将强制VM使用反射和类加载器来生成Object。其本身已经是较慢的

相关内容

  • 没有找到相关文章

最新更新