(java)继承和多态 (详解)

目录

1 继承

1.1为什么需要继承

1.2 继承概念

1.3 继承的语法

1.4 父类成员访问 

1.4.1 子类中访问父类的成员变量

1.4.2 子类中访问父类的成员方法 

 1.5 super关键字

1.6 子类构造方法

1.7 super和this

1.7.1 this

1.7.2 super和this 

1.8 再谈初始化

1.9 继承方式

1.10 继承与组合

2 多态

2.1 多态的概念

2.2 多态实现条件

2.3 重写

2.4 向上转移和向下转型

2.4.1 向上转型

2.4.2 向下转型

2.5 多态的优缺点

2.6 避免在构造方法中调用重写的方法


 

1 继承


1.1为什么需要继承

Java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象则可以用来表示现实中的实体,但是现实世界错综复杂,事物之间可能会存在一些关联,那在设计程序是就需要考虑。
比如:狗和猫,它们都是一个动物


(java)继承和多态 (详解)_第1张图片

 那我们在对猫和狗的各种动作进行描述时,利用Java,就会设计出这样的代码

(java)继承和多态 (详解)_第2张图片

发现了吗,这里面有很多重复的地方 

那能否将这些共性抽取呢?面向对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。(共性的抽取,达到代码的复用)

1.2 继承概念

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

图解:

(java)继承和多态 (详解)_第3张图片

上述图示中,Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类或超类Dog和Cat可以称为Animal的子类/派生类继承之后子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可
 

 从继承概念中可以看出继承最大的作用就是:实现代码复用


1.3 继承的语法

在Java中如果要表示类之间的继承关系,需要借助extends关键字,具体如下:

修饰符 class 子类 extends 父类{

}

实操 :(将上面的代码用继承方法重新设计)

上面的代码: 

 (java)继承和多态 (详解)_第4张图片

重新设计的: 

(java)继承和多态 (详解)_第5张图片

然后我们测试一下 :

 (java)继承和多态 (详解)_第6张图片

运行: 

 (java)继承和多态 (详解)_第7张图片

注意:1.没有赋值的话,默认放null或0

(java)继承和多态 (详解)_第8张图片

2.子类会将父类中的成员变量或者成员方法继承到子类中了
3. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了

 

1.4 父类成员访问 

在继承体系中,子类将父类中的方法和字段继承下来了,那在子类中如何访问父类中继承下来的成员呢? 

1.4.1 子类中访问父类的成员变量

1. 子类和父类不存在同名成员变量

(java)继承和多态 (详解)_第9张图片

(java)继承和多态 (详解)_第10张图片

 2. 子类和父类成员变量同名

(java)继承和多态 (详解)_第11张图片

(java)继承和多态 (详解)_第12张图片

(java)继承和多态 (详解)_第13张图片

总结:由上诉可知 

  • 如果访问的成员变量子类中有优先访问自己的成员变量。
  • 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
  • 如果访问的成员变量与父类中成员变量同名与类型无关,同名都先访问自己),则优先访问自己的

1.4.2 子类中访问父类的成员方法 

1. 成员方法名字不同 


(java)继承和多态 (详解)_第14张图片

(java)继承和多态 (详解)_第15张图片

总结:成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时再到父类中找,如果父类中也没有则报错。

2. 成员方法名字相同
 (java)继承和多态 (详解)_第16张图片

(java)继承和多态 (详解)_第17张图片

 总结:

  • 通过子类对象访问父类与子类中不同名方法时,优先在子类中找找到则访问,否则在父类中找,找到则访问,否则编译报错
  • 通过子类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法传递的参数选择合适的方法访问,如果没有则报错

 1.5 super关键字

由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,直接访问是无法做到的Java提供了super关键字,该关键字主要作用:在子类方法中访问父类的成员。

public class Base {
    int a;
    int b;
    public void methodA(){
        System.out.println("Base中的methodA方法");
    }
    public void methodB() {
        System.out.println("Base中的methodB方法");
    }
}
public class Derived extends Base{
    int a; // 与父类中成员变量同名且类型相同
    char b; // 与父类中成员变量同名但类型不同
    //与父类中的methodA构成重载
    public void methodA(int a){
        System.out.println("Derived中的methodA方法");
    }
    //与父类中的methodB构成重写
    public void methodB() {
        System.out.println("Derived中的methodB方法");
    }
    public void methodC(){
        // 对于同名的成员变量,直接访问时,访问的都是子类的
        a = 10;//相当于 this.a = 10
        b = '1';//相当于 this.b = '1'
        
        // 访问父类的成员变量时,需要借助super关键字
        // super是获取到子类对象中从基类继承下来的部分
        super.a = 3;
        super.b = 4;
        // 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
        methodA(); // 没有传参,访问父类中的methodA()
        methodA(20); // 传递int参数,访问子类中的methodA(int)

        // 如果在子类中要访问重写的基类方法,则需要借助super关键字
        methodB(); // 直接访问,则永远访问到的都是子类中的methodA(),基类的无法访问到
        super.methodB(); // 访问基类的methodB()
    }
}

