4.6 对象构造
前面已经学会了编写简单的构造器,以便对定义的对象进行初始化.但是,由于对象构造非常重要,所以Java提供了多种编写构造器的方式.下面将详细地介绍一下.4.6.1 重载
从前面可以看到,GregorianCalendar类有多个构造器,可以使用:GregorianCalendar today = new GregorianCalendar();
或者
GregorianCalendar deadline = new GregorianCalendar(2009, Calendar.DECEMBER, 31);
这种特征叫做重载.如果多个方法有相同的名字,不同的阐述,便产生了重载.
注释:Java允许重载任何方法,而不只是构造器方法.因此,要完整地描述一个方法需要指出方法名以及参数类型.这叫做方法的签名. 返回值类型不是方法签名的一部分,也就是说,不能有两个名字相同,参数类型也相同但返回值类型却不同的方法.
4.6.2 默认域初始化
如果在构造器中没有显式地给域赋予初值,那么就会被自动地赋为默认值:数值为0,布尔值为 false,对象引用为 null .然而,只有缺少程序设计经验的人才会这样做/如果不明确地对域进行初始化,就会影响代码的可读性.注释:这是域与局部变量的主要不同点.必须明确地初始化方法中的局部变量,但是如果没有初始化类中的域,将会被初始化为默认值(0,false,null).
4.6.3 无参数的构造器
很多类都包含一个无参数的构造器,对象由无参数构造函数创建时,其状态会设置为适当的默认值.例如,以下是Employee类的无参数构造函数:public Employee()
{
name = "";
salary = 0;
hireDay = new Date();
}
如果在编写一个类时没有编写构造器,那么系统就会提供一个无参数构造器.这个构造器将所有的实例域设置为默认值.于是,实例域中的数值型数据设置为0,布尔型数据设置为 false,所有对象变量设置为 null .
如果类中提供了至少一个构造器,但是没有提供无参数的构造器,则在构造对象时如果没有提供参数就会被视为不合法.
警告:请记住,仅当类没有提供任何构造器时,系统才会提供一个默认的构造器.如果在编写类的时候,给出了一个构造器,要想让这个类的用户能够采用下列方式构造实例:
new ClassName()
就必须提供一个默认的构造器(即不带参数的构造器).
4.6.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;
}
...
}
注释:在C++中,不能直接初始化类的实例域.所有的域必须在构造器中设置.但是,有一个特殊的初始化器列表语法,如下所示:
Employee::Employee(String n, double s, int y, int m, int d)
: name(n), salary(s), hireDay(y, m, d)
{}
C++使用这种特殊的语法来调用域构造器.在Java中没有这种必要,因为对象没有子对象,只有指向其他对象的指针.
4.6.5 参数名
在编写很小的构造器时,常常在参数名上出现错误.通常,参数用单个字符命名:
public Employee(String n, double s)
{
name = n;
salary = s;
}
但这样有一个缺陷:只有阅读代码才能够了解参数n和参数s的含义.于是有些
程序员在每个参数前面加上一个前缀"a":
public Employee(String aName, double aSalary)
{
name = aName;
salary = aSalary;
}
这样很清晰.
注释:在C++中,经常用下划线或某个固定的字母(一般选用m或x)作为实例域的前缀.例如salary域可能被命名为_salary,mSalary或xSalary.Java程序员通常不这样做.
4.6.6 调用另一个构造器
关键字 this 引用方法的隐式参数.然而,关键字还有另外一个含义.如果构造器的第一个语句形如 this(...),这个构造器将调用同一个类的另一个构造器.下面是一个典型的例子:
public Employee(double s)
{
// call Employee(String, double)
this("Employee #" + nextId, s);
nextId++;
}
当调用 new Employee(6000)时,Employee(double)构造器将调用Employee(String, double)构造器.
采用这种方式使用 this 关键字非常有用,这样对公共的构造器代码部分只编写一次即可.
注释:在Java中,this 引用等价于C++的 this 指针.但是在C++中,一个构造器不能调用另一个构造器.在C++中必须将抽取出的公共初始化代码写成一个独立的方法.
4.6.7 初始化块
前面已经讲过两种初始化数据域的方法:在构造器中设置值
在声明中赋值
实际上,Java还有第三种机制,称为 初始化块(initialization block).在一个类的声明中,可以包含多个代码块.只要构造类的对象,这些块就会被执行.例如,
class Employee
{
private static 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.所有数据域被初始化为默认值(0,false,null)
2.按照在类声明中出现的次序,依次执行所有域初始化语句和初始化块
3.如果构造器第一行调用了第二个构造器,则执行第二个构造器主体
4.执行这个构造器的主体
下面这个程序展示了本节讨论的很多特性:
重载构造器
用 this(...)调用另一个构造器
无参数构造器
对象初始化块
静态初始化块
实例域初始化
constructorTest.java如下所示:
import java.util.*;
public class ConstructorTest
{
public static void main(String[] args)
{
// fill the staff array with three Employee objects
Employee[] staff = new Employee[3];
staff[0] = new Employee("harry", 4000);
staff[1] = new Employee(6000);
staff[2] = new Employee();
// print out information about all Employee objects
for (Employee e : staff)
System.out.println("name = " + e.getName() + ", id = " + e.getId() + ", salary = " + e.getSalary());
}
}
class Employee
{
private static int nextId;
private int id;
private String name = ""; // instance field initialization
private double salary;
// static initialization block
static
{
Random generator = new Random();
// set nextId to a random number between 0 and 9999
nextId = generator.nextInt(10000);
}
// object initialization block
{
id = nextId;
nextId++;
}
// three overloaded constructors
public Employee(String n, double s)
{
name = n;
salary = s;
}
public Employee(double s)
{
// call the Employee(String, double) constructor
this("Employee #" + nextId, s);
}
// the default construct
public Employee()
{
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public int getId()
{
return id;
}
}
4.6.8 对象析构与finalize方法
C++有显式的析构器方法,其中放置一些对当对象不再使用时需要执行的清理代码.在析构器中最常见的操作是回收分配给对象的存储空间.由于Java有自动的垃圾回收器,不需要人工回收内存,所以Java不支持析构器.如果某个资源需要在使用完毕后立刻被关闭,那么就需要由人工来管理.对象用完时,可以应用一个close方法来完成相应的清理操作.