构造器是一个特殊的方法,这个特殊方法用于创建实例时执行初始化。 构造器是创建对象的重要途径(即使使用工厂模式、反射等方式创建对象,其实质还是依赖于构造器),因此,Java类必须包含一个或一个以上的构造器。
一、使用构造器执行初始化
构造器最大的用处就是在创建对象时执行初始化。创建一个对象时,系统为这个对象的实例变量进行默认初始化,这个默认的初始化把所有基本类型的实例变量设为0(对数值型实例变量)或false(对布尔型实例变量),把所有引用类型的实例变量设为null。
如果向改变这种默认的初始化,想让系统创建对象时就为该对象的实例变量显示指定初始化,就可以通过构造器来实现。
注:如果程序员没有为Java类提供任何构造器,则系统会为这个类提供一个无参数的构造器,这个构造器执行体为空,不做任何事情。
下面看一个例子:
public class ConstructorTest
{
public String name;
public int count;
//提供自定义的构造器,该构造器包含两个参数
public ConstructorTest(String name , int count)
{
//构造器this代表他进行初始化的对象
this.name = name;
this.count = count;
}
public static void main(String[] args)
{
//使用自定义的构造器来创建对象
//系统将会对该对象执行自定义的初始化
ConstructorTest tc = new ConstructorTest("Java",1);
//输出ConstructorTest对象的name和count两个实例变量
System.out.println(tc.name);
System.out.println(tc,count);
}
}
运行上面程序,可以看到,输出ConstructorTest对象时,它的name实例变量是Java,count实例变量时0。
问:构造器是创建对象的途径,是不是说构造器完全负责创建Java对象?
答:不是。构造器是创建Java对象的重要途径,通过new关键字调用构造器时,构造器也返回了该类的对象,但这个对象不是完全由构造器负责创建的。 实际上,当程序在调用构造器时,系统会先为该对象分配内存空间,并为这个对象执行默认初始化,这个对象已经产生了 —— 这些操作在构造器执行子安就都完成了。也就是说,当系统开始执行构造器的执行体之前,系统已经创建了一个对象,只是这个对象还不能被外部系统访问。只能在该构造器中通过this来引用。当构造器的执行体执行结束后,这个对象作为构造器的返回值被返回,通常还会赋给另一个引用类型的变量,从而让外部程序可以访问该变量。
一旦程序员提供了自定义的构造器,系统就不再提供默认的构造器。因此上面的ConstructorTest类不能再通过new ConstructorTest();代码来创建实例,因为该类不再包含无参数的构造器。
如果希望保留无参数的构造器,或者希望有多个初始化过程,则可以为该类提供多个构造器。如果一个类中提供了多个构造器,就形成了构造器的重载。
因为构造器主要用于被其他方法调用,用以返回该类的实例,因而把构造器设置成public访问权限,从而允许系统中任何位置的类来创建该类的对象。除非在一些极端的情况下,业务需要限制创建该类的对象,可以把构造器设置成其他访问权限,例如protected,主要用于被其子类调用。
二、构造器重载
同一个类里具有多个构造器,多个构造器的形参列表不同,即成为构造器的重载。构造器重载允许Java类里包含多个初始化逻辑,从而允许使用不同的构造器来初始化Java对象。
构造器重载和方法重载基本相似:要求构造器的名字相同。这一点无须特别要求,因为构造器必须与类名相同,所以同一个类的所有构造器名看到相同。为了让系统区分不同的构造器,多个构造器的参数列表必须不同。
下面的Java类示范了构造器重载,利用构造器重载就可以通过不同的构造器来创建Java对象。
public class ConstructorOverload
{
public String name;
public int count;
//提供无参数的构造器
public ConstructorOverload() {}
//提供带两个参数的构造器
//对该构造器返回的对象执行初始化
public ConstructorOverload(String name,int count)
{
this.name = name;
this.count = count;
}
public static void main(String[] args)
{
//通过无参数构造器创建ConstructorOverload对象
ConstructorOverload ocl = new ConstructorOverload();
//通过有参数构造器创建ConstructorOverload对象
ConstructorOverload oc2 = new ConstructorOverload("java" , 1);
System.out.println(oc1.name + " " + oc1.count);
System.out.println(oc2.name + " " + oc2.count);
}
}
上面ConstructOverload类提供了两个重载的构造器,两个构造器的名字相同,但形参列表不同,系统通过new调用构造器时,系统将根据传入的实参列表来决定调用哪个构造器。
如果系统中包含了多个构造器,其中一个构造器的执行体里完全包含了另一个构造器的执行体。
构造器A
代码行一
代码行二
代码行三
构造器B
代码行一
代码行二
代码行三
代码行四
可以看出,构造器B完全包含了构造器A。对于这种完全包含的情况,如果是两个方法之间存在这种关系,则可在方法B中调用方法A。但构造器不能直接被调用,调用构造器必须用new 关键字来调用构造器,这会导致系统重新创建一个对象。为了在构造器B中调用构造器A中的初始化代码,又不会重新创建一个Java对象,可以使用this关键字来调用相应的构造器。
下面代码实现了在一个构造器中直接使用另一个构造器的初始化代码
public class Apple
{
public String name;
public String color;
public double weight;
public Apple() {}
//两个参数的构造器
public Apple(String name,String color)
{
this.name = name;
this.color = color;
}
//三个参数的构造器
public Apple(String name,String color ,double weight)
{
//通过this调用另一个重载的构造器的初始化代码
this(name , color);
//下面this引用该构造器正在初始化的Java对象
this.weight = weight;
}
}
上面的Apple类包含了三个构造器,其中第三个构造器通过this来调用另一个重载构造器的初始化代码。程序中this(name,color);调用表明调用该类另一个带两个字符串参数的构造器。
使用this调用另一个重载构造器只能在构造器中使用,而且必须作为构造器执行体的第一条语句。使用this调用重载构造器时,系统会根据this后括号里的实参调用形参列表与之对应的构造器。
为什么要用this来调用另一个重载的构造器?我把另一个构造器里的代码复制、粘贴到这个构造器里不就可以了吗?
答:如果仅仅从软件功能的角度上来看,这样复制、粘贴确实可以实现这个效果;但从软件工程的角度来看,这样做事相当糟糕的。在软件开发有一个规则,不要把相同的代码书写两次以上!因为软件是一个需要不断更新的产品,如果有一天需要更新上面构造器A中的初始化代码,假设构造器B、构造器C……里都包含了相同的初始化块代码,则需要同时打开构造器A、构造器B、构造器C…..的代码进行修改;反之,如果构造器B、构造器C……是通过this调用了构造器A的初始化代码,则只需要打开构造器A进行修改即可,尽量避免相同的代码重复出现,充分利用每一段代码,既可以让程序更加简洁,也可以降低软件的维护成本。