Java基础教程之继承详解_java

继承(inheritance)是面向对象的重要概念。继承是除组合(composition)之外,提高代码重复可用性(reusibility)的另一种重要方式。我们在组合(composition)中看到,组合是重复调用对象的功能接口。我们将看到,继承可以重复利用已有的类的定义。

类的继承

我们之前定义类的时候,都是从头开始,详细的定义该类的每一个成员。比如下面的Human类:

复制代码 代码如下:

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

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

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

    private int height;
}

从上面的类定义,我们可以了解该类的所有细节: 该类的数据成员,该类的方法,该类的接口。

现在要定义一个新的类,比如Woman类,并假设Woman与Human类相当类似:


Human & Woman

我们可以像以前一样,从头开始,完整的定义Woman类:

复制代码 代码如下:

class Woman
{
    /**
     * accessor
     */
    public int getHeight()
    {
       return this.height;
    }

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

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

    /**
     * new method
     */
    public Human giveBirth()
    {
        System.out.println("Give birth");
        return (new Human(20));
    }

    private int height;
}

一个程序员在写上面程序的时候,会有很大的烦恼。许多定义都曾在Human类中写过,但我们还要重新敲一遍。Woman类只新增了一个giveBirth()方法 (该方法创建并返回一个新的Human对象)。

利用继承,我们可以避免上面的重复。让Woman类继承自Human类,Woman类就自动拥有了Human类中所有public成员的功能。

我们用extends关键字表示继承:

复制代码 代码如下:

class Woman extends Human
{
    /**
     * new method
     */
    public Human giveBirth()
    {
        System.out.println("Give birth");
        return (new Human(20));
    }
}

这样,我们就省去了大量的输入。通过继承,我们创建了一个新类,叫做衍生类(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());                                            
    }
}

衍生层

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

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

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

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

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

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

还有更加复杂的地方。我们之前在类的内部可以自由访问类的成员(利用this指代对象)。然而,当我们在Woman类的定义范围内,我们无法访问基类Human的private成员。我们记得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成员。

(衍生层的private成员有访问禁忌,所以标为斜线。基层的private成员访问禁忌最多,所以标为交叉斜线)

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

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

protected

我们之前介绍了两个访问权限相关的关键字,private和public,它们控制了成员的外部可见性。现在,我们介绍一个新的访问权限关键字: protected。

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

方法覆盖

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

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

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

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

复制代码 代码如下:

class Woman extends Human
{/**
     * new method
     */
    public Human giveBirth()
    {
        System.out.println("Give birth");
        return (new Human(20));
    }

    /**
     * override Human.breath()
     */
    public void breath()
    {
        super.breath();
        System.out.println("su...");
    }
}

注意,此时我们位于衍生层,依然可以通过super来调用基类对象的breath()方法。当我们外部调用Woman类时,由于方法覆盖,就无法再调用基类对象的该方法了。

方法覆盖保持了基类对象的接口,而采用了衍生层的实现。

构造方法

在了解了基类对象和衍生层的概念之后,衍生类的构造方法也比较容易理解。

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

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

2.基类对象创建之后,开始构建衍生层 (初始化衍生层成员)。这和一般的构建方法相同,参考构造方法与方法重载

比如下面的程序中,Human类有一个构造方法:

复制代码 代码如下:

class Human
{  

    /**
     * constructor
     */
    public Human(int h)
    {
        this.height = h;
    }

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

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

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

    private int height;
}

衍生类Woman类的定义及其构造方法:

复制代码 代码如下:

class Woman extends Human
{
    /**
     * constructor
     */
    public Woman(int h)
    {
        super(h); // base class constructor
        System.out.println("Hello, Pandora!");
    }

    /**
     * new method
     */
    public Human giveBirth()
    {
        System.out.println("Give birth");
        return (new Human(20));
    }

    /**
     * override Human.breath()
     */
    public void breath()
    {
        super.breath();
        System.out.println("su...");
    }
}

总结

extends

method overriding

protected

super.member, super()

时间: 2016-08-31

Java基础教程之继承详解_java的相关文章

基于Java中字符串内存位置详解_java

