12、Java基础---类初始化器和实例初始化器

类初始化器和实例初始化器

一、类初始化器(静态初始化器)

我们来回忆一下上一篇创建的类Id,每创建一个类的实例,都会为其赋上连续的标识编号1、2、3······

这里对该类进行修改,使得标识编号的开始编号并不是1, 而是一个随机数值, 程序如下:

// 标识编号类(通过随机数来设定开始编号)
import java.util.Random;
class RandId {
	private static int counter;	// 赋到了哪一个标识编号
	private int id;				// 标识编号
	static {
		Random rand = new Random();
		counter = rand.nextInt(10) * 100;
	}
	//-- 构造函数 --//
	public RandId() {
		id = ++counter;			// 标识编号
	}
	//--- 获取标识编号 ---//
	public int getId() {
		return id;
	}
}
public class RandIdTester {
	public static void main(String[] args) {
		RandId a = new RandId();
		RandId b = new RandId();
		RandId c = new RandId();
		System.out.println("a的标识编号:" + a.getId());
		System.out.println("b的标识编号:" + b.getId());
		System.out.println("c的标识编号:" + c.getId());
	}
}

输出:

12、Java基础---类初始化器和实例初始化器_第1张图片

代码中的如下部分称为类初始化器或静态初始化器,类初始化器也被称为静态程序块或static程序块;

static {
		Random rand = new Random();
		counter = rand.nextInt(10) * 100;
	}

具语法结构图如图:

正如其名,类初始化器是在类被初始化时执行的,所谓“类被初始化” 是指下述时间点:

• 创建类的实例
• 调用类中的类方法
• 将值赋给类中的类变量
• 取出类中非常量的类变量的值

也可以这样理解:

当以某种方式初次使用类时,该类的类初始化器就执行完毕了。

即使程序中声明了类,但如果不使用的话,这个类就不会被初始化,类初始化器也不会被执行;如果将类变量(静态字段)初始化为常量,就只需将初始值赋给类变量的声明即可。不过,如上述程序所示, 当要赋给类变量的值是通过某些计算来设定时,也可以不赋初始值。这样就变成了在类初始化器中进行计算后,再为类变量设置值。

初始化类变量所需的处理可以通过类初始化器来执行。

类初始化器中不可以存在return语句,也不可以使用this或super,否则会发生编译错误,类Randld的类初始化器将随机数赋给了类变量counter, 赋的值为0,100,200,···,900中的某个值。
运行示例所示为赋给counter的值为300时的情形。每次构造函数创建类Randld类型的实例时,就会赋予标识编号301、302······
与每次创建实例时就执行构造函数(执行次数与创建的实例的个数相等)的处理不同,
类初始化器只执行一次【当首次调用某个类中的构造函数肘,这个类的类初始化器一定已经执行完毕了】

在类初始化器中,也可以执行初始化类变量之外的操作。例如,可以打开写入了类相关信息的文件并取出信息。
这里我们来思考一个在画面上进行显示的示例。对上一个程序中的类初始化器进行改写后, 程序如下:
先来运行一下程序,这里是
根据运行程序的日期,来设定赋给实例的标识编号。

// 标识编号类(通过今天的日期来设定开始编号)
import java.util.GregorianCalendar;
import static java.util.GregorianCalendar.*;
class DateId {
	private static int counter;	// 赋到了哪一个标识编号
	private int id;				// 标识编号
	static {
		GregorianCalendar today = new GregorianCalendar();
		int y = today.get(YEAR);			// 年
		int m = today.get(MONTH) + 1;		// 月
		int d = today.get(DATE);			// 日
		System.out.printf("今天是%04d年%02d月%02d日。\n", y, m, d);
		counter = y * 1000000 + m * 10000 + d * 100;
	}
	//-- 构造函数 --//
	public DateId() {
		id = ++counter;			// 标识编号
	}
	//--- 获取标识编号 ---//
	public int getId() {
		return id;
	}
}
public class DateIdTester {
	public static void main(String[] args) {
		DateId a = new DateId();
		DateId b = new DateId();
		DateId c = new DateId();
		System.out.println("a的标识编号:" + a.getId());
		System.out.println("b的标识编号:" + b.getId());
		System.out.println("c的标识编号:" + c.getId());
	}
}

输出:

12、Java基础---类初始化器和实例初始化器_第2张图片

静态初始化器中执行的操作如下所示:

