Java_语法基础_执行初始化的构造器

构造器,也称构造方法,用来初始化类的实例变量,在使用new关键字创建对象的时候,由系统自动调用。构造器必须与类名相同,并且没有返回值,在外观上与类中声明的方法相似,例如,也可以具备形式参数、类型变量、异常列表等。然而,构造器不是方法,也不是类的成员。
另外,很多人认为构造器创建了对象,并且在构造器是否存在返回值的方面存有疑问,那事情是怎样的呢?在构造器中,我们还经常使用this关键字,可是,this到底是什么,从哪里来的呢?

构造器与方法成员的区别

尽管构造器与方法很相似,但是构造器不是方法,也不是类的成员。
首先,构造器不是方法。方法既可以在其声明的类中调用,也可以在类外调用(假设满足一定的访问权限),而构造器方法通常在使用new关键字创建对象的时候,由系统自动调用,如果显式调用构造器,则必须在同类或子类的另一个构造器中,使用this或super关键字调用,方法则是类或对象的引用(静态方法或实例方法),通过方法名称调用。
如果显式地使用this或super调用构造器,则this或super语句必须是构造器中的第1条语句,否则就会产生编译错误。由此可知,this与super只能有一个出现在同一个构造器中,正所谓一山不能容二虎,如果两个同时出现,则必有一个不能出现在构造器的第1条语句中,因此就会产生编译错误。
可以总结为:显式构造器调用语句(this或super)必须出现在构造器的第1条语句中。因为构造器的作用就是用来初始化类的实例成员变量的,如果允许先执行其他的代码,再执行构造器的调用,那只能算是对实例变量的“赋值”,又何来“初始化”而言?
例:

package deep;

public class ConstructorInvocation {
    public void m() {
        this();// 错误,只能在构造器中显式调用构造器
    }

    public ConstructorInvocation() {
        super();
        this(5);// 错误,必须是第一条语句
    }

    public ConstructorInvocation(int x) {
        super();
    }
}

其次,构造器也不是类的成员。类的成员可以由子类继承(如果访问权限满足的话),但是构造器不能由子类继承,即使构造器声明为public也是如此。其实,构造器就是在创建对象的时候,用来初始化实例成员变量的,子类可以有自己的成员变量,也有自己的初始化方法,继承父类的构造器没有意义。
然而,虽然构造器不会由子类继承,但是在子类的构造器中,如果第一条语句没有使用this来调用另外一个构造器,则会使用如下的语句隐式调用父类无参的构造器:

    super();

此时,如果父类中没有无参的构造器,就会发生编译错误。
例(构造器不会被子类继承):

package deep;

public class ConstructorNoInheritance {
    public static void main(String[] args) {
        AppleTree appleTree = new AppleTree(30);// 错误!构造器不能继承
    }

}

class Tree {
    private int height;

    public Tree(int height) {
        this.height = height;
    }
}

class AppleTree extends Tree {
    public AppleTree() {
        // 错误提示:Implicit super constructor Tree() is undefined. Must explicitly invoke another constructor
    }
}

子类AppleTree中的构造器没有使用this调用其他构造器,则会隐式地使用super调用父类无参的构造器,而父类中没有无参的构造器,因此会产生错误。

构造器创建了对象吗?

例:

package deep;

public class CreateObject {
    void f(){
        Object o=new Object();
    }

}

然后使用javap对CreateObject进行反编译,在命令行输入:
javap -c CreateObject

其中f()方法的反编译伪指令如下:

  void f();
    Code:
       0: new           #2                  // class java/lang/Object
       3: dup
       4: invokespecial #1                  // Method java/lang/Object."<init>":()V
       7: astore_1
       8: return

只要看第1条与第3条指令就可以了,其中第1条指令:

new           #2                  // class java/lang/Object

