我们来回忆一下上一篇创建的类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());
}
}
输出:
代码中的如下部分称为类初始化器或静态初始化器,类初始化器也被称为静态程序块或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());
}
}
输出:
静态初始化器中执行的操作如下所示:
• 获取当前(程序运行时的)日期
• 将获取的日期显示为“今天是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);
}
}
输出:
类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 { }是类初始化器(静态初始化器),类变量可以在类初始化器中进行初始化,当以某种方式首次使用类时,这个类的"类初始化器” 就执行完毕了。