继承与多态

目录

一、为什么会有继承

二、认识继承

1、继承的概念

2、继承的语法

3、父类成员访问

3.1  子类访问父类的成员变量

3.2  子类访问父类的成员方法

4、super关键字

5、子类和父类中的构造方法

6、代码运行顺序

7、继承与组合

三、多态

1、多态的概念

2、实现多态的条件

2.1 重写

2.2 向上转型


一、为什么会有继承

Java中使用类来对现实世界中的实体来进行描述,但是现实世界非常复杂,复杂之中却也存在一些联系,比如狗和鸡都是动物,它们都有一些共同的属性:颜色、体重、吃东西、喝水...

转换成代码:

可以发现,两个类中有很多重复的属性和方法,那么我们就可以把这些共性拿出来,实现代码复用

二、认识继承

1、继承的概念

继承(inheritance)机制 :是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用
那么我们就可以把上述代码中,狗和鸡类的相同属性抽取出来,然后采用继承的思想来达到共用。

上图中,Dog和Chicken都继承了Animal类,那么Animal类就是父类/基类/超类,Dog和Chicken就是Animal的子类/派生类,继承之后,子类就可以复用父类的成员。

2、继承的语法

在Java中,如果要表示类之间的继承关系,需要使用extends关键字:

注意:

(1)子类会将父类中的成员变量和成员方法继承到子类中 

(2)子类继承父类后,必须要添加新成员,以显示出与父类的不同,否则就没必要继承了。 

3、父类成员访问

3.1  子类访问父类的成员变量

1、子类和父类中不存在同名变量时

 2、子类和父类存在同名成员变量

此时会优先访问子类的成员变量。

总结:成员变量遵循就近原则,自己有则优先访问自己的,自己没有则在父类中寻找,父类中也没有代码就会报错。

思考:那么如何在子类中访问同名的父类成员变量呢?

3.2  子类访问父类的成员方法

1、子类和父类中没有同名的成员方法

此时优先在子类中找,找到则访问,找不到则在父类中寻找,找到则访问,找不到则报错。

2、子类和父类中有同名的成员方法

代码运行结果:

说明当子类和父类中存在同名且同参数列表(构成方法重写)的成员方法时,优先访问子类的成员方法。

如果子类和父类的方法名相同,但参数列表不同(构成方法重载),则根据传递的参数选择合适的方法访问。

代码运行结果:

总结:与访问父类的成员变量类似,遵循就近原则,如果子类与父类中的方法构成重载,那么根据传递的参数访问合适的方法。

思考:如何在子类中访问构成重写的成员方法呢?

4、super关键字

为了解决上述两个思考问题,我们需要使用到super关键字。

该关键字的主要作用就是在子类方法中访问父类的成员

注意:

(1)super关键字类似于this关键字,只能在非静态方法中使用

(2)super关键字用来在子类方法中,访问父类的成员变量和方法

5、子类和父类中的构造方法

子类中有两部分成员,一类是从父类继承来的,一类是自己新增的,所以在构造子类对象的时候先要调用父类的构造方法,把父类的成员初始化之后,再调用子类的构造方法,初始化自己新增的成员。

注意:

(1)在子类构造方法中,super()调用父类构造方法时,必须是子类构造方法的第一条语句;

(2)如果父类的构造方法是默认的或者无参的,那么在子类构造方法第一行默认有隐藏的super(),用来调用父类构造方法;

 (3)如果父类的构造方法是有参数的,此时编译器不再给子类生成默认的构造方法,需要我们手动定义子类构造方法;

 (4)super只能在子类构造方法中出现一次

6、代码运行顺序

如果父类和子类中都有实例代码块、静态代码块、构造方法,那么它们的执行顺序是如何呢?我们通过一段代码来试验一下:

class Father{
    public Father() {
        System.out.println("父类构造方法");
    }
    static {
        System.out.println("父类静态代码块");
    }
    {
        System.out.println("父类实例代码块");
    }
}
class Son extends Father{
    public Son() {
        System.out.println("子类构造方法");
    }
    static {
        System.out.println("子类静态代码块");
    }
    {
        System.out.println("子类实例代码块");
    }
}
public class Test2 {
    public static void main(String[] args) {
        Son son1 = new Son();
        System.out.println("——————————————————————");
        Son son2 = new Son();
    }
}

代码运行结果:

 根据代码运行结果,可以得出以下结论:

(1)父类静态代码块最早执行,优先于子类静态代码块;

(2)父类实例代码块和父类构造方法紧接着执行;

(3)子类实例代码块和子类构造方法最后执行;

(4)第二次实例化子类对象时,父类和子类的静态代码块都不再会执行。

