继承

继承(inheritance)是面向对象的重要概念。继承是除 组合(composition)之外,提高代码重复可用性(reusibility)的另一种重要方式。




类的继承

我们之前定义了 Human 类,现在我们定义一个 Woman 类,让它继承自 Human 类:

class Human
{   
    public int getHeight()
    {
       return this.height;
    }

    public void growHeight(int h)
    {
        this.height = this.height + h;
    }

    private int height; 
}


// woman 类继承自 Human 类
class Woman extends Human
{
    // woman 类新增的方法
    public Human giveBirth()
    {
        System.out.println("Give birth");
        return (new Human());
    }
}

在 Java 中使用 extends 关键字来表示继承;现在 Woman 类既有了原先 Human 类所有 public 成员的方法,也有自身新增的 giveBirth() 方法。

这样,我们就省去了大量的输入。通过继承,我们创建了一个新类,叫做 衍生类 (derived class)。被继承的类(Human)称为 基类(base class)。衍生类以基类作为自己定义的基础,并补充基类中没有定义的 giveBirth() 方法。

可以用以下 Test 类测试:

public class Test
{
    public static void main(String[] args)
    {
        Woman aWoman = new Woman();
        aWoman.growHeight(120);
        System.out.println(aWoman.getHeight());                            
    }
}

输出:

120




衍生层

通过继承,我们创建了 Woman 类。整个过程可以分为三个层次:基类定义,衍生类定义,外部使用。

基类定义的层次就是正常的定义一个类,比如上面的Human类定义。

在外部使用者看来(比如 Test 类中创建 Woman 类对象),衍生类有一个统一的外部接口:

// Woman 对象拥有的外部接口
getHeight()
growHeight()
giveBirth()

对于外部使用者来说,上述接口就已经足够了。仅从接口看,衍生类也没有什么特别之处。

然而,当程序员在衍生类定义的层次时,就必须要小心:

首先,接口是混合的:getHeight() 方法和 growHeight() 方法来自基类,giveBirth() 方法则是在衍生类内部定义的。

还有更加复杂的地方。我们之前在类的内部可以自由访问类的成员(利用 this指代对象)。然而,当我们在 Woman 类的定义范围内,我们无法访问基类 Humanprivate成员(比如 height)。

我们记得 private 的含义:private 的成员仅供该类内部使用。Woman 类是一个不同于 Human 类的新类,所以位于 Human 类的外部。在衍生类中,不能访问基类的 private 成员。

但有趣的是,我们的 growHeight()getHeight() 方法依然可以运行。这说明基类的 private 成员存在,我们只是不能直接访问。

为了清晰概念,我们需要了解衍生类对象的生成机制。当我们创建一个衍生类的对象时,Java 实际上先创建了一个 基类对象(subobject),并在基类对象的外部(注意,这里是基类对象的外部,衍生类对象的内部),增加衍生类定义的其他成员,构成一个衍生类对象。外部使用者能看到的,就是基类和衍生类的 public 成员。

如下图:

图中内框部分(深绿色和浅绿色部分)表示基类对象。基层的成员之间可以互相访问(利用 Human 类定义中的 this 指代基类对象)。

图中外框部分(浅蓝色和深蓝色部分)为衍生对象新增的内容,我将这部分称为衍生层;蓝色和黄色部分共同构成衍生对象。

衍生层的成员可以相互访问(Woman 定义中的 this)。更进一步,我们还可以访问基层中 public 的成员。为此,我们用 super 关键字来指代基类对象,使用 super.member 的方式来表示基层的 public 成员。

当我们位于衍生层时(也就是在定义 Woman 类时),不能访问深绿色的基层 private 成员。当我们位于外部时,既不能访问深蓝色的衍生层 private 成员,也不能访问深绿色的基层 private 成员。

superthis 类似,也是隐式参数。我们在类定义的不同层次时,this 会有不同的含义。要小心的使用 thissuper 关键字。

Java 并不强制使用 thissuper。Java 在许多情况下可以自动识别成员的归属。但我觉得这是个好习惯。




protected

标为 protected 的成员在该类及其衍生类中可见。这个概念很容易理解,就是说,基类的 protected 成员可以被衍生层访问,但不能被外部访问,如下图:




