初始化数据字段的方法:
- 在构造器中设置值。
- 在声明中赋值。
- 使用初始化块(initialization block)赋值。
1. 默认字段初始化
如果在构造器中没有显示地为字段设置初值,那么就会被自动地赋为默认值:数值为 0、布尔值为 false、对象引用为 null。有些人认为依赖默认值的做法是一种不好的编程实践。确实,如果不明确地对字段进行初始化,就会影响程序代码的可读性。
注释: 这是字段与局部变量的一个重要区别。方法中的局部变量必须明确初始化。但是在类中,如果没有初始化类中的字段,将会自动初始化为默认值(0、 false 或 null);
public class Employee {
private String name;
private double salary;
protected LocalDate hireDay;
public Employee() {}
...
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public LocalDate getHireDay() {
return hireDay;
}
}
如果没有在构造器中指定如何初始化某些字段,默认情况下,将会将 slary 字段初始化为 0,将 name 和 hireDay 字段初始化为 null。
但是,这并不是一个好主意。如果此时调用 getName 方法或 getHireDay 方法,就会得到一个 null 引用。这应该不是我们希望的结果:
LocalDate h = harry.getHiredDay();
int year = h.getYear(); // throws exception if h is null
2. 在构造器中设置值
先看看 Employee 类的构造器:
public class Employee {
private String name;
private double salary;
private LocalDate hireDay;
public Employee(String name, double salary, int year, int month, int day) {
this.name = name;
this.salary = salary;
hireDay = LocalDate.of(year, month, day);
}
...
}
构造器与类同名。在构造 Employee 类的对象时,构造器会运行,从而将实例字段初始化为所希望的初始状态。
当使用下面这条代码创建 Employee 类的实例时:
new Employee("James Bond", 100000, 1950, 1, 1);
将会把实例字段设置为:
name = "James Bond";
salary = 100000;
hireDay = LocalDate.of(1950, 1, 1); // 1950-01-01
3. 在声明中赋值
可以在类定义中直接为任意字段赋值:
public class Employee {
private String name = "";
}
在执行构造器之前先完成这个赋值操作。如果一个类的所有构造器都希望把某个特定的实例字段设置为同一个值,这个语法特别有用。
初始值不一定是常量值。在下面的例子中,就是利用方法调用初始化一个字段。考虑以下 Employee 类,其中每个员工有一个 id 字段。可以使用下列方式进行初始化:
public class Employee {
private static int nextId;
private int id = assignId();
...
private static int assignId() {
int r = nextId;
nextId++;
return r;
}
...
}
4. 初始化块
在一个类的声明中,可以包含任意多个代码块。只要构造这个类的对象,这些块就会被执行。
class Employee {
private static int nextId;
private int id;
private String name;
private double salary;
// 初始化块
{
id = nextId;
nextId++;
}
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
...
}
无论使用哪个构造器构造对象,id 字段都会在对象初始化块中初始化。首先运行初始化块,然后才运行构造器的主体部分。
这种机制不是必需的,也不常见。通常会直接将初始化代码放在构造器中。
注释: 可以在初始化块中设置字段,即使这些字段在类后面才定义,这是合法的。但是,为了避免循环定义,不允许读取在后面初始化的字段。这些规则太过复杂,让编译器的实现者都很头疼,所以较早的 Java 版本中这些规则的实现存在一些小错误,因此建议总是将初始化块放在字段定义之后。
可以通过提供一个初始化值,或者使用一个静态的初始化块来初始化对静态字段。前面已经介绍过第一种机制:
private static int nextId = 1;
如果类的静态字段需要很复杂的初始代码,那么可以使用静态的初始化块。将代码放在一个块中,并标记关键字 static。下面的示例中,其功能是将员工 ID 的起始值赋予一个小于 10 000 的随机数:
class Employee {
private static int nextId;
// 静态的初始化块(static initialization block)
static
{
Random generator = new Random();
nextId = generator.nextInt(1000);
}
}
在类第一次加载的时候,将会进行静态字段的初始化。与实例字段一样,除非将静态字段显式地设置成其他值,否则默认的初始值是 0、false 或 null。所有的静态字段初始化方法以及静态初始化块都将依照类声明中出现的顺序执行。
import java.util.Random;
public class ConstructorTest {
public static void main(String[] args) {
var staff = new Employee[3];
staff[0] = new Employee("Harry", 4000);
staff[1] = new Employee(6000);
staff[2] = new Employee();
for(Employee employee : staff) {
System.out.println(employee);
}
}
}
class Employee {
private static int nextId;
private int id;
private String name = "";
private double salary;
static
{
Random generator = new Random();
// 设置 nextId 为 0 到 9999 的随机数
nextId = generator.nextInt(10000);
}
// 初始化块
{
id = nextId;
nextId++;
}
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public Employee(double salary) {
this.salary = salary;
}
public Employee() {
}
/**
* 增加工资
* @param byPercent 百分比
*/
public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100;
salary += raise;
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public int getId() {
return id;
}
public String toString() {
String str = "%s[name=%s,id=%d,salary=%s]";
return String.format(str, getClass(), name, id, salary);
}
}