我一直在尝试理解使用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/
传统new
和newInstance
之间的主要区别在于,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。其本身已经是较慢的