前言 之前写过一篇关于JVM内存区域划分的文章,但是昨天接到蚂蚁金服的面试,问到JVM相关的内容,解释一下JVM的内存区域划分,这部分答得还不错,但是后来又问了Java里面String存放的位置,之前只记得String是一个不变的量,应该是要存放在常量池里面的,但是后来问到new一个String出来应该是放到哪里的,这个应该是放到堆里面的,后来又问到String的引用是放在什么地方的,当时傻逼的说也是放在堆里面的,现在总结一下:基本类型的变量数据和对象的引用都是放在栈里面的,对象本身放在堆里面,

Java中Properties的使用详解_java

Java中有个比较重要的类Properties(Java.util.Properties),主要用于读取Java的配置文件,各种语言都有自己所支 持的配置文件,配置文件中很多变量是经常改变的,这样做也是为了方便用户,让用户能够脱离程序本身去修改相关的变量设置.今天,我们就开始Properties的使用. Java中Properties的使用 Properties的文档说明: The Properties class represents a persistent set of propertie

Java静态方法不具有多态性详解_java

动态绑定机制使得基类的引用能够指向正确的子类对象,从而使得面向基类编程成为可能. 然而动态绑定在以下两种情况会失效. 1.基类方法是private或final修饰的 这个很好理解,因为private说明该方法对子类是不可见的,子类再写一个同名的方法并不是对父类方法进行复写(Override),而是重新生成一个新的方法,也就不存在多态的问题了.同理也可以解释final,因为方法同样是不可覆盖的. 2.方法是static修饰的 代码如下所示. class Base { public static v

Java 23种设计模型详解_java

设计模式(Design Patterns)                                   --可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样.项目中合理的运用设计模式可以完美的解决很多问题,每

5种Java经典创建型模式详解_java

一.概况 总体来说设计模式分为三大类: (1)创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. (2)结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元模式. (3)行为型模式,共十一种:策略模式.模板方法模式.观察者模式.迭代子模式.责任链模式.命令模式.备忘录模式.状态模式.访问者模式.中介者模式.解释器模式. 二.设计模式的六大原则 1.开闭原则(Open Close Principle) 开闭原则就是说对扩展开放,对修

java equals和==的区别详解_java

大概说equals和==都比较的是什么: 1. boolean tem = a == b; 首先==比较的肯定是地址,从堆栈的角度说也就是说==比较的是栈上面的内容.因为栈是用来存放地址或是java中八大基本类型中自动变量的字面值(自动变量就是用int a = 1;这种形式来定义的变量).如果是自动变量比较值的话肯定是用==来比较,因为equals()是一个方法,所以必须由对象调用才可以用于比较.而自动变量既不是类的实例也不是类的引用所以不能用equals()方法. 2.boolean tem

Java中Map的排序问题详解_java

Map的种类 在Java中,Map的主要作用是存储键值对.由于是根据键得到值,所以不允许键重复.它主要有如下几个类别: HashMap: 最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的.HashMap最多只允许一条记录的键为Null;允许多条记录的值为Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致.如果需要同步,可以用Collections的sy

Java中JSON字符串与java对象的互换实例详解_java

在开发过程中,经常需要和别的系统交换数据,数据交换的格式有XML.JSON等,JSON作为一个轻量级的数据格式比xml效率要高,XML需要很多的标签,这无疑占据了网络流量,JSON在这方面则做的很好,下面先看下JSON的格式, JSON可以有两种格式,一种是对象格式的,另一种是数组对象, {"name":"JSON","address":"北京市西城区","age":25}//JSON的对象格式的字符串 [

Java 中Map 的用法详解_java

Map简介 将键映射到值的对象.一个映射不能包含重复的键:每个键最多只能映射到一个值.此接口取代 Dictionary 类,后者完全是一个抽象类,而不是一个接口. Map 接口提供三种collection 视图,允许以键集.值集或键-值映射关系集的形式查看某个映射的内容.映射顺序 定义为迭代器在映射的 collection 视图上返回其元素的顺序.某些映射实现可明确保证其顺序,如 TreeMap 类:另一些映射实现则不保证顺序,如HashMap 类. 注:将可变对象用作映射键时必须格外小心.当对