重载
有些类有多个构造器。例如, 可以如下构造一个空的 StringBuilder 对象:
StringBuilder messages = new StringBuilder();
或者, 可以指定一个初始字符串:
StringBuilder todoList = new StringBuilder("To do:\n");
这种特征叫做重载( overloading)。如果多个方法(比如, StringBuilder 构造器方法)有相同的名字、 不同的参数,便产生了重载。编译器必须挑选出具体执行哪个方法,它通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。
Java 允许重载任何方法, 而不只是构造器方法。因此,要完整地描述一个方法,需要指出方法名以及参数类型。这叫做方法的签名(signature)。例如, String 类有 4 个称为 indexOf 的公有方法。它们的签名是
indexOf(int)
indexOf(int, int)
indexOf(String)
indexOf(String, int)
返回类型不是方法签名的一部分。也就是说, 不能有两个名字相同、 参数类型也相同却返回不同类型值的方法。
默认域初始化
如果在构造器中没有显式地给域赋予初值,那么就会被自动地赋为默认值: 数值为 0、布尔值为 false、 对象引用为 null。 如果不明确地对域进行初始化,就会影响程序代码的可读性。
无参数的构造器
很多类都包含一个无参数的构造函数,对象由无参数构造函数创建时, 其状态会设置为适当的默认值。 例如, 以下是 Employee 类的无参数构造函数:
public Employee()
{
name ="";
salary = 0;
hireDay = LocalDate,now();
}
如果在编写一个类时没有编写构造器, 那么系统就会提供一个无参数构造器。这个构造器将所有的实例域设置为默认值。于是, 实例域中的数值型数据设置为 0、 布尔型数据设置为 false、 所有对象变量将设置为 null。如果类中提供了至少一个构造器, 但是没有提供无参数的构造器, 则在构造对象时如果没有提供参数就会被视为不合法。
显示域初始化
通过重载类的构造器方法,可以采用多种形式设置类的实例域的初始状态。确保不管怎样调用构造器,每个实例域都可以被设置为一个有意义的初值。可以在类定义中, 直接将一个值赋给任何域。例如:
class Employee
{
private String name = "";
...
}
在执行构造器之前,先执行赋值操作。当一个类的所有构造器都希望把相同的值赋予某个特定的实例域时,这种方式特别有用。但是,初始值不一定是常量值。在下面的例子中, 可以调用方法对域进行初始化。
class Employee
{
private static int nextId;
private int id = assignId();
...
private static int assignId()
{
int r = nextld;
nextld++;
return r;
}
...
}
参数名
在编写很小的构造器时(这是十分常见的), 常常在参数命名上出现错误。通常, 参数用单个字符命名:
public Employee(String n, double s)
{
name=n;
salary=s;
}
但这样做有一个缺陷:只有阅读代码才能够了解参数 n 和参数 s 的含义。有些程序员在每个参数前面加上一个前缀“ a”:
public Employee(String aName, double aSalary)
{
name = aName ;
salary = aSalary;
}
还一种常用的技巧,它基于这样的事实:参数变量用同样的名字将实例域屏蔽起来。 例 如,如果将参数命名为 salary,salary 将引用这个参数, 而不是实例域。 但是,可以采用 this.salary 的形式访问实例域。下面是一个示例:
public Employee(String naie, double salary)
{
this.name = name;
this.salary = salary;
}
调用另一个构造器
如果构造器的第一个语句形如 this(…), 这个构造器将调用同一个类的另一个构造器。下面是一个典型的例子:
public Employee(double s)
{
// calls Employee(String, double)
this("Employee #" + nextld, s);
nextld++;
}
当调用 new Employee(60000) 时, Employee(double) 构造器将调用 Employee(String,double)构造器。采用这种方式使用 this 关键字非常有用, 这样对公共的构造器代码部分只编写一次即可。
初始化块
前面已经讲过两种初始化数据域的方法:
- 在构造器中设置值
- 在声明中赋值
实际上,Java 还有第三种机制, 称为初始化块(initialization block)。在一个类的声明中,
可以包含多个代码块。只要构造类的对象,这些块就会被执行。例如:
class Employee
{
private static int nextld;
private int id;
private String name;
private double salary;
// object initialization block
{
id = nextld;
nextld++;
}
public Employee(String n, double s)
{
name=n;
salary=s;
}
public Employee()
{
name="";
salary=0;
}
...
}
在这个示例中,无论使用哪个构造器构造对象,id 域都在对象初始化块中被初始化。首先运行初始化块,然后才运行构造器的主体部分。这种机制不是必需的,也不常见。通常会直接将初始化代码放在构造器中。下面是调用构造器的具体处理步骤:
- 所有数据域被初始化为默认值(0、false 或 null)。
- 按照在类声明中出现的次序, 依次执行所有域初始化语句和初始化块。
- 如果构造器第一行调用了第二个构造器,则执行第二个构造器主体
- 执行这个构造器的主体.
可以通过提供一个初始化值, 或者使用一个静态的初始化块来对静态域进行初始化。前面已经介绍过第一种机制:
private static int nextld = 1;
如果对类的静态域进行初始化的代码比较复杂,那么可以使用静态的初始化块。将代码放在一个块中,并标记关键字 static。下面是一个示例。其功能是将雇员 ID 的起始值赋予一个小于 10 000 的随机整数。 // static initialization block
static
{
Random generator = new Random();
nextld = generator.nextInt(10000);
}
在类第一次加载的时候, 将会进行静态域的初始化。与实例域一样,除非将它们显式地设置成其他值, 否则默认的初始值是 0、 false 或 null。 所有的静态初始化语句以及静态初始化块都将依照类定义的顺序执行。
对象析构与finalize方法
有些面向对象的程序设计语言,特别是 C++, 有显式的析构器方法,其中放置一些当对象不再使用时需要执行的清理代码。在析构器中, 最常见的操作是回收分配给对象的存储空间。由于 Java 有自动的垃圾回收器,不需要人工回收内存, 所以 Java 不支持析构器。
当然,某些对象使用了内存之外的其他资源,在这种情况下,当资源不再需要时, 将其回收和再利用将显得十分重要。可以为任何一个类添加 finalize 方法。finalize 方法将在垃圾回收器清除对象之前调用。在实际应用中,不要依赖于使用 finalize 方法回收任何短缺的资源, 这是因为很难知道这个方法什么时候才能够调用。
如果某个资源需要在使用完毕后立刻被关闭, 那么就需要由人工来管理。对象用完时,可以应用一个 close 方法来完成相应的清理操作。