类实例化
Java程序中,类可以被显示或者隐藏实例化。
显示实例化有4种方式
- new操作符
- 调用Class或者java.lang.reflect.Constructor对象的newInstance()方法
- 调用任何现有对象的clone()方法
- 调用java.io.ObjectInputStream类的getObject()方法反序列化
隐藏实例化有4种方式
- 保存命令行参数的String对象
- Java虚拟机装载每个类型,暗中会实例化Class对象表示这个类型
- Java虚拟机装载了在常量池包含CONSTANT_String_info入口的类的时候,它会创建新的String对象的实例来表示这些常量字符串。(这是常量池解析CONSTANT_String_info)的过程
- 通过创建执行包含字符串连接符的表达式产生对象”a”+”b”
public class Example7 {
public static void main(String[] args) {
if (args.length<2) {
return;
}
System.out.println(args[0]+args[1]);
}
}
class文件关键部分
0: aload_0
1: arraylength
2: iconst_2
3: if_icmpge 7
6: return
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: new #3 // class java/lang/StringBuilder
13: dup
14: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
17: aload_0
18: iconst_0
19: aaload
20: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: aload_0
24: iconst_1
25: aaload
26: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
29: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
32: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
35: return
默认生成StringBuilder对象,创建了String对象
当Java虚拟机创建一个类的新实例,无论是明确的还是隐含的,首先都需要在队中为对象的实例变量分配内存。所有在对象的类中和它的超类中申明的变量(包括隐藏的实例变量)都要分配内存。并立即把实例变量初始化为默认的初始值,这个类变量赋予默认的默认初始值有异曲同工之妙。
一旦虚拟机为新对象分配内存和为实例变量赋予默认值后,虚拟机会为实例变量赋予正确的初始值。一般而言有三种赋初始值的技术:
- 对象是clone(),虚拟机拷贝被克隆的实例变量值到新变量中去。
- 调用readObject()进行反序列化,虚拟机通过从输入流中读入的值来初始化那些非暂时性的实例变量。
- 如果都不是上述两种状况,虚拟机调用对象的实例初始化方法进行初始化。
Java的class文件中,将实例初始化方法称作为<init>
方法;源代码中每一个类的构造方法,都会产生一个<init>
方法。没有明确声明任何构造方法,将会产生一个默认的无参数的构造方法,相对应的class文件里面也会产生一个<init>
方法。
一个<init>
方法可能包含三种代码:调用另外一个<init>
方法,实现对任何实例变量的初始化,构造方法体的代码。我们分为三种方式讨论:
- 如果在构造方法里显示调用同一个类的另一个构造方法this调用,那么该构造方法就调用另外一个构造方法,不调用父类构造方法;
- 如果在构造方法里显示调用父类的构造方法,那么该构造方法就调用父类的构造方法;
- 如果在构造方法中未显示调用父类构造方法,也未显示调用本类的构造方法,就默认调用父类的无参构造方法。
举个例子:
public class Example8 extends Example9 {
public Example8() {
this(1);
System.out.println("Example8");
}
public Example8(int i) {
System.out.println("Example8(int)");
}
public static void main(String[] args) {
Example8 example8 = new Example8();
}
}
public class Example9 {
public Example9() {
System.out.println("Example9");
}
}
运行结果:
Example9
Example8(int)
Example8
可以看出调用顺序如下Example8()的this(1) 调用 Example8(int) , Example8(int)默认的super()调用 Example9()。我们可以查看下字节码就看的很清楚了:
Example8()
0: aload_0
1: iconst_1
2: invokespecial #1 // Method "<init>":(I)V
5: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #3 // String Example8
10: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: return
Example8(int)
0: aload_0
1: invokespecial #5 // Method Initialization/Example9."<init>":()V
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #6 // String Example8(int)
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: return
Example9()
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String Example9
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: return
main函数首先调用Example8()
0: new #7 // class Initialization/Example8
3: dup
4: invokespecial #8 // Method "<init>":()V
7: astore_1
8: return
这其实也说明了一个事实,构造函数第一行必须是this()或者super(),如果都没有,默认是super()。
还有一点值得说明的是<init>
方法不允许捕捉他们调用的<init>
方法抛出的异常,如果被调用的<init>
方法抛出异常终止,那么执行调用的<init>
方法也终止。