简单来说就是,想要明确访问父类中的成员,要使用 super 关键字 

 注意:

  • super只能在非静态方法中使用
  • super是在子类方法中,访问父类的成员变量和方法。 

ps:那super和this的区别是什么? 

  • this可以访问父类,也可以访问子类
  • this优先访问子类(有同名时)
  • super只能访问从父类继承下来的成员变量
  • 当前类使用了super,那当前类一定是子类

1.6 子类构造方法

子类对象构造时,需要先调用父类构造方法,然后执行子类的构造方法。

  • 当子类继承了父类之后,一定要先帮助父类构造他的构造方法,然后再构造子类自己的构造方法(因为子类对象中成员是有两部分组成的,父类继承下来的以及子类新增加的部分
  • 父类的构造方法只能在子类中调用

代码演示: 

(java)继承和多态 (详解)_第18张图片 

(java)继承和多态 (详解)_第19张图片 

初学小贴士:构造方法不需要通过d.来访问,只要实例化对象就行 

                    (当调用完相应的构造方法之后,实例化对象才产生,也就是说你创建对象的时候即使不写构造方法,也会有一个默认的空构造方法)

注意:

  • 子类构造方法默认调用父类的无参构造方法super(),用户没有写时,编译器会自动添加,而且super(..)必须是子类构造方法中第一条语句,并且只能出现一次,并且不能和this()同时出现
  • 如果父类构造方法带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。

代码演示:

(java)继承和多态 (详解)_第20张图片

父类的构造方法有参数,子类没有,编译失败,如何做看下面吧

(java)继承和多态 (详解)_第21张图片 

 在调用构造方法时,super(..)与this()不能同时出现,因为他们俩都只能在第一行

 (java)继承和多态 (详解)_第22张图片

 


1.7 super和this

1.7.1 this

再说super和this之前我先来介绍一下this 

  1. this引用 用在形参名不小心与成员变量名相同时

(java)继承和多态 (详解)_第23张图片

那函数体中到底是谁给谁赋值?形参给成员变量?还是成员变量给形参?傻傻分不清楚

看看这样会打印出什么?

(java)继承和多态 (详解)_第24张图片
 

(java)继承和多态 (详解)_第25张图片 

 

这不是我们想要的,这时候就要用this了,

我们可以使用"this.属性"或"this.方法"的方式,调用当前正在运行的对象属性或方法。

如果方法的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性(类中的属性),而非形参。

(java)继承和多态 (详解)_第26张图片

(java)继承和多态 (详解)_第27张图片 

(java)继承和多态 (详解)_第28张图片

 

 总结:

  • this表示当前对象的引用,不能再引用其他对象(成员方法运行时调用该成员方法的对象)
  • this只能使用在非静态方法(又称实例方法,或成员方法)中谁调用这个实例方法,this就是谁。所以this代表的是:当前对象。
  • this不能在静态方法中使用, 因为this代表当前对象,静态方法中不存在当前对象。  

ps:静态方法静态方法属于类,可以在实例化之前被类直接调用

(java)继承和多态 (详解)_第29张图片 

   2.可以通过this调用其他构造方法来简化代码 (语法this();)

  • this();调用当前类当中的其他构造方法(看匹配到那个,就调用那个)
  • 只能在当前的构造方法内部来使用
  • 只能在第一行 

(java)继承和多态 (详解)_第30张图片 

(java)继承和多态 (详解)_第31张图片 

(java)继承和多态 (详解)_第32张图片 

1.7.2 super和this 

  • < 相同点>

1. 都是Java中的关键字
2. 只能在类的非静态方法中使用,用来访问非静态成员的方法和字段
3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在

  • <不同点>

1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
3. 在构造方法中:this(...)用于调用本类中的其他构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现
4. 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有


1.8 再谈初始化

1、父类静态代码块优先子类静态代码块执行,且是最早执行
2、父类实例代码块和父类构造方法紧接着执行
3、子类的实例代码块和子类构造方法紧接着再执行
4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行

(java)继承和多态 (详解)_第33张图片  

(java)继承和多态 (详解)_第34张图片 

 


1.9 继承方式

Java中只支持以下几种继承方式:

  1.单继承
public class A{
}
public class  B extends A{
}
  2.多层继承
public class A{
}
public class  B extends A{
}

public class C extends B{

}

3.不同类继承同一个类 
public class A{
}
public class  B extends A{
}

public class C extends A{

}

注意:

  • Java中不支持多继承。(public class A{ }   public class B{ } public class C extends A,B )
  •  一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.
  • 如果想从语法上进行限制继承, 就可以使用 final 关键字 


1.10 继承与组合

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。

  • 继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
  • 组合表示对象之间是has-a的关系,比如:汽车

(java)继承和多态 (详解)_第35张图片 

ps:  组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,

       一般建议:能用组合尽量用组合


2 多态
 


2.1 多态的概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态

同一件事情,发生在不同对象身上,就会产生不同的结果。


2.2 多态实现条件

在java中要实现多态,必须要满足如下几个条件,缺一不可:
1. 必须在继承体系下
2. 子类必须要对父类中方法进行重写
3. 通过父类的引用调用重写的方法

多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。


2.3 重写

重写:也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变,名字也要相同。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。

 方法重写的规则:

  • 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致
  • 被重写的方法返回值类型可以不同,但是必须是具有父子关系的

(java)继承和多态 (详解)_第36张图片

 

  • 访问权限不能比父类中被重写的方法的访问权限更低。(private < 默认权限 < protected < public)
  • 父类被static、private修饰的方法、构造方法都不能被重写。
  • 重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写.

(java)继承和多态 (详解)_第37张图片 

(java)继承和多态 (详解)_第38张图片 

 

重写重载区别 :

区别点 重写(override) 重载(override)
参数列表 一定不能修改 必须修改
返回类型 一定不能修改【除非可以构成父子类关系】 可以修改
访问限定符 一定不能做更严格的限制(可以降低限制) 可以修改


2.4 向上转移和向下转型


2.4.1 向上转型

向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。

1. 直接赋值 

(java)继承和多态 (详解)_第39张图片
2. 方法传参

(java)继承和多态 (详解)_第40张图片
3. 方法返回

(java)继承和多态 (详解)_第41张图片 

向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。

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

在编译的时候确实认为应该调用Animal的eat方法。但是运行的时候发现,子类重写父类的这个
eat方法,所以会直接调用子类 

(java)继承和多态 (详解)_第42张图片 

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

 


2.4.2 向下转型

我们看下面的代码报错了,因为这样调用的是父类的fly()方法,但是父类中有没有fly()方法,当然报错上面的可以调用子类中的eat()方法,是因为父类中本来就有eat()方法,但是子类中又重写了eat方法,调用时发生了动态绑定,所以调用了子类的方法) 