创建对象指令。根据索引(#2)所指向的常量池地址(该地址含有需要创建对象的类型信息)创建一个新对象(这里是Object),如果创建成功,将指向新创建对象的引用压入栈。
第3条指令:

invokespecial #1                  // Method java/lang/Object."<init>":()V

调用实例方法指令。根据索引(#1)所指向的常量池地址(该地址含有需要调用的实例方法信息)调用某实例方法或初始化方法(构造器)。其实,这里调用的初始化方法< init >就可以近似地认为是程序中类Object的构造器。
由此可知,对象是先创建,然后再调用构造方法初始化的。构造器是在对象创建的时候初始化类的实例成员,而不是创建了对象。

默认的构造器为空吗

如果我们在类中没有显式地声明构造器,则编译器会自动生成一个默认的构造器,该构造器的参数列表为空,访问权限与类的访问权限相同。可以使用javap反编译类或使用javadoc生成文档,来查看默认生成的构造器。
默认的构造器有什么用呢?有些人认为默认的构造器不执行任何操作。这是不正确的。如果什么事都不做,那编译器就没必要画蛇添足,做这些无用功。在上面提到过,如果一个构造器内没有使用this来调用同类中其他的构造器,则构造器的第1条语句就会使用super来调用父类的构造器,即使我们没有显式地使用super调用,编译器也会为我们补上这条语句。构造器递归的调用父类构造器,直到Object类为止。
因为构造器是创建对象时,用来初始化实例成员变量的,因此递归调用时必要的。因为继承关系的存在,子类可能会继承父类的某些成员,也就是父类的实例初始化应该在子类的实例初始化之前进行。因而,子类在构造器中需要首先调用父类的构造器来初始化父类的实例变量,然后再初始化自己的实例变量。
所以,如果类中没有声明构造器,编译器就会生成一个默认的构造器,该构造器并不是什么也不做,而是会首先调用父类的构造器,相当于语句:

    super();

也就是说,默认的构造器至少调用了父类的构造器。其实,默认的构造器所做的工作还可以更多,包括实例变量声明处初始化与实例初始化块,都是在构造器中执行的。

构造器的访问权限

如果构造器声明为public,则在任何位置都是可见的,在任何位置都可以创建该类的对象(假设构造器所在的类也声明为public)。如果生命为private,则只能在类的内部创建该类的对象。如果构造器是包访问权限的,则可以在本包的任何类中创建该类的对象。关键是声明为protected的构造器,protected与包访问权限相比,就是除了在包内可见外,对包外的子类也是可见的。可是,构造器不能由子类继承,那么protected的构造器是否与包访问权限的构造器相同呢?
不是的。protected的构造器依然有一定意义。尽管构造器不是类的成员,不能由子类继承,但是在子类的构造器中会使用super调用父类的构造器(如果没有使用this调用子类中其他的构造器),能否有权限调用父类的构造器,就在于父类构造器的访问权限,如果访问权限不足,就会产生编译错误。
例:

package deep.in;

public class PackageConstructor {
    PackageConstructor() {
    }
}

在deep.in包中,声明PackageConstructor类,并在类中声明一个包访问权限的构造器。

package deep.in;

public class ProtectedConstructor {
    protected ProtectedConstructor() {
    }
}

在deep.in包中,声明ProtectedConstructor类,并在类中声明一个protected的构造器。

package deep.out;

import deep.in.PackageConstructor;
import deep.in.ProtectedConstructor;

public class SubProtectedConstructor extends ProtectedConstructor {
    public SubProtectedConstructor() {
        super();
    }
}

// class SubPackageConstructor extends PackageConstructor{
//      public SubPackageConstructor(){
//          super();
//      }
// }

在deep.out包中,声明SubProtectedConstructor类,并继承deep.in包中的ProtectedConstructor类,然后在子类的构造器中调用父类的构造器,这是允许的,因为父类构造器的访问权限为protected,可以在子类中使用super来调用。
然而,如果取消最后五排的注释,将会产生编译错误。因为类PackageConstructor的构造器是包访问权限的,不能在包外使用,故不能在子类的构造器中调用父类的构造器。

要点总结:

  • 构造器不是方法,也不是类的成员。因此,子类不能继承父类的构造器。
  • 构造器是递归调用的,子类的构造器会调用父类的构造器,直到调用到Object类的构造器为止。
  • 构造器没有创建对象,构造器是使用new创建对象时由系统自动调用的,用来初始化类的实例成员。从顺序上来说,是先创建对象,然后才调用构造器的。
  • 当类中没有显式地声明构造器时,编译器会自动添加一个无参的构造器,该构造器的访问权限与类的访问权限相同。默认的构造器并不为空,该构造器会调用父类的无参构造器,并可能执行实例成员变量的初始化。
  • protected构造器与包访问权限构造器是不同的,前者可以在子类的构造器中使用super来调用,而后者不能。
  • 在构造器或是实例方法调用的时候,会将其关联的对象作为第1个参数隐式传递,这个对象就是我们在构造器或实例方法中使用的当前对象this,静态方法没有关联对象,因此也不会隐式传递对象。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值