我无法在JLS / JVMSpec或SO中轻松找到它。我确定一定有人问过…
那么,“新”实际上是做什么的呢?假设我们在A中实例化一个B类:
class A { // ... new B(); // ... }
这等于
class A { // ... A.class.getClassLoader().loadClass("B's canonical name").newInstance(); // ... }
?
是不是还是在每种环境下都无法正常工作?
如果您能将我引到JLS / JVMSpec中的相应章节,将不胜感激。谢谢!
编辑:我们肯定不能叫B.class.getCanonicalName()在loadClass()打电话,因为B的尚未加载。JVM必须根据导入语句来解析名称。
B.class.getCanonicalName()
loadClass()
这等于 class A { // ... A.class.getClassLoader().loadClass("B's canonical name”).newInstance(); // … } ?
class A { // ... A.class.getClassLoader().loadClass("B's canonical
name”).newInstance(); // … }
不,并非总是如此。
对于给定的名称空间,类加载仅执行一次,除非Class之前已卸载了该问题。因此,A.class.getClassLoader().loadClass("B's canonical name")在大多数情况下,等效表达式将仅执行一次。换句话说,如果您有两个表达式- new A(),loadClass则将仅执行一次。
Class
A.class.getClassLoader().loadClass("B's canonical name")
new A()
loadClass
JVM将构造函数的调用视为方法调用,但这需要Java编译器的配合。JVM和编译器必须遵守Java虚拟机规范的3.9节,其中规定:
3.9特别命名的初始化方法 在Java虚拟机级别,每个构造函数(第2.12节)都作为具有特殊名称的实例初始化方法出现 <init>。该名称由编译器提供。因为该名称<init> 不是有效的标识符,所以不能直接用Java编程语言编写的程序中使用它。实例初始化方法只能由 invokespecial 指令在Java虚拟机内调用 ,并且只能在未初始化的类实例上调用。实例初始化方法具有从其获得构造函数的访问权限(第2.7.4节)。 一个类或接口最多具有一个类或接口初始化方法,并通过调用该方法进行初始化(第2.17.4节)。类或接口的初始化方法是静态的,不带参数。它有特殊的名字<clinit>。该名称由编译器提供。因为该名称<clinit>不是有效的标识符,所以不能直接用Java编程语言编写的程序中使用它。Java虚拟机隐式调用类和接口初始化方法。绝对不能从任何Java虚拟机指令直接调用它们,而只能在类初始化过程中间接调用它们。
3.9特别命名的初始化方法
在Java虚拟机级别,每个构造函数(第2.12节)都作为具有特殊名称的实例初始化方法出现 <init>。该名称由编译器提供。因为该名称<init> 不是有效的标识符,所以不能直接用Java编程语言编写的程序中使用它。实例初始化方法只能由 invokespecial 指令在Java虚拟机内调用 ,并且只能在未初始化的类实例上调用。实例初始化方法具有从其获得构造函数的访问权限(第2.7.4节)。
<init>
一个类或接口最多具有一个类或接口初始化方法,并通过调用该方法进行初始化(第2.17.4节)。类或接口的初始化方法是静态的,不带参数。它有特殊的名字<clinit>。该名称由编译器提供。因为该名称<clinit>不是有效的标识符,所以不能直接用Java编程语言编写的程序中使用它。Java虚拟机隐式调用类和接口初始化方法。绝对不能从任何Java虚拟机指令直接调用它们,而只能在类初始化过程中间接调用它们。
<clinit>
本节假定Class与该类有关的对象可用于当前线程。一旦Class对象是可用的,该方法<init>对应于与右组参数的构造,将被调用。
使用哪个类加载器加载类(如果尚未加载)的问题有些不同,并且与new关键字无关。它取决于一个类如何引用另一个类,即是否需要在运行时常量池中解析符号引用?Java虚拟机规范的第5.3节定义了这种情况下的行为:
5.3创建和加载 用名称N表示的类或接口C的创建包括Java虚拟机的方法区域(第3.5.4节)中特定于实现的内部C表示的构造。类或接口的创建由另一个类触发或接口D,它通过其运行时常量池引用C。 … Java虚拟机使用以下三个过程之一来创建由N表示的类或接口C: 如果N表示非数组类或接口,则使用以下两种方法之一来加载并创建C: 如果D由引导类加载器定义,则引导类加载器将启动C的加载(第5.3.1节)。 如果D是由用户定义的类加载器定义的,则该用户定义的类加载器将启动C的加载(第5.3.2节)。 否则,N表示数组类。数组类是由Java虚拟机(第5.3.3节)而不是由类加载器直接创建的。但是,在创建数组类C的过程中使用D的定义类加载器。
5.3创建和加载
用名称N表示的类或接口C的创建包括Java虚拟机的方法区域(第3.5.4节)中特定于实现的内部C表示的构造。类或接口的创建由另一个类触发或接口D,它通过其运行时常量池引用C。
…
Java虚拟机使用以下三个过程之一来创建由N表示的类或接口C:
如果N表示非数组类或接口,则使用以下两种方法之一来加载并创建C:
如果D由引导类加载器定义,则引导类加载器将启动C的加载(第5.3.1节)。
如果D是由用户定义的类加载器定义的,则该用户定义的类加载器将启动C的加载(第5.3.2节)。
否则,N表示数组类。数组类是由Java虚拟机(第5.3.3节)而不是由类加载器直接创建的。但是,在创建数组类C的过程中使用D的定义类加载器。
注意If D was defined by a user-defined class loader, then that same user- defined class loader initiates loading of C上面引用中的句子。在表达式的上下文中new A(),加载了封闭类的类加载器将A根据VM Spec 进行加载;当然,假设引导类加载器未加载该封闭类。
If D was defined by a user-defined class loader, then that same user- defined class loader initiates loading of C
A