• java继承与多态——继承


    1. 继承

    1.1 为什么需要继承


    先来实现猫狗类

    package inherit;
    
    class Dog {
        public String color;
        public String name;
        public void barks() {
            System.out.println(name + "汪汪叫");
        }
    }
    
    class Cat {
        public String color;
        public String name;
        
        public void barks() {
            System.out.println(name + "喵喵叫");
        }
        public void eat() {
            System.out.println(name + "在吃饭");
        }
        public void catchMouse() {
            System.out.println(name + "在抓老鼠");
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            Dog dog = new Dog();
            dog.name = "大黄";
            dog.color = "yellow";
            dog.eat();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    结合上面代码会发现,两个类都有name、color成员和eat方法,代码在两个类中重复的较多,如何将这些共性抽取呢?



    面向对象思想中提出了继承的概念,专门用来进行共性抽取实现代码复用

    1.2 继承的概念

    继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。

    继承主要解决的问题是:共性的抽取实现代码复用

    例如:狗和猫都是动物,那么我们就可以将共性的内容进行抽取,然后采用继承的思想来达到共用。

    在这里插入图片描述

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

    从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态(后序讲)。

    1.3 继承的语法


    对猫狗类的重新实现

    package inherit;
    
    class Animal {
        public String color;
        public String name;
        public void eat() {
            System.out.println(name + "在吃饭");
        }
    }
    
    class Dog extends Animal{
        public void barks() {
            System.out.println(name + "汪汪叫");
        }
    }
    
    class Cat extends Animal{
        public void barks() {
            System.out.println(name + "喵喵叫");
        }
    
        public void catchMouse() {
            System.out.println(name + "在抓老鼠");
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            Dog dog = new Dog();
            dog.name = "大黄";
            dog.color = "yellow";
            dog.eat();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34


    实现一个Animal类,将Dog、Cat类相同的成员和方法放在其中。



    父类里面有什么,子类就会继承什么。当子类继承父类后,子类就会有父类的成员属性和方法

    1.4 父类成员访问

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

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

    1. 子类和父类不存在同名成员变量
    package inherit;
    
    class Base {
        int x;
        int y;
    }
    
    public class Derived extends Base{
        int z;
    
        public void method() {
            x = 10;// 访问从父类中继承下来的x
            y = 20;//访问从父类中继承下来的y
            z = 30;//访问子类自己的z
            System.out.println(x);
            System.out.println(y);
            System.out.println(z);
        }
    
        public static void main(String[] args) {
            Derived derived = new Derived();
            derived.method();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    1. 子类和父类成员变量同名
    package inherit;
    
    class Base {
        int x;//Base中的x
        int y;
    }
    
    public class Derived extends Base{
        int x;//Derived中的x
        int z;
    
        public void method() {
            x = 100;//执行就近原则 - 子类优先
            y = 20;
            z = 30;
            System.out.println(x);
            System.out.println(y);
            System.out.println(z);
        }
    
        public static void main(String[] args) {
            Derived derived = new Derived();
            derived.method();
            Base base = new Derived();
            System.out.println("Base中的x:" + base.x);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27



    上述图片表示Base中的x没有赋值,说明当子类和父类成员变量同名时,采用就近原则,也就是子类优先


    在子类方法中 或者 通过子类对象访问成员时:

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

    成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。


    那如果想要访问父类的成员该该怎么办?

    使用super关键字

    public void method() {
            super.x = 100;//访问父类
            y = 20;
            z = 30;
            System.out.println(x);
            System.out.println(super.x);
            System.out.println(y);
            System.out.println(z);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9



    可以看到加上super之后,子类访问了父类的x成员。

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

    1. 成员方法名不同
    class Base {
        int x;//Base中的x
        int y;
        public void methodA() {
            System.out.println("Base中的methodA方法");
        }
    }
    
    public class Derived extends Base {
        int x;//Derived中的x
        int z;
        public void methodB() {
            System.out.println("Derived中的methodB方法");
        }
        public void methodC() {
            methodB();//访问子类自己的methodB()
            methodA();//访问父类继承的methodA()
            methoD()//;//编译失败,在整个继承体系中没有发现方法Derived()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    优先匹配子类,然后是父类。


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

    1. 成员方法名相同
    class Base {
        int x;//Base中的x
        int y;
        public void methodA() {
            System.out.println("Base中的methodA方法");
        }
    }
    
    public class Derived extends Base {
        int x;//Derived中的x
        int z;
        public void methodA() {
            System.out.println("Derived中的methodA方法");
        }
        public void methodB() {
            System.out.println("Derived中的methodB方法");
        }
    
        public void methodC() {
            methodB();//访问子类自己的methodB()
            methodA();//访问父类继承的methodA()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    还是优先访问子类。


    【说明】

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

    当子类和父类有成员方法相同的时候,访问父类的方法该怎么办?

    后续super关键字会讲到。

    1.5 super关键字

    super 关键字的主要作用是:在子类中访问父类的成员


    super 的三个用法:

    • super.date 访问父类的普通成员变量
    • super.func() 访问父类的普通成员方法
    • super() 调用父类的构造方法
    1. 访问父类的普通成员变量
    class Animals{
        public String name;;
        public int age;
    
        public void eat() {
            System.out.println(name + "正在吃饭");
        }
    }
    
    class Dog extends Animals{
        public String silly;//傻 - 狗的属性
        public String name = "大黄";
    
        public void  houseGuard() {
            System.out.println(super.name + "正在看家护院");
        }
    }
    
    class Cat extends Animals{
        public String name = "小黑";
    
        public void catchMouse() {
            System.out.println(super.name + "正在抓老鼠");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    1. 访问父类的普通成员方法
    class Cat extends Animals{
        public String name = "小黑";
    
        public void catchMouse() {
            System.out.println(super.name + "正在抓老鼠");
            super.eat();//调用父类的普通方法 - 静态方法不可调用
            eat();//直接eat()也可以
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    只能在非静态方法中使用。

    1. 调用父类的构造方法
    class Animals{
        public String name;;
        public int age;
    
        public Animals (String name, int age ) {
            this.age = age;
            this.name = name;
        }
    }
    
    class Cat extends Animals{
        public Boolean loveClean;//爱干净
    
        public Cat(String name, int age, boolean loveClean) {
            super(name, age);
            this.loveClean = loveClean;
        }
        public void catchMouse() {
            System.out.println(super.name + "正在抓老鼠");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    1.6 子类构造方法

    class Animals{
        public String name;;
        public int age;
    
        public Animals (String name, int age ) {
            this.age = age;
            this.name = name;
        }
        public void eat() {
            System.out.println(name + "正在吃饭");
        }
    }
    
    class Dog extends Animals{
        public boolean silly;//傻 - 属性
        public Dog(String name, int age, boolean silly) {
            //1.先帮助父类初始化
            super(name, age);//用到super关键字
            this.silly = silly;
        }
       public void  houseGuard() {
            System.out.println(super.name + "正在看家护院");
        }
    }
    
    class Cat extends Animals{
        public Boolean loveClean;//爱干净
        public Cat(String name, int age, boolean loveClean) {
            super(name, age);
            this.loveClean = loveClean;
        }
        public void catchMouse() {
            System.out.println(super.name + "正在抓老鼠");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    【注意】

    • super(); 注意子类构造方法中默认会调用基类的无参构造方法:super()。
    • 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语局。
    • 并且只能出现一次。


    子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。

    1.7 super和this

    共同点

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

    不同点

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

    1.8 再谈初始化

    class Person{
        public String name;
        public int age;
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
            System.out.println("构造方法执行");
        }
        {
            System.out.println("实例代码块执行");
        }
        static {
            System.out.println("静态代码块执行");
        }
    }
    
    public class TestDemo {
        public static void main(String[] args) {
            Person person1 = new Person("zhangan", 19);
            System.out.println("------------------------");
            Person person2 = new Person("lisi", 18);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    • 静态代码会最先执行,并且只执行一次。
    • 接下来才是实例和构造。


    如果是继承关系的执行顺序又会是什么呢?

    class Person {
        public String name;
        public int age;
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
            System.out.println("Person:构造方法执行");
        }
        {
            System.out.println("Person:实例代码块执行");
        }
        static {
            System.out.println("Person:静态代码块执行");
        }
    }
    
    class Student extends Person {
        public Student(String name, int age) {
            super(name, age);
            System.out.println("Student:构造方法执行");
        }
        {
            System.out.println("Student:实例代码块执行");
        }
        static {
            System.out.println("Student:静态代码块执行");
        }
    }
    
    public class TestDemo {
        public static void main(String[] args) {
            Student student1 = new Student("zhangsan", 18);
            System.out.println("------------------");
            Student student2 = new Student("zhangsan", 18);
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    • 父类的静态代码执行顺序优先于子类的静态代码,而且是最早执行的。
    • 父类实例代码块和父类构造方法紧接着执行。
    • 子类的实例代码块和子类构造方法紧接着再执行。
    • 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行。

    1.9 protected关键字


    同一个包中的不同类该如何访问?

    package demo1;
    
    public class TestDemo {
        protected int a = 10;
    
        public void func() {
            System.out.println(a);
        }
    }
    
    package demo1;
    
    public class TestDemo2 {
        public static void main(String[] args) {
            TestDemo testDemo = new TestDemo();
            System.out.println(testDemo.a);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18


    不同包中的非子类如何访问?

    package demo1;
    
    public class TestDemo {
        protected int a = 10;
    
        public void func() {
            System.out.println(a);
        }
    }
    
    package demo1;
    
    public class TestDemo2 {
        public static void main(String[] args) {
            TestDemo testDemo = new TestDemo();
            System.out.println(testDemo.a);
        }
    }
    
    package demo2;
    
    import demo1.TestDemo;
    
    public class Test extends TestDemo {
        public void test() {
            System.out.println(super.a);
        }
    
        public static void main(String[] args) {
            Test t = new Test();
            t.test();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    1.10 继承方式

    java中支持的继承方式

    1.单继承

    class Cat {
        public int age;
    }
    
    class Dog extends Cat{
        public int age;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.多层继承

    class A {
        
    }
    class B extends A{
        
    }
    class C extends B{
        
    }
    class D extends C {
    //可以一直写下去
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.不同类继承同一个类

    class Animals {
        public String color;
        public int age;
    }
    
    class Wolf extends Animals{
        
    }
    
    class Tiger extends Animals{
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    java不支持多继承

    1.11 final关键字

    final 可以用来修饰成员方法,变量,类。

    1. 修饰变量:

    修饰变量或者字段时表示常量,既不能被修改。

    final int a = 10;
    a = 20;//这里会报错,因为常量无法被修改
    
    • 1
    • 2
    1. 修饰类:

    表示这个类不能被继承

    final class Plant {
    
    }
    
    public class Animal extends Plant{
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7


    编译器提示无法继承。

    1. 修饰方法: 表示该方法不能被重写(后序介绍)

    1.12 继承与组合

    组合与继承类似,两者都可以起到代码复用的效果,只是组合的使用的情况更多。

    组合表示的是部分的关系。

    比如显示屏、CPU、键盘是计算机的一部分。

    class Keyboard {
    
    }
    class Display {
    
    }
    class CPU {
    
    }
    
    public class Computer {
        //属性或者是字段
        public Keyboard[] keyboards;
        public Display[] displays;
        public CPU[] cpu;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    每一个类都是Computer的一部分,并且都可以拿出来使用。

  • 相关阅读:
    时间是主观的,每个人对时间的感受都不同#Exploration of Time - 时间的探索
    JavaScript 68 JavaScript Browser BOM 68.8 JavaScript Cookies
    2020华数杯全国大学生数学建模竞赛C题-脱贫帮扶绩效评价体系的构建与探究(二)(附MATLAB和Python代码)
    GStreamer应用开发手册学习笔记之二
    数据库基础
    docker启动命令,docker重启命令,docker关闭命令
    JavaScript处理数组数据-数据匹配-剔除
    Batchsize影响测试集的准确度吗
    HBase-集群部署
    LeetCode 1106. 解析布尔表达式
  • 原文地址:https://blog.csdn.net/m0_63033419/article/details/126364738