Java学习指南(11) 继承

本文是《Java学习指南》原书的网络版,作者邵发,拥有本书的全部权利。相关视频课程在此查看

目录

第11章 继承

11.1 类的继承

11.2 重写

11.3 构造方法的继承

11.4 单根继承

11.5 多态


11章 继承

11.1 类的继承

本章描述继承关系在Java语言中的表示。那什么叫继承关系呢?还是先用自然界面的例子说起。

11.1.1 引例1

在自然界中,树可以称为一个类,而苹果树也是一个类。它们之间的关系可以用下表示。

其中,树作为一类,具有树叶、主干和根(属性),能进行光合作用(方法)。而苹果树也是一种树,所有也有树叶、主干和树,也能进行光合作用。除此之外,苹果树还有自己的特性:结苹果。

这就是继承关系,苹果树作为一种树,继承了树的所有共性。另外,苹果树也有自己的特性。

11.1.2 引例2

下面,再给出一个贴近计算机编程的例子,来进一步说明继承关系。在计算机上有文件类,而有些文件是视频文件类。这两个类就是继承关系。

所有的文件都有大小、创建时间等属性,都可以读操作、写操作等方法。

视频文件作为文件的一种,自然也具备大小、创建时间等属性,也能读写。除此之外,视频文件还具有时长属性,能进行播放操作。

可以说,视频文件类继承了文件类的所有属性和方法。

 

11.1.3 继承 extends

在Java语言里,用关键字extends表示类与类之间的继承关系。一般写法为,

public class B extends A { }

其中,A,B是两个类。把A称为父类(superclass),把B称为子类(subclass)。整体上可以读作B继承于A。

当B继承于A时,那么父类A中的所有public的属性和方法,都被自然的继承拥有。还是以视频文件和文件类为例,

先定义一个父类MyFile表示文件类,

public class MyFile
{
    public long size; // 文件大小
    public String name; // 文件名

    public void info() // 显示文件信息
    {
        System.out.println("文件:"+name+",大小:" + size);
    }
}

父类具有:

- 2个属性:size, name

- 1个方法info() ,用于显示文件的信息

下面,再添加一个MyVideoFile 表示视频文件类,注意下面的extends关键字的写法,

public class MyVideoFile extends MyFile
{

}

作为子类,MyVideoFile已经自动地继承了父类的属性和方法。所以共有的东西就不必再写一遍了,只要写上自己特有的东西就可以了,示例代码如下,

public class MyVideoFile extends MyFile
{
    public int duration ; // 时长

    public void play()
    {
        System.out.println("播放视频"+ this.name);
    }

    public void stop()
    {
        System.out.println("停止播放"+ this.name);
    }
}

再来看一下怎么样使用子类MyVideoFile,

MyVideoFile f = new MyVideoFile();
f.size = 1293034;  // 继承于父类
f.name = "abc.mp4";// 继承于父类
f.duration = 130;
f.info(); // 继承于父类
f.play();
f.stop();

可以看到,在MyVideoFile里并没有定义size, name, info(),但却可以直接使用它们。原因就在于extends,使用extends就可以把父类的属性和方法继承过来。

11.2 重写

重写 ( Override ):在继承的时候,如果觉得父类的方法不满足要求,可以把这个方法在子类里重写一遍。

例如,在先前的例子中,父类MyFile表示一般性的文件,子类MyVideoFile表示视频文件。在父类中,有一个info() 方法,用来打印输出文件的一般信息性息。但对子类来说,这个info() 方法就不够用了,因为它没有显示出视频的时长信息的,所以可以把info()方法重写。示例如下,

public class MyVideoFile extends MyFile
{
    public int duration ; // 时长


    @Override
    public void info()
    {
        System.out.println("文件名:" + this.name   + ",文件大小: " + this.size
                + ",视频时长: " + this.duration   );
    }
}

然后再看一下调用,

MyVideoFile f = new MyVideoFile();
f.size = 1293034;  
f.name = "abc.mp4";
f.duration = 130;
f.info(); // 子类重写了这个方法