• 获取当前(程序运行时的)日期
• 将获取的日期显示为“今天是y年m月d日。”
•根据日期设定counter的初始值。如果日期为yyyy年mm月dd日, 则将yyyymmddOO赋给变量counter

这里展示的运行示例是2019年9月22日执行的示例通过执行静态初始化器,变量counter中赋入的值为2019092200,
因此, 赋给实例的标识编乃就是2019092201、2019092202··· 

二、实例初始化器

方法包含类方法和实例方法,变量包含类变量和实例变量;同样地,初始化器也包含类(静态)初始化器和实例初始化器。

正如其名,实例初始化器用于初始化实例;使用实例初始化器的程序示例如下:

// 带标识编号的XY类
class XY {
	private static int counter = 0;	// 赋到了哪一个标识编号
	private int id;					// 标识编号
	private int x = 0;	// X
	private int y = 0;	// Y
	{
		id = ++counter;
	}
	public XY()             { }
	public XY(int x)        { this.x = x; }
	public XY(int x, int y) { this.x = x; this.y = y; }
	public String toString() {
		return "No." + id + " … (" + x  + ", " + y + ")";
	}
}
public class XYTester {
	public static void main(String[] args) {
		XY a = new XY();			// 初始化为( 0,  0)
		XY b = new XY(10);			// 初始化为(10,  0)
		XY c = new XY(20, 30);		// 初始化为(20, 30)
		System.out.println("a = " + a);
		System.out.println("b = " + b);
		System.out.println("c = " + c);
	}
}

输出:

12、Java基础---类初始化器和实例初始化器_第3张图片

类XY拥有两个字段x和y,它们都初始化为0、但使用重载的构造函数、可以只指定x的值,或者同时指定x和y的值;
此外,该类的各个实例被赋予1、2、3..... 的标识编号,编号的赋予方法与前面相同;
 

	{
		id = ++counter;
	}

上述代码是关键的实例初始化器,类中没有加上static的{ }部分就是实例初始化器,实例初始化器的语法结构图如下:

实例初始化器会将counter递增后的值赋给id,类XY的实例初始化器在构造函数主体开始运行时执行;
因此,不管调用三个构造函数中的哪一个,都会首先执行实例初始化器,然后再执行构造函数主体

如果本程序不使用实例初始化器来实现的话,构造函数就会变成下面这样:

public XY()	{id = ++counter;}
public XY(int X)	{id = ++counter; this.x = x;}
public XY(int x, int y)	{id = ++counter; this.x = x; this.y = y;}

也就是说counter和id的更新处理要写在所有的构造函数中。这样一来就可能会发生在一部分构造函数中忘了书写更新处理, 或者添加新的构造函数时忘了书写更新处理等错误,因此,如果类的全部构造函数中存在共同的处理(创建实例时一定要执行的处理),那么该共同的处理就可以独立为实例初始化器。

三、类变量和类方法总结


1)声明中加上static的字段和方法就是类变量(静态字段)和类方法(静态方法);
2)类变量(静态字段)并不是属于各个实例的数据,而是该类的全部实例共享的数据;类变量与实例的个数无关(即使不存在实例),只有一个使用"类名.字段名“ 进行访问;
3)提供给类的使用者的常量可以声明为public且final的类变量;
4)类方法(静态方法)不用于特定实例, 而是用于与整个类相关的处理或者与类实例的状态无关的处理;使用“类名.方法名(...) " 进行调用;
5)重载是定义签名不同但名称相同的方法,可以对类方法和实例方法执行重载;
6)实例方法可以访问同一个类中的实例变量(非静态字段)和实例方法(非静态方法);
7)类方法不可以访问同一个类中的实例变量(非静态字段)和实例方法(非静态方法);
8)不具有内部状态(实例变量),只提供类方法的类称为工具类.工具类适用于数值计算等某些特定领域的方法和常量的封装;
9)Math工具类会提供数值计符所需的常量和众多方法;
10)类声明中的{ }是实例初始化器,实例初始化器会在构造函数的开头被自动调用,类的全部构造函数中的共同处理(创建实例时一定执行的处理)可以独立为实例初始化器。
11)类声明中的static { }是类初始化器(静态初始化器),类变量可以在类初始化器中进行初始化,当以某种方式首次使用类时,这个类的"类初始化器” 就执行完毕了。

12、Java基础---类初始化器和实例初始化器_第4张图片

12、Java基础---类初始化器和实例初始化器_第5张图片
12、Java基础---类初始化器和实例初始化器_第6张图片

你可能感兴趣的:(#,Java基础)