7、继承与组合

组合和继承类似,也是一种表达类之间的方式,组合是将一个类的实例,作为另一个类的字段,以此来达到代码复用的效果。

举个栗子:一辆汽车和它的轮胎、发动机、方向盘、车身、座椅等的关系就可以用组合来表示,因为汽车是由这些部件组成的。

组合和继承都可以实现代码复用,应该使用哪种需要根据应用场景来选择,一般建议:能用组合尽量用组合。

三、多态

1、多态的概念

多态,即多种状态,就是同一种行为,不同的对象去做会产生不同的状态/结果。

比如,一张高考数学试卷,让一个小学生做,结果可能是0分;让一个高中生做,结果可能是100分,让一个高中数学老师做,结果可能是120分。

2、实现多态的条件

在Java中实现多态,必须满足以下几个条件:

(1)必须在继承体系下;

(2)子类必须要对父类中的方法进行重写;

(3)通过父类的引用调用重写的方法。

2.1 重写

重写(override)也叫覆写、覆盖,是子类对父类中static、private、final修饰,以及非构造方法的实现过程进行重新编写,方法名、返回值类型和参数列表不变。

方法重写的规则:

(1)子类重写父类的方法时,一般要求必须与父类方法原型一致:修饰符,方法名、返回值类型、参数列表要完全一致;

(2)被重写的方法返回值类型可以不同,但必须具有父子类关系;

(3)子类中重写的方法的访问权限必须大于等于父类的访问权限。比如,父类方法如果被protect修饰,子类中重写该方法的修饰符只能为protect或者public。

访问权限关系图:

(4) 父类的方法如果被static、private、final修饰,子类不能重写。

2.2 向上转型

向上转型就是用父类引用来引用一个子类对象。

例如:

class Animal{
    public String name;

    public void eat(){          //父类重写方法的原型
        System.out.println(name+"吃东西");
    }

    public Animal(String name) {  //父类构造方法
        this.name = name;
    }
}
class Dog extends Animal{
    public Dog(String name){   //子类构造方法
        super(name);
    }
    public void eat(){         //子类重写方法
        System.out.println(name + "正在吃狗粮");
    }
    public void bark(){        //子类普通方法
        System.out.println(name+"正在汪汪叫");
    }
}
class Bird extends Animal{
    public Bird(String name){   //子类构造方法
        super(name);
    }
    public void eat(){          //子类重写方法
        System.out.println(name+"正在吃虫子");
    }
    public void fly(){          //子类普通方法
        System.out.println(name + "正在天空飞翔");
    }
}
public class Test {
    public static void main(String[] args) {
        Animal animal1 = new Dog("炫炫");
        Animal animal2 = new Bird("坤坤");
    }
}

上述代码中,父类是Animal,子类是Dog和Bird,在main函数中,我们分别new了一个Dog类的对象和一个Bird类的对象各自存入到一个Animal父类的引用中,这就是一次向上转型。当我们通过animal1和animal2这两个引用来调用eat方法时,结果如下:

animal1和animal2两个父类的引用并没有调用父类的eat方法,而是分别调用了两个子类中重写的eat方法,但是当我们通过反汇编查看字节码文件时,可以发现,它调用的还是父类的方法???

//图先欠着

是因为在代码运行时发生了动态绑定。

动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用哪个类的方法。

静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。函数重载就是典型的代表。

向上转型的三种使用场景:

(1)第一种就是刚才的直接赋值:子类对象赋值给父类引用。

(2)第二种是方法传参:

(3)第三种是作为方法的返回类型:

向上转型的优点:让代码变得更加简单灵活。

向上转型的缺点:不能调用子类特有的方法。

 

当通过向上转型调用子类自己的方法时,代码就会报错,此时就需要借助向下转型了。

2.3 向下转型

向下转型就是将已经进行向上转型的父类引用再还原为子类对象。

向下转型一般用的比较少,因为它不安全,可能会转换失败:

代码虽然可以通过编译,但运行时出现异常。因为animal1本来向上转型之后引用的是一个鸟类对象,它实际指向的对象是鸟,当我们把它向下转型还原为狗类时就会无法正常还原。

Java中为了提高向下转型的安全性,引入了instanceof关键字,如果该表达式为真,则可以安全转换:

此时代码就不会抛出异常了。

3、多态的优缺点

优点:

(1)能够降低代码的“圈复杂度”,避免使用大量的if-else语句;

(2)可扩展能力强,使用多态的方式改动代码的成本也比较低,如果需要新增一种动物的eat方法,只要创建一个新的类,重写一个eat方法就可以了。

缺点:

代码的运行效率较低。

  • 12
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

吃点橘子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值