由于子类把info()重写了一遍,所以最终输出时调用的是子类的代码,控制台显示如下,

11.2.1 部分重写

部分重写:就是觉得父类的方法写得还行,只需补充修改就可以。还是以上述场景为例,在父类MyFile的info()里,已经打印显示了文件名和文件大小;对于子类MyVideoFile来说,只需要把时长补充打印一下即可。把MyVideoFile的代码稍做更改,如下,

@Override
public void info()
{
     super.info();
     System.out.println("视频时长"+ this.duration);
}

其中,super.info() 表示调用父类的info()方法。这样,就在父类的基础上,补充了视频时长输出的功能。

提示:在书写时应注意,被重写的方法前面的一行 @Override ,也是有用的,不要随便删掉。

11.3 构造方法的继承

在Java语言里,构造方法是自动继承的。这意味着,如果B继承于A,则A的构造方法会被自动调用。

例如,先定义一个类 Parent,

public class Parent
{
    int a;

    public Parent()
    {
        a = 10;
        System.out.println("父类Parent构造...");
    }
}

在定义一个Child继承于Parent,

public class Child extends Parent
{
    public Child()
    {
        System.out.println("子类Child构造...");
    }
}

显然,在子类中并没有看到有调用父类的构造方法。但是,运行以下代码试一下,

public static void main(String[] args)
{
    Child ch = new Child();
    System.out.println("程序退出");
}

在这里,创建了一个Child对象,自然地会调用Child的构造方法。但在控制台的输出显示中,

在这个显示中,可以确定是先调用了父类的构造方法,再调用了子类的构造方法。这充分说明,父类的构造方法默认会被调用 。

11.3.1 显式调用父类构造方法

可以在子类中显示调用父类的构方法。在父类有多个构造方法的时候,就特别的有用。例如,先给父类添加多个构造方法,

public class Parent
{
    int a;


    public Parent()
    {
        a = 10;
        System.out.println("父类Parent构造...");
    }

    public Parent(int a)
    {
        this.a = a;
        System.out.println("父类Parent构造222...");
    }
}

此时父类有2个构造方法,那么在子类里就可以显式指定调用哪一个,例如,

public class Child extends Parent
{
    public Child()
    {
        super(12);
        System.out.println("子类Child构造...");
    }
}

使用 super 关键字可以显式指定调用父类的构造方法。例如,super()表示调用父类的无参构造方法,而super(12) 表示调用另一个带参的构造方法。其匹配规则在第七章(方法的重载)那一章节已经讲过。

11.4 单根继承

在Java语言里,一个类只能有一个父类。例如,下面的写法是错误的,

public class  A  extends B, C  // 错误的写法!

{

}

A不能同时有两个父类B,C,这是语法禁止的。

如果A继承于B,  B继承于 C,  C继承于D,也就是说B是父亲,C是祖父,D是曾祖父,可以就形成一根继承链条:

A -> B -> C -> D

其中,箭头表示继承关系。链条的最顶端,是顶级父类D。

11.4.1 Object类

在Java语言里,如果一个类没有显式地指定父类,则默认继承于Object类。例如,

public class Student
{
    public String id;
    public String name;
    public boolean sex;
}

这个Student类没有指定父类,则默认父类是Object类。相当于写成,

public class Student extends Object
{
    public String id;
    public String name;
    public boolean sex;
}

通常情况下,extends Object 是省略不写的。

在Java里,所有类的顶级父类都是Object。 或者说,所有的类都是Object类的子类或孙子类。

以前面一节所用的例子进行说明,

public class Parent
{

}

public class Child extends Parent
{

}

由于Parent的父类是Object,所以最终的继承链为:

此图可以在Eclipse中,右键选中一个类,然后点菜单Quick Type Hierarchy显示。图中可以看到,Child的父类是Parent,而Parent的父类为Object。

11.4.2 重写toString方法

在Object中有一个方法toString(),用于将对象转成字符串显示。这是我们经常需要重写的一个方法。

