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

20
我一直在尝试理解使用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;
    }
}

2
我猜它按名称查找类是一项昂贵的操作。编译器对该类一无所知,因此无法进行任何优化。 - Ted Hopp
另外,您可能需要对实例进行某些操作,以确保编译器不会将其优化掉。 - sje397
1
它是反射,它允许通过名称创建类的新实例。它也比较慢。它加载类并初始化其静态部分,因此由于副作用而用于加载驱动程序。 - Ivan Ivanov
很可能method1没有try-catch块会更快,因为这并不是必需的。 - PeterMmm
我一直在研究Java中以不同方式完成任务的性能。我的第一个研究目标是深入分析各种Java特性的速度问题和特性架构,@Chris Hayes。谢谢。 - karimvai
显示剩余5条评论
5个回答

25
A a = new A();

直接调用A的构造函数和new操作符,其中A在源代码中被提及并已经被加载和初始化。

A a = (A) Class.forName("A").newInstance();
  • 查看是否已经加载A
  • 如果必要,加载它
  • 如果必要,初始化它
  • 通过反射找到无参构造函数
  • 通过反射调用new操作符和无参构造函数
  • 将结果强制类型转换为A

8
执行new A()时不能假设类A已经被加载和初始化。相反,当第一次使用A时,可能恰好是加载和初始化A的时候。典型的JVM(包括Oracle的JVM)在类加载时是懒惰的。 - Holger
1
@Bakuriu,除非编译器能够以某种方式知道它始终产生相同的结果,否则它不是常量表达式,因为它不需要这样做。 - user207421
1
@EJP那么编译器如何避免在new A()中进行类查找呢?如果与A相关联的类可以在运行时更改,则即使使用其他语法,编译器每次都必须查找它。如果类不会更改,则编译器可以通过仅使用A(我的意思是对类对象的引用,从而避免按名称查找)来优化掉Class.forName(“A”) - Bakuriu
2
这个小代码片段展示了Holger所说的:类可以按需加载。 - nhahtdh
1
@Bakuriu:对于编译器而言,Class.forName 可能会增加计数器并返回一个随机类。 - njzk2
显示剩余8条评论

5
使用反射 Class.forName("A").newInstance(); 不仅是一种昂贵的操作(因为需要在运行时委托正确的类加载器并加载类),而且使您的代码更难调试,并且失去了编译期间的类型安全性优势。 结论:
除非必须使用反射(例如,如果您正在编写面向方面的插件/库),否则请避免使用它。

4

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

  • new operator can be used with any constructor of the class by passing any number of arguments as accepted by the constructor. newInstance method requires the presence of no-arg constructor in the class for which it has been invoked. If you want to use a constructor with newInstance, then you need to get an instance of Constructor class for any constructor and then invoke newInstance like:

    Class class = Class.forName("A");  
    Constructor const = class.getConstructor(A.class);  
    A a = null;  
    const.newInstance(a);
  • Using new operator doesn’t require explicit class loading as it is internally handled by the JVM. For newInstance() method an instance of that class’s Class object is required (Class.forName(“A”).newInstance(); or as shown in above point). The Class object referring to the underlying class is obtained by invoking the forName method.

  • The use of new operator is recommended when the name of class is known at compile time. Since newInstance uses reflection to create an object of class, it is recommended to be used when the class is not known at compile time but is determined at run time.

  • Since there is no extra processing related to method invocation like forName in new operator, it is faster than newInstance. The use of newInstance results in extra processing on part of JVM (type checks, security checks) and hence is not recommended to be used for performance degradation reasons.(at least when thousands of instances are being created using newInstance)

  • All Java developers are supposed to know the new operator as it is basic concept which is taught at beginner level, so there is nothing special to learn about it. Not all developers working on a application be aware of reflection and hence there is a learning curve for beginners working on the code with newInstance method.

  • You can see new operator being used in any normal Java program. newInstance is being used at multiple places inside Java especially in server side like loading and instantiating servlets, applets, JNDI stub/skeletons, JDBC database drivers.

  • With new operator, the class loading and object creation is done by the default class loader of JDK.But with newInstance method, one can explicitly specify the class loader to be used for loading class and object instantiation.

  • There are very less chances for runtime exception with new operator. Only rare case is when the class was present during compile time but was not available on classpath during runtime. The use of newInstance method with Class.forName(String …) can result in runtime exception even if the class name passed as argument to forName method is invalid.

  • The use of new operator results in generation of corresponding byte code in the .class file. When newInstance is used, there is no extra byte code generated for object creation inside the class as object creation is handled dynamically.

  • With new operator there is inherent type checking and compiler error is shown if the class doesn’t exist. Since the class name is passed as argument to Class.forName method as string, there is no compile type checking and usually results in run time exception as described in one of the earlier points.

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

本文比较了Java中使用new运算符和Class.forName().newInstance()创建对象的不同方式。使用new运算符可以在编译时确定要创建的对象类型,而Class.forName().newInstance()则在运行时动态地加载类并创建对象。此外,前者只能调用public构造函数,后者可以调用所有可访问的构造函数。对于大多数情况下,使用new运算符即可,但如果需要在运行时根据用户输入或配置文件动态地创建不同类型的对象,则可以使用后者。

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

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

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

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

  • Class.forName(“A”).newInstance() 创建由此Class对象表示的类的新实例,因此您将获得类型为A的实例。如果尚未初始化,则会初始化该类。这实际上相当于新的A(),并返回A的新实例。

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

参考资料:


-2

你用来衡量速度的测试不是一个有效的测试。Java性能非常复杂,首先涉及到热点VM、智能编译器和垃圾收集器。

在方法1中,Java通常足够聪明,只需在内存中创建1个A实例并重复使用它即可。

在方法2中,你强制VM使用反射和类加载器来创建对象。这本身就会更慢。


1
这是完全错误的。即使在 method1() 中,Java 也会创建一个新实例。 - Uwe Plonus
1
也许我没有表达清楚,当然它会创建一个新实例,但虚拟机不必为其分配新内存。它足够智能,可以重复使用先前的分配。 - Peter
3
即使如此,Java也不会重复使用内存,因为这是由垃圾回收器完成的工作。垃圾回收逻辑本身已经够复杂了,不需要进行这样的“优化”。 - Uwe Plonus
Java在这两种情况下同样有可能重复使用相同的内存。它达到了需要N字节的某个点,无论上次是否存在相同的内存,都会使用它们。这不是两种情况之间的区别。 - user207421

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