对象构造
Java中构造器(constructor)用来定义对象的初始状态,并且伴随着new操作符使用。由于对象构造非常重要,所以Java提供了多种编写构造器的机制。
(一)重载
有些类有多个构造器。例如:
StringBuilder messages = new StringBuilder();//构造一个空的StringBuilder对象
StringBuilder todoList = new StringBuilder("To do :\n");//也可以指定一个初始字符串
这种特征叫做重载(overloading),重载就是有多个方法叫相同的名字,不同的参数,这样就叫重载。编译器必须挑选出与给定参数匹配的方法,如果找不到匹配的,则编译错误,可能该构造器根本不存在。
注意:Java中允许任何方法重载,而不只是构造器。因此,要完整的描述一个方法,需要指出方法名和参数类型,这个叫做方法签名。但是需要注意,返回类型不是方法签名的一部分,由此可以知道,不能有两个方法名字相同,参数类型相同而返回类型不同的方法。
(二)默认域初始化
如果在构造器中没有显示的给域赋予初值,那么就会被自动的赋予默认值:数值为0、布尔值为false、对象引用为null。但是我们不建议使用这种方式,只有缺少程序设计经验的人才会这样做。
注意:这是域和局部变量主要不同点。局部变量必须明确的初始化,但是如果没有初始化类中的域,那么就会被自动的初始化为默认值(0或false或null)。
package Test;
/**
* 测试默认初始化的值
*
*/
public class Demo2 {
public static void main(String[] args) {
Student s = new Student();
System.out.println("name="+s.getName());
System.out.println("id="+s.getId());
System.out.println("isMan="+s.isMan());
}
}
class Student{
private String name;
private int id;
private boolean isMan;
public boolean isMan() {
return isMan;
}
public void setMan(boolean isMan) {
this.isMan = isMan;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
(三)无参数的构造器
很多类都包含一个无参数的构造器,对象由无参数构造器创建时,其状态会设置为适当的默认值。例如:
public Employee()
{
name="";
salary=0;
hireDay=LocalDate.now();
}
如果在编写一个类的时候,没有编写构造器,那么系统会提供一个无参数构造器。这个构造器将所有的实例域值都设置为默认值。
如果类中提供了至少一个构造器,但是没有提供无参构造器,那么在构造对象时,没有提供参数就视为不合法。
警告:当且仅当类没有提供任何构造器时,系统才会提供默认的无参构造器,但凡提供任何有参数的构造器的类,在创建对象时要想使用无参构造器必须得提供一个无参构造器。
(四)显示域初始化
通过重载类构造器方法,可以采用多种形式设置类的初始状态。确保不管怎样调用构造器,每个实例域都可以被设置为一个有意义的初值。
显示域初始化有两种方式:
①可以在类定义中直接将一个值赋给任何域。例如
class Employee{
private int dept =10;
...
}
②也可以调用方法对域进行初始化。例如:
class Employee
{
private static int nextId;
private int id = assignId();
...
private static int assignId(){
int r = nextId;
nextId++;
return r;
}
}
(五)调用另一个构造器
关键字this引用方法的隐式参数。然而,这个关键字还有另外一个含义。如果构造器的第一个语句形如this(...),这个构造器将调用同一个类的另一个构造器。例如:
public Employee(double s)
{
//calls Employee(String,double)
this("Employee #"+nextId,s);
nextId++;
}
当调用Employee(6000)时,Employee(double)构造器将调用Employee(String,double)构造器。
采用这种方法使用this关键字非常有用,这样对公共的构造器代码部分只编写一次即可。
(六)初始化块
前面提到过两种初始化数据域的方法:
①在构造器中设置
②在声明中赋值
实际上还有第三种机制,称为初始化块(initialization block),在一个类中可以包含多个代码块,只要构造类对象,这些块就会被执行。
class Employee{
private static int nextId;
private int id;
private String name;
private double salary;
//初始化块
{
id= nextId;
nextId++;
}
public Employee(String n,double s){
name=n;
salary=s;
}
public Employee(){
name="";
s=0;
}
}
在这个示例中,无论使用哪个构造器构造对象,id域都在对象初始化块中被初始化。首先运行初始化块,然后才运行构造器部分。
下面是调用构造器的具体处理步骤:
1)所有数据域被初始化为默认值(0,false,null)
2)按照在类声明中的次序,依次执行初始化语句和初始化块
3)如果构造器第一行调用了第二个构造器,则执行第二个构造器主体
4)执行这个构造器主体
如果对类的静态域进行初始化的代码比较复杂,那么可以使用静态的初始化块。例如给雇员Id赋值为一个小于10000的随机数:
static{
Random generator = new Random();
nextId = generator.nextInt(10000);
}
与实例域一样,在类加载的过程中,将会进行静态域初始化,除非将他们显示的设置成其他值,否则默认就是0,false,null,然后所有的静态初始化语句以及静态初始化块都将依次按照类定义的顺序执行。
package CoreJava;
import java.util.Random;
/**
* This program demonstrates object construction.
* 重载构造器
* 用this(...)调用另一个构造器
* 无参数构造器
* 对象初始化块
* 静态初始化块
* 实例域初始化
*/
public class ConstructorTest {
public static void main(String[] args) {
Employee4[] staff = new Employee4[3];
staff[0] = new Employee4("Harry",40000);
staff[1] = new Employee4(60000);
staff[2] = new Employee4();
for(Employee4 e : staff) {
System.out.println("name="+e.getName()+",id="+e.getId()+",salary="+
e.getSalary());
}
}
}
class Employee4{
private static int nextId;
private int id;
private String name="";//实例域初始化
private double salary;
//静态初始化块
static {
Random ran = new Random();
nextId = ran.nextInt(10000);//产生10000以内的随机数
}
//对象初始化块
{
id=nextId;
nextId++;
}
//下面是三个重载的构造器
public Employee4(String n,double s) {
name=n;
salary=s;
}
//使用this(...)调用另一个构造器
public Employee4(double s) {
this("Employee4 #"+nextId,s);
}
//默认无参构造器
public Employee4() {
//name 已经在上面实例域初始化
//salary 没有设置,默认即为0
//id 已经在对象初始化块中初始化了
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public int getId() {
return id;
}
}
(七)对象析构与finalize方法
析构器就是防止一些当对象不再使用时需要执行清理代码。在析构器中,最常见的操作是回收分配给对象的存储空间。由于Java有自动垃圾回收器,所以Java不支持析构器。
当然,也可以为任何一个类添加finalize方法,finalize方法将在垃圾回收器清除对象之前调用。在实际应用中,不要依赖于使用finalize方法回收任何短缺的资源,因为很难知道这个方法在声明时候调用。