Java 类中变量的初始化,类内部变量/代码块的加载顺序
@
目录
- 1. 变量的初始化方法
- 1.1 直接定义字段时赋值 or 显示字段初始化
- 1.2 调用方法进行初始化
- 1.2.1 调用方法赋值给变量
- 1.2.2 在非构造器的普通方法内进行初始化
- 1.3 成员变量-初始化代码块中初始化
- 1.4 静态变量-静态化代码块中初始化
- 1.5 构造器中初始化
- 1.5.1 默认构造器初始化
- 1.5.2 自定义构造器初始化
- 2. 类的加载顺序(无继承类和接口实现情形)
- 3. 类的加载顺序(继承类+接口实现情形)
- 3待更新
1. 变量的初始化方法
先不考虑继承父类和实现接口的情形,设想对一个Pet类的成员变量和静态变量进行初始化,有下列方法。
1.1 直接定义字段时赋值 or 显示字段初始化
private String name = "Lina";
private static String MasterName = "John";
1.2 调用方法进行初始化
1.2.1 调用方法赋值给变量
可以使用任何静态方法给静态变量/非静态变量赋值,也同样可以使用非静态方法给非静态变量赋值,只要方法返回的数据类型和变量的类型一致即可。
private int age = setAge();
public int setAge() {
int num = 2;
return num;
}
private static String id = setId();
public static String setId() {
String id = "X0012";
return id;
}
以上方式进行初始化本质上等同于直接赋值,只不过使用了方法的返回值代替了原本的值。
1.2.2 在非构造器的普通方法内进行初始化
变量在可以非构造器的普通方法内进行初始化,但是只有在创建对象,调用了方法之后才能真正完成初始化,而不是发生在类加载字段时。
(通常static变量不采用这种方式进行初始化,但只要使用该变量前完成了初始化,并不会影响实际使用)
private static String FatherName;
private String MotherName;
public static void setFatherName() {
FatherName = "Dog A";
}
public void setMotherName() {
FatherName = "Dog B";
}
1.3 成员变量-初始化代码块中初始化
成员变量可以在初始化代码块中初始化,初始化代码块可以定义多个。
private double height;
private double weight;
// initialization block 1
{
height = 0.5;
}
// initialization block 2
{
weight = 20;
}
注:静态变量是属于整个类的变量而不是属于某个对象的,因此不能把任何方法体内 (包括静态方法和静态代码块) 的局部变量声明为静态的。
1.4 静态变量-静态化代码块中初始化
类似地,静态变量可以在静态化代码块中初始化,静态代码块同样可以定义多个。
private static String color;
private static String sex;
// static block 1
static {
color = "Dotted";
}
// static block 2
static {
sex = "Female";
}
1.5 构造器中初始化
静态代码块和初始化代码块可以完成初始化任务,但通常情况下初始化变量的任务由构造器完成。下面初始化一下三个变量:
private String type;
private boolean healthy;
private static double price;
1.5.1 默认构造器初始化
在没有定义任何构造器时,Java会给我们自动提供一个默认的无参数构造器。
该构造器会将基本数据类型初始化为默认值,引用对象类型设置为null。
public Pet() {
this.type = null;
this.healthy = false;
this.price = 0.0;
}
但是当自主定义了一个构造器时,就不会自动提供默认构造器。没有被自定义的构造器初始化的变量将不会被初始化,除非再次定义一个无参数的构造器。如果自定义无参数的构造器,若构建对象时存在没有初始化的变量,也将自动初始化变量为默认值。
1.5.2 自定义构造器初始化
Java允许自定义多个构造器,也即重载构造器。
一个构造器可以通过this(...)调用其他的构造器,但是该语句必须位于构造器首行
public Pet(String type) {
this.type = type;
}
public Pet(String type, boolean healthy, double price) {
this(type);
this.healthy = healthy;
this.price = price;
}
- 引用对象类型的变量的初始化:
有时候,会经常碰到一个类中含有引用对象类型的变量,与基本数据类型变量相似,引用对象类型变量也同样可通过上述方法进行初始化。
现在假设有一个Toy类,而Pet类中列出了几种宠物爱玩的玩具,分别用1-6数字来代替玩具名
class Toy {
private int toolNum;
// Tool class 无参数构造器
public Toy() {
}
// Tool class 含参数构造器
public Toy(int toolNum) {
this.toolNum = toolNum;
}
}
/* 实际情况下,很少会创造静态对象,因为静态修饰的字段被类共享。此处引入静态对象仅仅是为了演示初始化方法及变量加载顺序,无其他实际意义。*/
初始化对象类变量:
private static Toy toy1;
private Toy toy2;
private Toy toy3 = generateToy();
private static Tool toy4 = new Toy();
private Toy toy5
static {
Pet.toy1 = new Toy(1);
}
public Toy generateToy() {
return new Toy();
}
{
tool3 = new Toy(3);
}
public Pet(){
this.toy5 = new Toy(5);
}
注:1. 虽然这里介绍了很多种初始化变量的方式,但是绝大部分情形仍然是直接在构造器中完成初始化,其他方式若使用不当易在实际开发过程中产生bug,应该尽量避免使用。
2. 对于静态常量 (例如: private static final int age = 20) 则通常在直接定义时赋值。
2. 类的加载顺序(无继承类和接口实现情形)
在不考虑继承父类、实现接口的情况下。类中变量和方法加载顺序:
- 在第一次调用类时,类进入加载阶段,会首先加载所有static修饰的内容: 静态变量 / 静态代码块 / 静态方法,其中静态方法只会被加载但并不会被执行。静态修饰的内容没有优先执行次序之分,在前的部分将先被执行。
- 加载完static修饰的内容后,若使用new关键字创建了对象则不再重新加载static修饰的内容了,因为静态内容在第一次调用类时已经被加载过了,后续可以直接调用静态内容。JVM会继续加载非静态内容: 成员变量/初始化代码块/成员方法,其中成员方法只会被加载但并不会被执行。这些除构造器外的非静态内容也无优先执行次序之分,在前的部分将先被执行。
- 第2步完成后,调用相应的构造器开始创建对象。若后续又使用new关键字新创建了对象,则重复2~3步。
- 对于静态方法和非静态方法都是被动调用,即系统不会自动调用执行。只有在主动调用后才会执行这些方法。
下面对于类的变量/代码块等加载顺序用一个完整的程序进行演示:
class Pet {
// 直接定义字段时赋值or显示字段初始化
private String name = "Lina";
private static String MasterName = "John";
// 初始化代码块中初始化
private double height;
private double weight;
// 静态初始化代码块中初始化
private static String color;
private static String sex;
// 构造器中初始化
private String type;
private boolean healthy;
private static double price;
// 引用数据类型初始化
private static Toy toy1 = new Toy(1);
// static block 1
static {
System.out.println("-- 静态初始化块 1 --");
color = "Dotted";
System.out.println("Color: " + color);
}
private static Toy toy2;
// 调用方法进行初始化
private static String id = setId();
// static block 2
static {
System.out.println("-- 静态初始化块 2 --");
sex = "Female";
System.out.println("Sex: " + sex);
Pet.toy2 = new Toy(2);
}
private Toy toy3 = new Toy(3);
// initialization block 1
{
System.out.println("-- 初始化块 1 --");
height = 0.5;
System.out.println("Height: " + this.height);
}
private int age = setAge();
private Toy toy4;
// initialization block 2
{
System.out.println("-- 初始化块 2 --");
weight = 20;
System.out.println("Weight: " + this.weight);
toy4 = new Toy(4);
}
private Toy toy5 = generateToy();
private Toy toy6;
public Toy generateToy() {
System.out.println("-- 调用 generateTool()方法初始化 --");
return new Toy();
}
// 引用返回值来初始化变量
public int setAge() {
System.out.println("-— 调用非静态方法初始化非静态变量 --");
int num = 2;
System.out.println("Age: " + num);
return num;
}
// 引用返回值来初始化变量
public static String setId() {
System.out.println("-— 调用静态方法初始化静态变量 --");
String id = "X0012";
System.out.println("Id: " + id);
return id;
}
// 方法内赋值
private static String FatherName;
private String MotherName;
public static void setFatherName() {
System.out.println("-- setFatherName() 方法内初始化 --");
FatherName = "Dog A";
System.out.println(FatherName);
}
public void setMotherName() {
System.out.println("-- setMotherName() 方法内初始化 --");
this.MotherName = "Dog B";
System.out.println(this.MotherName);
}
// 无参数构造器
public Pet() {
System.out.println("-- Pet Class 无参构造器 --");
}
// 含参构造器 1
public Pet(String type) {
System.out.println("-- Pet Class 含参构造器 1 --");
this.type = type;
this.toy6 = new Toy();
System.out.println("Type: " + type);
}
// 含参构造器 2
public Pet(String type, boolean healthy, double price) {
this(type);
System.out.println("-- Pet Class 含参构造器 2 --");
this.healthy = healthy;
Pet.price = price;
System.out.println("Healthy: " + this.healthy);
System.out.println("Price: " + Pet.price);
}
// 普通静态方法
public static void print() {
System.out.println();
for (int i = 10; i <= 50; i += 10)
System.out.println(i);
System.out.println();
}
// 辅助打印变量信息
public void toPetString() {
System.out.println();
System.out.println("|-----------打印 Pet 对象信息----------|");
System.out.println("Pet [Name = " + name + ", MasterName = "
+ MasterName + ", Age = " + age + ", Id = " + id + ", Height = "
+ height + ", Weight = " + weight + ", Type = " + type
+ ", Healthy = " + healthy + ", Price =" + price + "]");
System.out.println("Pet [FatherName = " + FatherName + ", MotherName = "
+ MotherName + "]");
System.out.println("Pet's Tools [ " + toy1 + ", " + toy2 + ", " + toy3
+ ", " + toy4 + ", " + toy5 + ", " + toy6 + "]");
System.out.println("|-----------------End----------------|");
}
}
class Toy {
private int toolNum;
// Tool class 无参数构造器
public Toy() {
System.out.println("-* Toy Class 无参构造器 *-");
System.out.println(this);
}
// Tool class 含参数构造器
public Toy(int toolNum) {
System.out.println("-* Toy Class 含参构造器 *-");
this.toolNum = toolNum;
System.out.println("Tool # " + this.toolNum);
}
// 辅助打印变量信息
@Override
public String toString() {
return "Tool # " + this.toolNum;
}
}
public class Run {
public static void main(String[] args) {
System.out.println("创建第 1 个Pet对象:\n");
Pet p1 = new Pet();
p1.toPetString();
Pet.print();
System.out.println("创建第 2 个Pet对象:\n");
Pet p2 = new Pet("Retriever", true, 100.0);
p2.setMotherName();
Pet.setFatherName();
p2.toPetString();
}
}
最终结果以两部分呈现,以便对比输出顺序。
Part 1
创建第 1 个Pet对象:
-* Toy Class 含参构造器 *-
Tool # 1
-- 静态初始化块 1 --
Color: Dotted
-— 调用静态方法初始化静态变量 --
Id: X0012
-- 静态初始化块 2 --
Sex: Female
-* Toy Class 含参构造器 *-
Tool # 2
-* Toy Class 含参构造器 *-
Tool # 3
-- 初始化块 1 --
Height: 0.5
-— 调用非静态方法初始化非静态变量 --
Age: 2
-- 初始化块 2 --
Weight: 20.0
-* Toy Class 含参构造器 *-
Tool # 4
-- 调用 generateToy()方法初始化 --
-* Toy Class 无参构造器 *-
Tool # 0
-- Pet Class 无参构造器 --
|-----------打印 Pet 对象信息----------|
Pet [Name = Lina, MasterName = John, Age = 2, Id = X0012, Height = 0.5, Weight = 20.0, Type = null, Healthy = false, Price =0.0]
Pet [FatherName = null, MotherName = null]
Pet's Tools [ Tool # 1, Tool # 2, Tool # 3, Tool # 4, Tool # 0, null]
|-----------------End----------------|
10
20
30
40
50
Part 2
创建第 2 个Pet对象:
-* Toy Class 含参构造器 *-
Tool # 3
-- 初始化块 1 --
Height: 0.5
-— 调用非静态方法初始化非静态变量 --
Age: 2
-- 初始化块 2 --
Weight: 20.0
-* Toy Class 含参构造器 *-
Tool # 4
-- 调用 generateTool()方法初始化 --
-* Toy Class 无参构造器 *-
Tool # 0
-- Pet Class 含参构造器 1 --
-* Toy Class 无参构造器 *-
Tool # 0
Type: Retriever
-- Pet Class 含参构造器 2 --
Healthy: true
Price: 100.0
-- setMotherName() 方法内初始化 --
Dog B
-- setFatherName() 方法内初始化 --
Dog A
|-----------打印 Pet 对象信息----------|
Pet [Name = Lina, MasterName = John, Age = 2, Id = X0012, Height = 0.5, Weight = 20.0, Type = Retriever, Healthy = true, Price =100.0]
Pet [FatherName = Dog A, MotherName = Dog B]
Pet's Tools [ Tool # 1, Tool # 2, Tool # 3, Tool # 4, Tool # 0, Tool # 0]
|-----------------End----------------|
3. 类的加载顺序(继承类+接口实现情形)
3待更新
参考内容:
- java中类加载与静态变量、静态方法与静态代码块详解与初始化顺序 by MrBoringBigFish
- 面试题——Java 类加载/创建对象的过程 by 天命ming
- Java中类的加载顺序介绍(ClassLoader) by hongjie_lin
If you have any question, please let me know, your words are always welcome.