个人小结:
一个Java对象的创建过程往往包括 类初始化 和 类实例化 两个阶段。特别需要指出的是,类的实例化与类的初始化是两个完全不同的概念:
-
类的实例化是指创建一个类的实例(对象)的过程;
-
类的初始化是指为类中各个类成员(被static修饰的成员变量)赋初始值的过程,是类生命周期中的一个阶段。
1. 一个Java对象的创建过程往往包括两个阶段:类初始化阶段 和 类实例化阶段。实例初始化不一定要在类初始化结束之后才开始初始化,实例初始化早于类初始化,先对实例初始化,实例初始化执行的是实例化的代码,如:实例变量的赋值,代码块,构造函数。然后按顺序执行静态代码块,进行类的初始化。
static StaticTest
st =
new StaticTest();//引发实例初始化过程
static {
//静态代码块
System.
out.println(
"1");
}
{
// 实例代码块
System.
out.println(
"2");
}
2. 创建一个对象常常需要经历如下几个过程:父类的类构造器<clinit>() -> 子类的类构造器<clinit>() -> 父类的成员变量和实例代码块 -> 父类的构造函数 -> 子类的成员变量和实例代码块 -> 子类的构造函数。
3. 静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,
在前面的静态语句块可以赋值,但是不能访问。
4. 注意子类和父类之间存在的方法重写,也就是多态的发生。
5. 主动引用情况:
1) 遇到new、getstatic、putstatic或invokestatic这四条字节码指令(注意,newarray指令触发的只是数组类型本身的初始化,而不会导致其相关类型的初始化,比如,new String[]只会直接触发String[]类的初始化,也就是触发对类[Ljava.lang.String的初始化,而直接不会触发String类的初始化)时,如果类没有进行过初始化,则需要先对其进行初始化。生成这四条指令的最常见的Java代码场景是:
使用new关键字实例化对象的时候;
读取或设置一个类的静态字段(
被final修饰,已在编译器把结果放入常量池的静态字段除外)的时候;
调用一个类的静态方法的时候。
2) 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
3) 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
4) 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
5) 当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化。
被动引用情况:
1)、通过子类引用父类的静态字段,不会导致子类初始化
2)、通过数组定义来引用类,不会触发此类的初始化
3)、常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
Java对象初始化的过程:
代码块,实例变量的初始化,构造函数赋值。
1,静态代码块,静态变量的赋值,按出现的顺序运行
2,实例变量的赋值,代码块的初始化,按出现的顺序运行
3,构造函数初始化
含继承关系的对象实例化的顺序:
父类的类构造器<clinit>() -> 子类的类构造器<clinit>() -> 父类的成员变量和实例代码块 -> 父类的构造函数 -> 子类的成员变量和实例代码块 -> 子类的构造函数。
类初始化和实例初始化的过程:
类加载的过程:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using) 和 卸载(Unloading)七个阶段
(1) 通过子类引用父类的静态字段,不会导致子类初始化,但是其父类会被加载,但是不会创建实例对象,只会加载类。
对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。在本例中,由于value字段是在类SClass中定义的,因此该类会被初始化;此外,在初始化类SClass时,虚拟机会发现其父类SSClass还未被初始化,因此虚拟机将先初始化父类SSClass,然后初始化子类SClass,而SubClass始终不会被初始化。
(2) 通过数组定义来引用类,不会触发此类的初始化
(3) 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
类加载过程:
1、加载(Loading)
2、验证(Verification)
3、准备(Preparation)
4、解析(Resolution)
准备阶段是正式为类变量(static 成员变量)分配内存并设置类变量初始值(零值)的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在堆中。
5、初始化(Initialization)
初始化阶段是执行类构造器<clinit>()方法的过程。<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,
静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。
6、使用
7、卸载
典型案例分析:
在Java中, 创建一个对象常常需要经历如下几个过程:父类的类构造器<clinit>() -> 子类的类构造器<clinit>() -> 父类的成员变量和实例代码块 -> 父类的构造函数 -> 子类的成员变量和实例代码块 -> 子类的构造函数。
实例初始化不一定要在类初始化结束之后才开始初始化,实例初始化早于类初始化,先对实例初始化,然后按顺序执行静态代码块。
如果类变量是final的,那么编译器在编译时就会为value生成ConstantValue属性,并在准备阶段虚拟机就会根据ConstantValue的设置将变量设置为指定的值。
package Day01;
public
class StaticTest {
public
static
void main(String[]
args) {
staticFunction();
}
static StaticTest
st =
new StaticTest();//引发实例初始化过程
static {
//静态代码块
System.
out.println(
"1");
}
{
// 实例代码块
System.
out.println(
"2");
}
StaticTest() {
// 实例构造器
System.
out.println(
"3");
System.
out.println(
"a=" +
a +
",b=" +
b);
}
public
static
void staticFunction() {
// 静态方法
System.
out.println(
"4");
}
int
a = 110;
// 实例变量
static
int
b = 112;
// 静态变量
static StaticTest
st1 =
new StaticTest();
}
/* Output:
2
3
a=110,b=0
1
2
3
a=110,b=112
4
*///:~
class Foo {
int
i = 1;
Foo() {
System.
out.println(
i);
int
x = getValue();
System.
out.println(
x);
}
{
i = 2;
}
protected
int getValue() {
return
i;
}
}
//子类
class Bar
extends Foo {
int
j = 1;
Bar() {
j = 2;
}
{
j = 3;
}
@Override
protected
int getValue() {
return
j;
}
}
public
class ConstructorExample {
public
static
void main(String...
args) {
Bar
bar =
new Bar();
System.
out.println(
bar.getValue());
}
}
由于Bar重载了Foo中的getValue方法,所以根据Java的多态特性可以知道,其调用的getValue方法是被Bar重载的那个getValue方法。
public
class
InitionliseDemo
extends supclass {
public
static
void main(String[]
args) {
subclass
sub =
new subclass();
}
}
class supclass {
static {
System.
out.println(
"supclass Init");
}
public
static String
s =
"hello";
public
static supclass
sup=
new supclass();
public
int
a = 10;
{
System.
out.println(
s);
System.
out.println(
a);
a = 100;
}
public supclass() {
System.
out.println(
"父类的构造函数");
}
}
class subclass
extends supclass {
static {
System.
out.println(
"subclass init");
s =
"java";
System.
out.println(
s);
}
public
static subclass
i =
new subclass();
public
int
b=9;
{
System.
out.println(
b);
}
public subclass() {
System.
out.println(
"子类的构造函数");
}
}
supclass Init
hello
10
父类的构造函数
subclass init
java
java
10
父类的构造函数
9
子类的构造函数
java
10
父类的构造函数
9
子类的构造函数
class SSClass {
static {
System.
out.println(
"SSClass");
}
}
class SClass
extends SSClass {
static {
System.
out.println(
"SClass init!");
}
public
static
final
int
value = 123;
public SClass() {
System.
out.println(
"init SClass");
}
}
class SubClass
extends SClass {
static {
System.
out.println(
"SubClass init");
}
static
int
a;
public SubClass() {
System.
out.println(
"init SubClass");
}
}
public
class NotInitialization {
public
static
void main(String[]
args) {
System.
out.println(SubClass.
value);
}
}
/*
* Output: SSClass SClass
init
! 123
*/// :~
123
参考: