1. 构造器解析
只要学过Java的人都知道, JVM在创建一个对象的时候会通过构造函数来决定这个类要如何构造, 构造函数在Java中是非常特殊的一类"方法", 通过这篇文章我们来探索一下构造器的底层实现.
还是以Tree.java为例, 分析一下Tree的字节码
public class Tree {
public static int state = 1;
int height;
public Tree(){
height = 0;
}
public Tree(int initialHeight){
height = initialHeight;
}
public static void message(String message){
return;
}
}
我定义了一个静态字段外加一个静态方法, 之后可以顺带分析一下带static标识的字段和方法是如何构造的. 现在先来看Tree的两个构造器:
public Dao.Tree();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: iconst_0
6: putfield #2 // Field height:I
9: return
LineNumberTable:
line 8: 0
line 9: 4
line 10: 9
public Dao.Tree(int);
descriptor: (I)V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: iload_1
6: putfield #2 // Field height:I
9: return
LineNumberTable:
line 12: 0
line 13: 4
line 14: 9
从字节码中我们看出, 无论是怎么样的构造器, 在一开始都会调用
public class Tree {
public static int state = 1;
int height;
public Tree(int initialHeight){
height = initialHeight;
}
public static void message(String message){
return;
}
}
构造器的字节码为:
public Dao.Tree(int);
descriptor: (I)V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: iload_1
6: putfield #2 // Field height:I
9: return
LineNumberTable:
line 8: 0
line 9: 4
line 10: 9
非常明显的, 这个类里面没有无参构造器, 但是却有
我们不妨举个例子, 对于下面这个类:
public class Tree {
int height = 3;
public Tree(){
}
public Tree(String message){
System.out.println(message);
}
}
它在JVM里面的表示应该就是:
public class Tree {
int height;
public Tree(){
height = 3;
}
public Tree(String message){
height = 3;
System.out.println(message);
}
}
这样就非常清楚了,
这里插几句, 关于构造器到底是个什么东西, <
2. 静态初始化解析
一旦理解了
public static void message(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 17: 0
static {};
descriptor: ()V
flags: (0x0008) ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: iconst_1
1: putstatic #3 // Field state:I
4: return
LineNumberTable:
line 5: 0
这里的static {}就是
初始化内容 | 普通成员变量赋值以及{}代码块 | 静态变量赋值以及static {}代码块 |
调用时机 | 对象构建时 | 类加载时(初始化) |
继承 | 可以继承 | 不能继承 |
可以看出,
Class.forName("com.mysql.jdbc.Driver");
Driver就自己加载了, 这是为什么呢? 其实非常简单, com.mysql.jdbc.Driver这个类一共就一个构造方法再加一个下面的静态代码段, 在forName的时候这段代码就自动被加载进JVM, 完成了driver的注册:
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
挺有意思的吧?
3. 构造器的继承解析
在构造器的继承中,
而对于
举个例子, 帮助你彻底理清新建对象时候的初始化问题:
public class Father {
{
System.out.println("father.{}");
}
static {
System.out.println("father.static{}");
}
public Father(){
System.out.println("father.constructor()");
}
}
public class Son extends Father{
{
System.out.println("Son.{}");
}
static {
System.out.println("Son.static{}");
}
public Son(){
System.out.println("Son.constructor");
}
public static void main(String[] args) {
Son son = new Son();
}
}
执行结果为:
father.static{}
Son.static{}
father.{}
father.constructor()
Son.{}
Son.constructor
从这个结果来看, 当新建一个对象时, 总是先加载父类再加载子类, 并且执行了他们的静态初始化过程, 也就是
从这个结果中我们还能看出, {}代码段是在构造器的输出之前执行的, 也间接证明了