Johnson Hang

  • ——————————————————————————————————————————————
  • My CSDN Blog

Java 类中变量的初始化,类内部变量/代码块的加载顺序

@

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. 类的加载顺序(无继承类和接口实现情形)

在不考虑继承父类、实现接口的情况下。类中变量和方法加载顺序:

  1. 在第一次调用类时,类进入加载阶段,会首先加载所有static修饰的内容: 静态变量 / 静态代码块 / 静态方法,其中静态方法只会被加载但并不会被执行。静态修饰的内容没有优先执行次序之分,在前的部分将先被执行。

  2. 加载完static修饰的内容后,若使用new关键字创建了对象则不再重新加载static修饰的内容了,因为静态内容在第一次调用类时已经被加载过了,后续可以直接调用静态内容。JVM会继续加载非静态内容: 成员变量/初始化代码块/成员方法,其中成员方法只会被加载但并不会被执行。这些除构造器外的非静态内容也无优先执行次序之分,在前的部分将先被执行。

  3. 第2步完成后,调用相应的构造器开始创建对象。若后续又使用new关键字新创建了对象,则重复2~3步。

  4. 对于静态方法和非静态方法都是被动调用,即系统不会自动调用执行。只有在主动调用后才会执行这些方法。


下面对于类的变量/代码块等加载顺序用一个完整的程序进行演示:

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待更新

参考内容:

  1. java中类加载与静态变量、静态方法与静态代码块详解与初始化顺序 by MrBoringBigFish
  2. 面试题——Java 类加载/创建对象的过程 by 天命ming
  3. Java中类的加载顺序介绍(ClassLoader) by hongjie_lin

If you have any question, please let me know, your words are always welcome.
新人入坑,如有错误/不妥之处,欢迎指出,共同学习。

posted @ 2020-07-16 05:06  Johnson1z  阅读(540)  评论(0编辑  收藏  举报