Java中的“new”做什么做了类加载器?
我不能在JLS / JVMSpec中找到它,也不能在SO中找到它。 我肯定一定会被问到……
那么,“新”究竟做了什么呢? 假设我们在A中实例化一个B类:
class A { // ... new B(); // ... }
这相当于
class A { // ... A.class.getClassLoader().loadClass("B's canonical name").newInstance(); // ... }
?
它是,还是在每个环境中都不像那样?
如果您能指出JLS / JVMSpec中的相应章节,我将不胜感激。 谢谢!
编辑:当然我们不能在loadClass()
调用中调用B.class.getCanonicalName()
,因为B尚未加载。 JVM必须根据import语句解析名称。
这相当于
class A { // ... A.class.getClassLoader().loadClass("B's canonical name").newInstance(); // ... }
?
不,不总是。
对于给定的命名空间,类加载仅执行一次,除非先前已卸载了相关的Class
。 因此,在大多数情况下,等效表达式A.class.getClassLoader().loadClass("B's canonical name")
将仅执行一次。 换句话说,如果你有两个表达式 – new A()
, loadClass
只会执行一次。
调用构造函数的调用被JVM视为方法调用,但这需要Java编译器的协作。 JVM和编译器必须遵守Java虚拟机规范的第3.9节,其中规定:
3.9特殊命名的初始化方法
在Java虚拟机的级别上,每个构造函数(第2.12节)都显示为具有特殊名称
的实例初始化方法。 该名称由编译器提供。 因为名称
不是有效的标识符,所以它不能直接用在用Java编程语言编写的程序中。 实例初始化方法可以仅通过invokespecial指令在Java虚拟机内调用,并且可以仅在未初始化的类实例上调用它们。 实例初始化方法接受从其派生的构造函数的访问权限(第2.7.4节)。
类或接口最多只有一个类或接口初始化方法,并通过调用该方法进行初始化(第2.17.4节)。 类或接口的初始化方法是静态的,不带参数。 它的特殊名称是
。 该名称由编译器提供。 因为名称
不是有效的标识符,所以它不能直接用在用Java编程语言编写的程序中。 Java虚拟机隐式调用类和接口初始化方法; 它们永远不会直接从任何Java虚拟机指令调用,但只能作为类初始化过程的一部分间接调用。
本节假定当前线程可以使用与所讨论的Class
相关的Class
对象。 一旦Class
对象可用,将调用与具有正确参数集的构造函数对应的方法
。
如果尚未加载类,将使用哪个类加载器加载类的问题有点不同,与new关键字无关。 它取决于一个类如何引用另一个类,即需要在运行时常量池中解析符号引用? 此上下文中的行为在Java虚拟机规范的第5.3节中定义:
5.3创建和加载
创建由名称N表示的类或接口C由Java虚拟机的方法区域中的构造(第3.5.4节)构成C的特定于实现的内部表示。类或接口创建由另一个类触发或接口D,它通过运行时常量池引用C.
…
Java虚拟机使用以下三个过程之一来创建由N表示的类或接口C:
如果N表示非arrays类或接口,则使用以下两种方法之一来加载,从而创建C:
如果D由引导类加载器定义,则引导类加载器启动加载C(第5.3.1节)。
如果D是由用户定义的类加载器定义的,那么同一个用户定义的类加载器会启动C的加载(第5.3.2节)。
否则N表示数组类。 数组类由Java虚拟机(第5.3.3节)直接创建,而不是由类加载器创建。 但是,D的定义类加载器用于创建数组类C的过程中。
注意句子 – If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of C
在上面的引号中If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of C
。 在表达式new A()
的上下文中,加载封闭类的类加载器将负责根据VM Spec加载A
这当然是假设引导类加载器没有加载封闭类。
跟进我的评论,像一条线
new A()
翻译成
0: new #2; //class A 3: dup 4: invokespecial #3; //Method A."":()V 7: pop
堆栈跟踪是:
[1] java.net.URLClassLoader$1.run (URLClassLoader.java:202) [2] java.security.AccessController.doPrivileged (native method) [3] java.net.URLClassLoader.findClass (URLClassLoader.java:190) [4] sun.misc.Launcher$ExtClassLoader.findClass (Launcher.java:229) [5] java.lang.ClassLoader.loadClass (ClassLoader.java:307) [6] java.lang.ClassLoader.loadClass (ClassLoader.java:296) [7] sun.misc.Launcher$AppClassLoader.loadClass (Launcher.java:301) [8] java.lang.ClassLoader.loadClass (ClassLoader.java:248) [9] Loader.main (Loader.java:11)
所以我猜你的猜测非常接近。
我发现这个链接主要解释了Java中“new”运算符的概念。 我的主要想法是这句话:
“(..)new运算符通过为新对象分配内存并返回对该内存的引用来实例化一个类.new运算符也调用对象构造函数。(..)”
我认为应该考虑三件事:
- “新”运算符在每个环境中都很常见,但由于开发人员需要它。 在Java的情况下,“new”运算符还为对象分配内存空间。
- 有时(在较旧的编译器中)它不可用,并且所有声明都必须是“漫长的道路”。 因此,为了兼容复古,两个语句都是等价的。
- 总是有这种情况你可能需要覆盖“loadClass”或“getClassLoader()”
“(..)即使Java的新关键字是该语言的核心,也可能有更好的方法来完成工作。(..)”
希望能帮助到你 ;)