构造器的声明
修饰符 class_name(类名) (参数列表){
逻辑代码
}
构造器的特性
* JAVA采取了与类同名的方法命名构造器。
* 构造器只有在对象被建时被调用,且只会调用一次
* 在JAVA中,每个类至少有一个构造方法(接口不能被实例化,所以接口中没有构造方法)。如果类中没有提供任何构造方法,那么JAVA将自动提供一个隐含的默认无参构造方法。该构造方法没有参数,用public修饰,而且方法体为空,格式如下:
public ClassName(){}
默认无参构造方法将所有的成员变量初始化为默认值,即数值型初始化为0,布尔型初始化为false,对象变量初始化为null。
* 如果类中显式定义了一个或多个构造方法,并且所有的构造方法都带参数,那么这个类就失去了默认构造方法
注:有有参数构造方法的类最好加一个无参数的构造方法
注:如果希望所有域初始化为默认值,只需要提供一个默认无参构造方法即可
public ClassName(){}
* 构造器总是伴随着new操作符的执行被调用,而不能对一个已经存在的对象调用构造器来达到重新设置实例域的目的
* 构造方法只能通过以下方式被调用:
A. 当前类的其他构造方法通过this语句调用它。
B. 当前类的子类的构造方法通过super语句调用它。
C. 在程序中通过new语句调用它。
构造器的作用
JAVA中引入构造器是为了确保每一个对象都得到初始化。当创建一个对象时,系统对该对象的属性进行默认初始化,JAVA通过使用构造器改变这种默认的初始化。
通过new关键字创建JAVA对象时,JAVA会调用构造器并获得构造器返回的该类的对象,但这个对象并不是完全由构造器负责创建的。调用构造器前,系统已经为该对象分配内存空间,并为这个对象执行默认初始化,因此实际上调用构造器前,系统已经创建了一个对象,只是这个对象还不能被外部程序访问,只能在该构造器中通过this来引用,当构造器的执行体执行结束后,这个对象作为构造器的返回值被返回,通常还会赋给另一个引用类型的变量,从而让外部程序访问该对象。
* 构造器仅仅可以通过public、private、protected这三个修饰符修饰或不加修饰符修饰,构造器不能被static、final、synchronized、abstract和native修饰,也就是说构造器不允许被成名成抽象、同步、静态等等访问限制以外的形式。
当构造方法被private修饰时,意味着只能在当前类中访问它:在当前类的其他构造方法中可以通过this语句调用它,此外还可以在当前类的成员方法中通过new语句调用它。
注:在以下场合之一,可以把类的所有构造方法都声明为private类型
A. 这个类中仅仅包含了一些供其他程序调用的静态方法,没有任何实例方法。其他程序无需创建该类的实例,就能访问类的静态方法。
B. 禁止这个类被继承。当一个类的所有构造方法都是private类型,假如定义了它的子类,那么子类的构造方法无法调用父类的任何构造方法,因此会导致编译错误。把一个类声明为final类型,也能禁止这个类被继承。这两者的区别是:
1)如果一个类允许其他程序用new语句构造它的实例,但不允许拥有子类,那就把类声明为final类型。
2)如果一个类既不允许其他程序用new语句构造它的实例,又不允许拥有子类,那就把类的所有构造方法声明为private类型。
由于大多数类都允许其他程序用new语句构造它的实例,因此用final修饰符来禁止类被继承的做法更常见。
C. 这个类需要把构造自身实例的细节封装起来,不允许其他程序通过new语句创建这个类的实例,这个类向其他程序提供了获得自身实例的静态方法,这种方法称为静态工厂方法。
public class SingletonClass{
private static SingletonClass instance=null;
public static SingletonClass getInstance()
{
if(instance==null)
{
synchronized(SingletonClass.class)
{
if(instance==null)
instance=new SingletonClass();
}
}
return instance;
}
private SingletonClass(){
}
}
构造器重载
在一个构造器中可以调用另一个构造器,通过this关键字实现。
class Employee {
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age=age;
}
public Employee(String name) {
this(name, -1);
}
}
注1:假如在一个构造方法中使用了this语句,那么它必须作为构造方法的第一条语句(不考虑注释语句)。
注2:只能在一个构造方法中用this语句来调用类的其他构造方法,而不能在实例方法中用this语句来调用类的其他构造方法。
注3:只能用this语句来调用其他构造方法,而不能通过方法名来直接调用构造方法。
注4:使用this()调用重载构造器不能同时相互调用,避免陷入死循环
构造器与普通方法的区别
* 构造器不是函数,所以没有返回值的,也不允许有返回值。但是这里要说明一下,构造器中允许存在return语句,但是return什么都不返回,如果你指定了返回值,虽然编译器不会报出任何错误,但是JVM会认为他是一个与构造器同名的函数罢了,这样就会出现一些莫名其妙的无法找到构造器的错误
class Sample {
private int x;
//不是构造方法
public int Sample(int x) {
return x++;
}
}
* 构造方法可以调自身类中的成员方法,但该方法恤声明为private。构造方法在运行时,是被某个正在创建的对象调用的。也就是说明当前这个对象在堆已经存在,只是正在进行初始化动作,因此可以通过这个对象调用自身的成员方法。同时,假使父类构造器中要调用一个非静态方法,而这个方法不是private的又被子类所重载,这样在实际创建子类的过程中递归调用到了父类的构造器时,父类构造器对这个方法的调用就会由于多态而实际上调用了子类的方法,当这个子类方法需要用到子类中实例变量的时候,就会由于变量没有初始化而出现异常。而当父类构造器中调用的方法是一个private方法时,多态就不会出现,也就不会出现父类构造器调用子类方法的情况,这样可以保证父类始终调用自己的方法,即使这个方法中调用了父类中的实例变量也不会出现变量未初始化的情况(变量初始化总是在当前类构造器主体执行之前进行)。
class Base{
public Base(){
print();
}
public void print() {
System.out.println("调用了print()方法");
}
}
* 构造器不能被继承,因此在写子类的构造器时,即使子类构造器参数和父类的完全一样,子类也要通过super关键字调用父类构造器
注1:在子类的构造方法中,不能直接通过父类方法名调用父类的构造方法,而是要使用super语句。
注2:使用super语句调用父类构造器时,必须放在子类构造方法的第一行。
* 构造器和成员方法使用关键字this有很大的区别。非静态成员方法引用this指向正在执行方法的类的实例。静态方法不能使用this关键字,因为静态方法不属于类的实例。构造器的this指向同一个类中,不同参数列表的另外一个构造器。
* 构造器和成员方法都用关键字super指向超类,但是用的方法不一样。方法用这个关键字去执行被重载的超类中的方法。构造器使用super去调用超类中的构造器。而且这行代码必须放在第一行,否则编译将出错。
构造器的执行顺序
类变量(静态变量)、实例变量(非静态变量)、静态代码块、非静态代码块的初始化顺序:先初始化父类的静态代码(按代码书写顺序执行)--->初始化子类的静态代码(按代码书写顺序执行)-->初始化父类的非静态代码(按代码书写顺序执行)--->初始化父类构造函数--->初始化子类非静态代码(按代码书写顺序执行)--->初始化子类构造函数
注:JAVA中静态代码是在字节码文件在最开始加载时完成的
注:静态初始化块可用于对静态域的初始化
在创建子类的对象时,JVM首先执行父类的构造方法,然后再执行子类的构造方法。在多级继承的情况下,将从继承树的最上层的父类开始,依次执行各个类的构造方法,这可以保证子类对象从所有直接或间接父类中继承的实例变量都被正确的初始化。
class Creature
{
{
System.out.println("Creature的非静态初始化块");
}
// 下面定义两个构造器
public Creature()
{
System.out.println("Creature无参数的构造器");
}
public Creature(String name)
{
// 使用this调用另一个重载的、无参数的构造器
this();
System.out.println("Creature带有nmae参数的构造器,name参数:" + name);
}
}
class Animal extends Creature
{
{
System.out.println("Animal的非静态初始化块");
}
public Animal(String name)
{
super(name);
System.out.println("Animal带一个参数的构造器,name参数:" + name);
}
public Animal(String name , int age)
{
// 使用this调用另一个重载的构造器
this(name);
System.out.println("Animal带2个参数的构造器,其age:" + age);
}
}
class Wolf extends Animal
{
{
System.out.println("Wolf的非静态初始化块");
}
public Wolf()
{
// 显式调用父类的带2个参数的构造器
super("灰太狼", 3);
System.out.println("Wolf无参数的构造器");
}
public Wolf(double weight)
{
// 使用this调用另一个重载的构造器
this();
System.out.println("Wolf的带weight参数的构造器,weight参数:" + weight);
}
}
public class Test
{
public static void main(String[] args)
{
new Wolf(5.6);
}
}
运行结果:
Creature的非静态初始化块
Creature无参数的构造器
Creature带有nmae参数的构造器,name参数:灰太狼
Animal的非静态初始化块
Animal带一个参数的构造器,name参数:灰太狼
Animal带2个参数的构造器,其age:3
Wolf的非静态初始化块
Wolf无参数的构造器
Wolf的带weight参数的构造器,weight参数:5.6
注1:当子类的构造方法没有用super语句显式调用父类的构造方法,那么通过这个构造方法创建子类对象时,JAVA虚拟机会自动先调用父类的默认构造方法。
注2:当子类的构造方法没有用super语句显式调用父类的构造方法,而父类又没有提供默认构造方法时,将会出现编译错误