例如,有一个类Student,

public class Student
{
    public String id;
    public String name;
    public boolean sex;
}

然后在main()里调用它,

public static void main(String[] args)
{
    Student s = new Student();
    s.id = "20180001";
    s.name = "邵发";
    s.sex = true;
    System.out.println("学生信息:" + s);
}

然后Eclipse运行程序,在控制台里输出如下,

实际上在前面的章节已经强调过,Java里的对象是默认不能打印显示的,否则就会出现 my.Student@6d06d69c 类似的字样。(类名+对象地址)。

此时我们应在Student类重写一下toString()方法,以便能以正常的字符串显示,示例如下,

public class Student extends Object
{
    public String id;
    public String name;
    public boolean sex;

    @Override
    public String toString()
    {
        String result = id + " / " + name + " / " ;
        if(sex)
            result += "男";
        else
            result += "女";

        return result;
    }
}

再运行程序,则输出为,

其实,

System.out.println("学生信息:" + s);

就相当于

System.out.println("学生信息:" + s.toString());

所以最终显示的是s.toString() 方法返回的字符串。

提示:toString()方法是经常要重写的方法,一定要理解掌握。

 

11.5 多态

多态(polymorphism)是一个软件设计上的一个术语。 Java支持多态设计,具体体现在以下语法现象:

v 重载 Overload: 方法允许重名

v 重写 Override: 允许子类重写父类的方法

v 泛型(模板): 在高级语法篇中讲解,例如ArrayList,HasMap

方法重写,就是一种多态的设计。比如说,父类MyFile的info()方法,与子类MyVideoFile的info()方法,两者方法名相同,但是子类重新把方法重写了一遍。同一个方法,两种不同的行为功能,这就是多态的设计理态。(注:“多态”这个术语翻译的有点晦涩,不必纠结字面意思)。

11.5.1 父子类型之间的转换

假设有一个类Pie表示饼干,另一个类ApplePie表示苹果味的饼干,它们具有继承关系,

public class ApplePie extends Pie
{

}

子类转成父类顺利成章的,例如,

ApplePie  p1  =  new ApplePie();
Pie p2 =  (Pie) p1;   // 类型转换: ApplePie -> Pie

其中,p1是一个ApplePie的对象,由于“苹果味饼干是一种饼干”,所以在逻辑上可以很容易接受 Pie p2 = (Pie) p1,这是很自然的转换、顺理成章的转换。

通过情况下,将子类类型转成父类类型,直接隐式转换就可以,

Pie p2 = p1;  // 隐式转换即可,没有风险

更简洁的,可以写成:

Pie p2 = new ApplePie();

这是一种常见的写法,右侧为一个ApplePie对象,被转成Pie类型的引用。

比如,有一个类Baby,需要传入Pie对象,

public class Baby
{
    // 宝宝要吃饼干
    public void eat ( Pie p)
    {
    }
}

这个eat()方法表示:宝宝要吃饼干,需传入Pie对象。那么,现在有一块ApplePie,传给它是不是也可以呢?当然可以。

Pie p = new ApplePie();
Baby bb = new Baby();
bb.eat( p );

虽然eat() 方法要求传入Pie类型的对象,但传入ApplePie类型也是没有问题的,因为ApplePie就是一种Pie。

11.5.2 方法的多态调用

考虑以下代码,

MyFile  file = new MyVideoFile();
file.info();

那么,file.info () 具体执行的是MyFile.info() 还是 MyVideoFile.info()呢?

在做这种判断时,有一个很简单的原则:看对象的真正类型。在这里,file真正指向的是一个MyVideoFile对象,所以真正执行的是MyVideoFile里的info()方法。

也就是说,虽然字面上file对象是MyFile引用类型,但它指向的对象的具体类型是MyVideoFile。我们要看目标对象的具体类型。

提示:这一语法在理解上需要一定时间,但初期我们应强行记住这种写法 Parent p = new Child() ,这是Java里面很常见的写法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿发你好

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值