C\C++ 程序员从零开始学习Android - 个人学习笔记(六) - java基础 - 类和对象的初始化
2012-02-01 18:13 CreateLight 阅读(525) 评论(0) 收藏 举报1,定义一个类
class A
{
A(){ this(3); m = 5; i = 6;}
A(int k){i = k;}
void f(){ }
private int i;
private int j = 3;
static int m;
static int n = 6;
static final int k = 11;
static final int k2;
{
i = 7;
j = 8;
m = 6;
}
static
{
m = 9;
n = 10;
k2 = 15;
}
}
2,new一个实例对象
A a = new A();
初始化过程:
2.1 类加载和初始化
最先发生的是类加载。虚拟机的启动是通过引导类加载器创建一个初始类(由虚拟机具体实现决定)完成,紧接着虚拟机链接这个初始类,初始化并调用它的 public void main(String [])方法,之后的所有执行过程都是由对此方法的调用开始,当执行到A a = new A();的字节码时,就会加载类A:
虚拟机以类A的完全限定名(包名/类名)查找方法区,判断是否该类已经被加载过,如果已经被加载过,则不再加载。
如果没有被加载,就用某种依赖于实现的方式查找A.class(比如windows下的通过搜索特定环境变量(PATH)指定的路径下的所有文件),如果没找到,抛出一个异常ClassNotFoundException。
虚拟机找到A.class后,解析二进制的class文件,加载此类。
加载过程 - 暂略,后续补上。
对于加载的每个类,虚拟机都会在堆上(如同其他对象)自动生成一个Class类的实例对象,这个对象保存了被加载类的相关信息,这就是A.class以及a.getClass();所对应的class对象。
类加载的过程中,会先对所有的类域(静态变量)赋予相应类型的初始值。
类加载的最后一步会对类域(静态变量)进行初始化,通过两种方法进行初始化:
a, 静态变量初始化语句: static int n = 6;
b, 静态初始化块:
static
{
m = 9;
n = 10;
k2 = 15;
}
这两种类初始化的代码会被java编译器收集到一起,放到一个特殊的方法中,这个方法被称为<clinit>,通常的java代码无法访问这个方法,只能由虚拟机调用。如果不需要类域初始化(没有静态变量或者只有编译时静态常量),就不会生成<clinit>。
初始化一个类包含两个步骤:
a, 如果此类存在直接超类,并且还没有被初始化,那么先初始化直接超类。
b,如果存在一个类的<clinit>,则调用这个方法完成初始化。
<clinit>中初始化的顺序和初始化语句在类定义中出现的顺序一致。
所以在这一步,会依据以下的顺序进行类A的初始化:
a, 将静态域m和n都设置成默认值0。
b, 初始化super类,即Object类,通过Object的<clinit>方法进行。
c,调用本类的<clinit>,按声明顺序执行初始化语句,先执行static int n = 6; 然后执行
static
{
m = 9;
n = 10;
k2 = 15;
}
d,至此,完成了类的初始化,m = 9; n = 10; k2 = 15;
e,特别的,这里的static final int k = 11;定义了一个编译时常量,对于编译时常量,java虚拟机并不会为此生成对应的初始化代码,亦不会在方法区中保存其值(作为类变量),所有引用此常量的地方都会直接使用常量的int值。编译时常量不会导致<clinit>方法的产生。
2.2 对象创建及初始化
2.2.1 对象创建
显式的实例化一个类(创建类对象)有四种途径:
a, 使用new操作符
b, 调用Class或java.lang.reflect.Constructor对象的newInstance()方法
c,调用任何现有对象的clone方法。
d, 通过java.io.ObjectInputStream类的getObject()方法反序列化
除此之外,还有一些隐式的操作也会导致对象的创建 - 暂略。
2.2.1 对象初始化
无论 哪种创建对象的方法,虚拟机首先要在堆中为保存对象的域变量分配内存,所有在对象的类中和他的超类中定义的域变量都需要分配内存,内存分配好之后,虚拟机会立刻将域变量初始化为其默认值。在此之后,虚拟机会将域变量初始化为正确的值,根据创建对象的方式不同,会采取不同的初始化方法:
a, 如果是clone方法产生的对象,虚拟机将被克隆的对象中的域变量的值拷贝的新对象中。
b, 如果对象是通过一个ObjectInputStream的readObject()调用反序列化的,虚拟机通过从输入流中读入的值来初始化哪些非暂时性的域变量。
c, 否则虚拟机调用对象的域初始化方法进行初始化。
java编译器为它编译的每一个类至少生成一个域初始化方法,在class文件中,这个方法被称之为<init>。对于源代码中的每个构造器方法,编译器都会产生一个对应的<init>方法;如果没有显示的声明构造器方法,编译器会生成一个默认的构造器方法,它仅调用超类的无参构造器方法,和其他构造器方法一样,在class中也会生成一个对应的<init>方法。
<init>方法中,存在三种代码,执行顺序如下:
a, 对另一个<init>方法的引用。如果对应构造器方法的第一句是this()则调用同类的指定的<init>方法;是super(),则调用super类指定的<init>方法;否则,调用super类的无参构造器所对应的<init>方法。除了Object类,所有类的<init>方法都以对另一个<init>方法的调用开始。即初始化子类之前必先初始化其直接super类。
b,对象域变量的初始化代码,类似private int j = 3; 以及初始化块中的代码。调用的顺序和在类中定义的顺序一致。
c,对应构造器方法中的代码。
3,总结:
使用new操作符创建一个对象时,初始化顺序如下:
1,类初始化,对于每个类只进行一次。
a, 将静态域变量设置为对应类型的默认值。
b, 初始化该类的所有super类。
c, 调用<clinit>方法进行初始化,包括静态域初始化语句和静态初始化块,初始化的顺序和类中声明的顺序一致。和<init>不同,<clinit>中并不会引用其他类(包括super类)的<clinit> 方法。
d, 在每个类或接口首次主动使用时会进行初始化,包括且只包括以下几种情景:
c.1 创建某个类的实例对象时。(new、clone、反射、反序列化等)
c.2 调用某个类的静态方法时。
c.3 使用某个类的静态域变量时。final静态域变量除外,它会作为编译时常量处理。
c.4 调用某些反射方法时。
c.5 初始化某个类的子类时(某个类初始化时,要求它的super类必须先被初始化)。
c.6 当虚拟机启动时,某个被标为启动类的类(即包含main方法的那个类)。
2,对象初始化
a, 初始化所有super类。
b, 初始化类的域变量,包括域变量初始化语句和初始化块,初始化的顺序和类中声明的顺序一致。
c, 执行构造器方法体。
浙公网安备 33010602011771号