方法覆盖

衍生类对象的外部接口最终由基类对象的 public 成员和衍生层的 public 成员共同构成。如果基类 public 成员和衍生层的 public 成员同名,Java 接口中呈现的究竟是哪一个呢?

我们在构造方法与方法重载中已经提到,Java 是同时通过方法名和参数列表来判断所要调用的方法的。方法是由方法名和参数列表共同决定的。上述问题中,如果只是方法名相同,而参数列表不同,那么两个方法会同时呈现到接口,不会给我们造成困扰。外部调用时,Java 会根据提供的参数,来决定使用哪个方法 。

如果方法名和参数列表都相同呢? 在衍生层时,我们还可以通过 superthis 来确定是哪一个方法。而在外部时,我们呈现的只是统一接口,所以无法同时提供两个方法。这种情况下,Java 会呈现衍生层的方法,而不是基层的方法。

这种机制叫做 方法覆盖(method overriding)。方法覆盖可以被很好的利用,用于修改基类成员的方法。比如,在衍生层,也就是定义 Woman 时,可以修改基类提供的 breath() 方法:

class Human
{   
    public int getHeight()
    {
       return this.height;
    }

    public void growHeight(int h)
    {
        this.height = this.height + h;
    }

    public void breath()
    {
        System.out.println("hu...hu...");
    }

    private int height; 
}


// woman 类继承自 Human 类
class Woman extends Human
{
    // woman 类新增的方法
    public Human giveBirth()
    {
        System.out.println("Give birth");
        return (new Human());
    }

    // 方法覆盖
    public void breath()
    {
        System.out.println("su...");
    }
}


public class Test
{
    public static void main(String[] args)
    {
        Woman aWoman = new Woman();
        aWoman.growHeight(120);
        aWoman.breath();
    }
}

输出结果:

su...

基类的 breath() 方法被 Woman 类的 breath() 方法覆盖了。

在这里我们也演示下 super 的用法:

// woman 类继承自 Human 类
class Woman extends Human
{

    // woman 类新增的方法
    public Human giveBirth()
    {
        System.out.println("Give birth");
        return (new Human());
    }

    // 方法覆盖
    public void breath()
    {
        super.breath();  // 调用基类 Human 的 breath() 方法
        System.out.println("su...");
    }
}


public class Test
{
    public static void main(String[] args)
    {
        Woman aWoman = new Woman();
        aWoman.growHeight(120);
        aWoman.breath();
    }
}

输出结果:

hu...hu...
su...

当我们位于衍生层,依然可以通过 super 来调用基类对象的 breath() 方法。




衍生类的构造方法

我们要在衍生类的定义中定义与类同名的构造方法。在该构造方法中:

  • 由于在创建衍生对象的时候,基类对象先被创建和初始化,所以,基类的构造方法应该先被调用。我们可以使用 super(argument list) 的语句,来调用基类的构造方法。

  • 基类对象创建之后,开始构建衍生层(初始化衍生层成员)。这和一般的构建方法相同。

class Human
{   
    // 构造器
    public Human(int h)
    {
        this.height = h;
    }

    public int getHeight()
    {
       return this.height;
    }

    public void growHeight(int h)
    {
        this.height = this.height + h;
    }

    public void breath()
    {
        System.out.println("hu...hu...");
    }

    private int height; 
}


// woman 类继承自 Human 类
class Woman extends Human
{
    // 构造器
    public Woman(int h)
    {
        // 调用基类构造器
        // 相当于执行 human(h)
        super(h); 
        // 再执行衍生类新增的构造器方法
        System.out.println("Hello, Pandora!");
    }

    // woman 类新增的方法
    public Human giveBirth()
    {
        System.out.println("Give birth");
        return (new Human(60));
    }

    // 方法覆盖
    public void breath()
    {
        super.breath();
        System.out.println("su...");
    }
}


public class Test
{
    public static void main(String[] args)
    {
        Woman aWoman = new Woman(100);
        System.out.println(aWoman.getHeight());
    }
}

输出结果:

Hello, Pandora!
100

你可能感兴趣的:(继承)