您已经了解了如何编写定义对象初始状态的简单构造函数。但是,由于对象构造非常重要,因此Java提供了许多用于编写构造函数的机制。我们将在以下各节中介绍这些机制。
1.重载函数
有些类具有多个构造函数。例如,您可以将一个空的StringBuilder对象构造为
var messages = new StringBuilder();
另外,您可以指定一个初始字符串:
var todoList = new StringBuilder(“ To do:\ n”);
此功能称为 重载。如果多个方法具有相同的名称(在本例中为StringBuilder构造函数方法),但参数不同,则会发生重载。编译器必须整理出要调用的方法。它
通过将各种方法的标题中的参数类型与特定方法调用中使用的值的类型进行匹配,来选择正确的方法。
如果编译器无法匹配参数,则可能会发生编译时错误,这可能是因为根本没有匹配项,或者是因为没有一个匹配项优于所有其他参数。(找到匹配项的过程称为 重载解析。)
注意: Java允许您重载任何方法,而不仅仅是构造方法。
因此,要完整描述一种方法,您需要指定其名称以及其参数类型。这称为方法的 签名。例如,String类具有四个称为indexOf的公共方法。它们具有签名indexOf(int)
indexOf(int,int)
indexOf(String)
indexOf(String,int)
返回类型不是方法签名的一部分。也就是说,不能有两个具有相同名称和参数类型但返回类型不同的方法。
2.默认字段初始化
如果未在构造函数中显式设置字段,则会将其自动设置为默认值:数字设置为0,布尔值设置为false,对象引用设置为null。有人认为依靠默认值是不好的编程习惯。当然,如果对字段进行了不可见的初始化,这会使某人更难理解您的代码。
注意:这是字段和局部变量之间的重要区别。您必须始终在方法中显式初始化局部变量。但是在类中,如果不初始化字段,则会将其自动初始化为默认值(0,false或null)。
例如,考虑Employee类。假设您未指定如何初始化构造函数中的某些字段。默认情况下,salary字段将初始化为0,name和hireDay字段将初始化为null。
但是,这不是一个好主意。如果有人调用了getName或getHireDay方法,他们将得到他们可能不希望的空引用:
LocalDate h = harry.getHireDay();
int year = h.getYear(); // throws exception if h is null
3.不带参数的构造函数
许多类都包含一个不带参数的构造函数,该构造函数创建的对象的状态设置为适当的默认值。例如,这是Employee类的无参数构造函数:
public Employee()
{
name = "";
salary = 0;
hireDay = LocalDate.now();
}
如果您编写的类没有任何构造函数,那么将为您提供一个无参数的构造函数。此构造函数将 所有实例字段设置为其默认值。因此,实例字段中包含的所有数字数据将为0,所有布尔值将为false,所有对象变量将为null。
如果一个类至少提供一个构造函数,但不提供无参数构造函数,则在不提供参数的情况下构造对象是非法的。例如,清单4.2中的原始Employee类提供了一个构造函数:
public Employee(String n, double s, int year, int month, int day)
对于该类,构造默认Employee是不合法的。也就是说,调用e = new Employee();
会是一个错误。
注意:请记住,仅当您的类没有其他构造函数时,您才能获得无参数构造函数。如果您使用自己的单个构造函数编写类,并且希望类的用户能够通过调用以下内容来创建实例:
new ClassName()
那么您必须提供一个无参数的构造函数。当然,如果您对所有字段的默认值都满意,则只需提供:
public ClassName()
{
}
C ++注意: C ++使用“初始化列表”语法构造字段:
Employee :: Employee(String n,double s,int y,int m,int d)// C ++
: name(n),
salary(s),
hireDay(y, m, d)
{
}
4.显式字段初始化
通过重载类中的构造方法,您可以建立许多方法来设置类的实例字段的初始状态。确保无论构造函数调用如何,每个实例字段都设置为有意义的值,这始终是一个好主意。
您可以简单地将一个值分配给类定义中的任何字段。例如:
class Employee
{
private String name = "";
. . .
}
该分配是在构造函数执行之前执行的。如果类的所有构造函数都需要将特定的实例字段设置为相同的值,则此语法特别有用。
初始化值不必为常数。这是一个使用方法调用初始化字段的示例。考虑一个Employee类,其中每个雇员都有一个id字段。您可以按以下方式对其进行初始化:
class Employee
{
private static int nextId;
private int id = assignId();
. . .
private static int assignId()
{
int r = nextId;
nextId++;
return r;
}
. . .
}
5.调用另一个构造函数
关键字this表示方法的隐式参数。但是,此关键字具有第二个含义。
如果 构造函数的第一条语句的形式为this(. . .),则构造函数将调用同一类的另一个构造函数。这是一个典型示例:
public Employee(double s)
{
// calls Employee(String, double)
this("Employee #" + nextId, s);
nextId++;
}
当您调用new Employee(60000)时,Employee(double)构造函数将调用Employee(String,double)构造函数。
以这种方式使用this关键字很有用-您只需编写一次通用构造代码。
C ++注: Java中的this引用与C ++中的this指针相同。
但是,在C ++中,一个构造函数不可能调用另一个构造函数。
6.初始化块
您已经看到了两种初始化数据字段的方法:
•通过在构造函数中设置值
•通过在声明中分配值
Java中存在第三种机制,称为 初始化块。类声明可以包含任意代码块。每当构造该类的对象时,就会执行这些块。例如:
class Employee
{
private static int nextId;
private int id;
private String name;
private double salary;
// object initialization block
{
id = nextId;
nextId++;
}
public Employee(String n, double s)
{
name = n;
salary = s;
}
public Employee()
{
name = "";
salary = 0;
}
. . .
}
在此示例中,无论使用哪个构造函数构造对象,都在对象初始化块中初始化id字段。初始化块首先运行,然后执行构造函数的主体。
这种机制从没有必要,也不是普遍的。通常是更多可以将初始化代码直接放在构造函数中。
这是在调用构造函数时发生的详细情况:
1.如果构造函数的第一行调用了第二个构造函数,则第二个构造函数将使用提供的参数执行。
2.除此以外,
a)所有数据字段均被初始化为其默认值(0,false或null)。
b)所有字段初始化程序和初始化块都按照它们在类声明中出现的顺序执行。
3.构造函数的主体被执行。
要初始化静态字段,请提供初始值或使用静态初始化块。您已经看到了第一种机制:
private static int nextId = 1;
如果您的类的静态字段需要复杂的初始化代码,请使用静态初始化块。
将代码放在一个块中,并用关键字static对其进行标记。这是一个例子。我们希望员工ID号以小于10,000的随机整数开头:
static
{
var generator = new Random();
nextId = generator.nextInt(10000);
}
静态初始化是在首次加载该类时发生的。与实例字段一样,静态字段为0,false或null,除非您将其明确设置为另一个值。
所有静态字段初始化程序和静态初始化块均按照它们在类声明中出现的顺序执行。
清单4.5中的程序显示了我们在本节中讨论的许多功能:
•重载的构造函数
•使用this(...)调用另一个构造函数
•无参数构造函数
•对象初始化块
•静态初始化块
•实例字段初始化
import java.util.*;
public class ConstructorTest {
public static void main(String[] args){
Employee1 [] staff = new Employee1[3];
//以三种不同方式初始化对象
staff[0] = new Employee1("Harry",30000);
staff[1] = new Employee1(60000);
staff[2] = new Employee1();
for(Employee1 e:staff){
System.out.println(e.getName()+" "+ e.getId()+" "+e.getSalary());
}
}
}
class Employee1{
private double salary;
private static int nextId;
private int Id;
private String name =" "; //初始化为“ ”
//生成随机编号
static
{
var generator = new Random();
nextId = generator.nextInt(10000);
}
{
Id = nextId;
nextId++;
}
public Employee1(String n,double s){
name = n;
salary = s;
}
public Employee1(double s){
this("Employee#"+nextId,s); //调用public Employee1(String n,double s)构造函数
}
public Employee1(){
}
public String getName(){
return name;
}
public int getId(){
return Id;
}
public double getSalary(){
return salary;
}
}