(java)继承和多态 (详解)_第43张图片 

此时就利用了向下转型,但是不安全 

(java)继承和多态 (详解)_第44张图片 

 你往下看

(java)继承和多态 (详解)_第45张图片

报了一个错误: Dog cannot be cast to Bird

 

Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换,是这样用的,如下:
 

(java)继承和多态 (详解)_第46张图片 

 


2.5 多态的优缺点

比如我们想打印【"●", "♦", "●", "♦", "❀"】, 那我们利用多态思想就会写出这样的代码

public class Shape {
    public void draw() {
        System.out.println("画图形!");
    }
}
class Rect extends Shape{
    @Override
    public void draw() {
        System.out.print("♦ ");
    }
}
class Cycle extends Shape{
    @Override
    public void draw() {
        System.out.print("● ");
    }
}
class Flower extends Shape{
    @Override
    public void draw() {
        System.out.print("❀ ");
    }
}

利用多态 :(动态绑定,向上转型)

public class Test {
    public static void drawMap(Shape shape){
        shape.draw();
    }
    public static void main(String[] args) {
        Cycle cycle = new Cycle();
        Rect rect = new Rect();
        Flower flower = new Flower();
        Shape[] shapes = {cycle, rect, cycle, rect, flower};
        for(int i = 0; i < shapes.length; i++){
           drawMap(shapes[i]);
        }
    }

}

(java)继承和多态 (详解)_第47张图片 

如果没有多态就会这样:(很多if else 语句)

public class Test2 {
    public static void main(String[] args) {
        Cycle cycle = new Cycle();
        Rect rect = new Rect();
        Flower flower = new Flower();
        String[] shape = {"cycle", "rect", "cycle", "rect", "flower"};
        for(int i = 0; i < shape.length; i++){
            if (shape[i].equals("cycle")) {
                cycle.draw();
            } else if (shape[i].equals("rect")) {
                rect.draw();
            } else if (shape[i].equals("flower")) {
                flower.draw();
            }
        }
    }
}

总结

多态优点:

1. 能够降低代码的 "圈复杂度", 避免使用大量的 if - else

2. 可扩展能力更强
    如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低

 多态缺陷:代码的运行效率降低


2.6 避免在构造方法中调用重写的方法

一段有坑的代码. 我们创建两个类, B 是父类, D 是子类. D 中重写 func() 方法. 并且在 B 的构造方法中调用 func()

public class B {
    public B() {
        func();
    }

     public void func() {
         System.out.println("B.func()");
     }
}

class D extends B {
    private int num = 1;
    @Override
    public void func() {
        System.out.println("D.func() " + num);
    }
}
public class Test {
    public static void main(String[] args) {
        D d = new D();
    }
}

 运行结果:

 (java)继承和多态 (详解)_第48张图片

是0,可是我们的num明明被初始化赋值成了1 ,因为此时D这个对象还没有构造完成

(java)继承和多态 (详解)_第49张图片

 
所以在构造函数内,尽量避免使用实例方法,除了final和private方法。
结论: "用尽量简单的方式使对象进入可工作状态", 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题.

 

╰(*°▽°*)╯╰(*°▽°*)╯╰(*°▽°*)╯╰(*°▽°*)╯╰(*°▽°*)╯完╰(*°▽°*)╯╰(*°▽°*)╯╰(*°▽°*)╯╰(*°▽°*)╯╰(*°▽°*)╯

你可能感兴趣的:(Java,java,开发语言)