问题简介

  今天在看《Java编程思想》的时候,看到了一个很特殊的语法,懵逼了半天——一个派生类继承自一个内部类,想要创建这个派生类的对象,首先得创建其父类的对象,也就是这个内部类,而调用内部类的构造方法创建其对象的语法,是外部类对象.super();


问题分析

  我们都知道,在Java当中,当我们创建一个类的对象时,在构造方法的第一行会默认的调用父类的构造方法,创建一个父类的对象,并用super关键字引用父类的对象。所以若一个类,它继承了一个内部类,那我们创建这个类的对象前,当然也必须创建一个其父类的对象,也就是这个内部类的对象。

  但是,麻烦的是,对于内部类来说,有一个规则,那就是每个内部类的对象,必定要绑定一个其外部类的对象,这就是在内部类中,能够调用外部类方法和操作外部类属性的原因。在我们平时创建内部类对象的时候,首先需要创建一个外部类对象,在使用 外部类对象.new 内部类() 语法来创建内部类对象,这时候内部类对象绑定的就是创建它的外部类对象。

  所以,当我们有一个类,它继承自一个内部类的时,我们要创建它的对象,需要满足两个条件:

  1. 在创建对象前,要先创建好它的父类对象,也就是它继承的内部类对象;
  2. 想要创建内部类对象,你得先有这个内部类的外部类对象,以供他绑定;

  于是出现了下面这种让人懵逼的代码:


代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 外部类
class Outer{
// 内部类
class Inner{

// 默认构造方法
Inner(){
}
// 带参构造方法
Inner(String string){
}
}
}

// 继承类内部类的类
public class Test01 extends Outer.Inner{
// 方式1:创建一个外部类Outer的对象,用来提供创建内部类所需的条件
private static Outer outer2 = new Outer();

// 方式2:类的构造方法传入一个外部类的对象,用来提供创建内部类所需的条件
public Test01(Outer outer1){
// 通过外部类对象.super()调用内部类的构造方法
outer1.super("调用Inner的带参数构造方法");
//也可以通过outer2调用
// outer2.super("调用Inner的带参数构造方法");
}

public static void main(String[] args) {
// 创建一个外部类的对象
Outer outer = new Outer();
// 创建内部类的派生类对象,传入外部类的引用
Test01 test01 = new Test01(outer);
}
}

代码解读

  在上面的代码中我们可以看到,Test01继承了一个内部类,于是在它的构造函数中,我们需要给他提供一个外部类Outer的对象,使它在可以满足问题分析中所说的条件2。上面的代码使用了两种方式来提供外部类的对象:

  1. 将外部类对象作为构造方法的参数传递进来,也就是上面代码中的outer1;
  2. 为类创建一个静态的外部类成员,也就是上面代码中的outer2;

  提供了外部类Outer的对象后,创建Test01的对象时,需要一同创建的父类对象(也就是Inner)就有了可以绑定的外部类对象。然而存在一个问题,平常我们创建一个子类的对象时,构造方法中第一行会自动调用父类的构造方法,不许要我们写,但是这里却不行。因为这里的父类是一个内部类,这也就意味着编译器并不知道你想用哪个外部类对象去创建这个内部类的对象,你需要自己指定内部类绑定的外部类对象。于是,就有了上面的代码:使用 外部类对象.super(参数) 调用内部类的构造函数,创建内部类的对象,且这个内部类对象绑定的外部类对象就是调用构造函数的内部类对象。

  上面的代码中有两个外部类Outer的对象,使用哪个外部类对象,创建Test01对象时,一同创建的Inner对象绑定的就是哪个outer。除此之外,还有一个问题,若构造方法中有多行代码,那外部类.super语句一定得在第一行,这和创建普通类时super语句要在第一行类似。这也很好理解,因为父类的对象一定要在子类对象之前创建


总结

  总之最重要的就是记住,一个类继承了内部类时,在这个类中想要调用父类的构造方法,得使用外部类对象.super(参数)语法,且必须显示的写出来,编译器不会自己帮你加,否则将无法成功创建类的对象。


参考文献

《Java编程思想》