• Java的继承


    Java的继承

    ​ 继承是面向对象三大特征之一,可以让类跟类之间产生子父的关系。可以把多个子类中重复的代码抽取到父类中,子类可以直接使用,并且也可以在父类的基础上增加其他功能,使得子类更加强大,减少代码的冗余,提高代码的复用性

    当类与类之间,存在相同(共性)的内容,并满足子类是父类中的一种,就考虑使用继承,来优化代码

    一、继承的格式

    • Java中提供了一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起继承关系
      • public class 子类 extends 父类 {}

    二、继承中的访问权限

    子类访问父类的权限

    父类中:

    1. 构造方法:public修饰的——不能访问 private修饰的——不能访问
    2. 成员变量:public修饰的——能访问 private修饰的——能访问(但不能直接使用
    3. 成员方法:public修饰的——能访问 private修饰的——不能访问

    构造方法不能继承,但可以调用

    三、继承中的注意事项

    • Java只能单继承:一个类只能继承一个直接父类
    • Java不支持多继承,但支持多层继承,当多层继承时,子类也可以直接使用它间接父类中的方法
    • Java中所有的类都是直接或者间接继承与Object类(Java内部的一个类)
    • 子类只能访问父类中非私有的成员

    四、继承成员变量的内存图

    在这里插入图片描述

    1. 测试类先执行,TestStudentclass文件会在方法区加载,main方法进入栈区
    2. 执行main方法的第一行代码,遇到新的Zi类,则将Ziclass文件在方法区加载,加载发现有父类,则会再给父类Fuclass文件在方法区加载(这里应该还要再加载Fu类的默认父类Object类,但是此处省略了),代码的左边会在栈区开辟一个存放地址值的内存空间,代码的右边new关键字会在堆区开辟一个空间(和以前的内存不同,有继承关系,会将new出来的这块内存一分为二,一部分记录父中的成员变量,另一部分会继承子中的成员变量),并默认初始化值,地址返回栈区
    3. 第二行代码,sout——打印语句的缩写,打印z即打印该地址值
    4. 第三、四、五行代码,给znameagegame赋值,会先在堆区开辟的空间的存储子类的空间中查找,若没找到再去存储父类的空间查找,找到并赋值
    5. 最后一行代码,打印这三个值
    6. 最后,main方法执行完了,main方法出栈,方法出栈,原本里面的变量也就消失了,堆区的对象也就不再被调用,就会被回收处理

    与之前非继承的内存图的不同

    • 在加载字节码文件时,会将父类也加载过来
    • 在创建对象时,里面的空间会有一部分存储从父类继承下来的空间,,还有一部分是存储自己(子类)内容的空间
    注意事项
    • 一旦父类中的成员变量用private修饰,子类中就不能用变量名调用(子类会在堆区开辟的内存中寻找,但是是找不到的)

    五、成员方法的继承方法

    ​ 如果要在子类中调用它多层以上的父类中的方法时,子类不会从下而上逐级寻找父类中是否有该方法,而是父类会提前自上而下的依次传递一个可能被经常调用的方法,并放到一个表中(虚方法表)——这样会大大的提高性能

    虚方法表

    • private修饰
    • static修饰
    • final修饰

    只有父类中的虚方法才能被子类继承

    继承成员方法的内存图

    在这里插入图片描述

    1. 首先测试类先运行,TestStudent类的字节码文件会在方法区加载,main方法进入栈区
    2. 第一行代码,用到Zi类,则开始在方法区加载zi的字节码文件,发现有父类,继续加载父类Fu的字节码文件,还有Fu类的父类Object类,都会被加载到方法区,同时开始自上而下依次传承并加载虚方法表,代码的左边会在栈区开辟一个存放地址值的内存空间,代码的右边new关键字会在堆区开辟一个空间(一分为二,因为没有写成员变量,所以看着是空的)
    3. 第二行代码,依旧打印开辟的地址值
    4. 第三,四行代码,通过z调用ziShow()方法,先判断该方法在不在虚方法表中,在的话可以直接调用,发现这些方法在虚方法表中,会直接将这些方法依次加载进栈中开始执行
    5. 第五行代码,判断fushow2()不是虚方法,则不会在虚方法表里面找,会先在本类中寻找该方法,若没有找到,则继续向上父级中寻找,查找到后是私有的,则会报错

    六、继承中的访问特点

    1、成员变量的访问特点:

    • 就近原则:(重名的情况下)

    ​ 先在局部位置找,本类成员位置找,父类成员位置找,主句往上

    示例代码

    public class Test {
        public static void main(String[] args) {
            Zi zi = new Zi();
            zi.ziShow();
        }
    }
    
    class Ye {
        String name = "Ye";
    }
    
    class Fu extends Ye {
        String name = "Fu";
    }
    
    class Zi extends Fu {
        String name = "Zi";
        public void ziShow() {
            String name = "ziShow";
            System.out.println(name);   // 输出: ziShow
            System.out.println(this.name);  // 输出: Zi
            System.out.println(super.name); // 输出: Fu
            // 但不可以再访问父级之上的类了
        }
    }
    
    
    • 最多只能访问到本类的父类之中

    2、成员方法的访问特点:

    • 直接调用满足就近原则:谁离我近,我就用谁
    • super调用,直接访问父类

    示例代码

    public class Test02 {
        public static void main(String[] args) {
            Student s = new Student();
            s.lunch();
    
            OverseasStudent os = new OverseasStudent();
            os.lunch();
        }
    }
    
    class Person {
        public void eat() {
            System.out.println("吃馒头");
        }
        public void drink() {
            System.out.println("喝米汤");
        }
    }
    
    class Student extends Person{
        public void lunch() {
            // 先在本类中查看eat和drink方法,就会调用子类的,如果没有,就会调用从父类继承下来的eat和drink方法
            eat();      // 输出:吃馒头
            drink();    // 输出:喝米汤
    
            // 直接调用父类中的eat和drink方法
            super.eat();    // 输出:吃馒头
            super.drink();  // 输出:喝米汤
        }
    }
    
    class OverseasStudent extends Person {
        public void lunch() {
            this.eat();     // 输出:吃意大利面
            this.drink();   // 输出:喝凉水
    
            super.eat();    // 输出:吃馒头
            super.drink();  // 输出:喝米汤
        }
    
        // 方法的重写
        @Override
        public void eat() {
            System.out.println("吃意大利面");
        }
    
        @Override
        public void drink() {
            System.out.println("喝凉水");
        }
    }
    

    代码中还用到了方法的重写

    方法的重写:

    ​ 当父类的方法不能满足子类现在的需求时,需要进行方法重写

    书写格式

    ​ 在继承体系中,子类出现了和父类中一模一样的方法声明时,我们就称子类这个方法时重写的方法

    @Override重写注释

    1. @Override是放在重写后的方法上,校验子类重写时语法生否正确
    2. 加上注释后如果有红色波浪线,表示语法错误
    3. 建议重写方法都加@Override注释,代码安全,优雅

    方法重写的本质

    自上而下创建虚方法表时,如果发生了重写,则会覆盖虚方法表中的内容,进而达成重写的目的

    方法重写的注意事项和要求:
    1. 重写方法的名称、形参列表必须与父类中的一致
    2. 子类重写父类方法时,访问权限子类必须大于等于父类( 空着不写 < protected < public
    3. 子类重写父类方法时,返回值类型子类必须小于等于父类
    4. 建议:重写的方法尽量和父类保持一致
    5. 只有被添加到虚方法表中的方法才能被重写

    3、构造方法的访问特点:

    • 父类中的构造方法不会被子类继承
    • 子类的所有构造方法默认先访问父类中的无参构造,再执行自己
      • 子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据
      • 子类初始化之前,一定要调用父类构造方法先完成父类数据空间的初始化

    示例代码

    父类:

    public class Person {
        String name;
        int age;
    
        public Person() {
            System.out.println("父类的构造方法");
        }
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    
    

    子类:

    public class Student extends Person{
        public Student() {
            //子类构造方法中隐藏的super()去访问父类的无参构造——且必须写在第一行,不写系统会自动补充空参构造
            super();
            System.out.println("子类的无参构造");
        }
    
        //如果想调用父类的有参构造,必须手动写super进行调用
        public Student(String name, int age) {
            // 将参数传递给父类的有参构造
            super(name, age);
        }
    }
    

    测试类:

    public class Test {
        public static void main(String[] args) {
            // 创建学生对象
            Student s = new Student();
            System.out.println(s.name + ", " + s.age);
    
            // 有参构造创建
            Student s1 = new Student("xiaoming", 19);
            System.out.println(s1.name + ", " + s1.age);
        }
    }
    

    运行结果:

    在这里插入图片描述

    访问特点

    • 子类不能继承父类的构造方法,但是可以通过super调用
    • 子类构造方法的第一行,有一个默认的super()
    • 默认先访问父类中无参的构造方法,再执行自己
    • 如果想要方法文父类有参构造,必须手动书写

    七、thissuper使用总结

    • this:理解为一个变量,表示当前方法调用者的地址值
    • super:代表父类存储空间
    关键字访问成员变量访问成员方法访问构造方法
    thisthis.成员变量 访问本类成员变量this.成员方法(…) 访问本类成员方法this(…) 访问本类构造方法
    supersuper.成员变量 访问父类成员变量super.成员方法(…) 访问父类成员方法super(…) 访问父类构造方法

    this访问本类构造方法:

    public class Student {
        String name;
        int age;
        String school;
    
        public Student() {
            // 表示调用本类其他构造方法——且必须放在第一行
            // 细节:虚拟机就不会再添加super()
            this(null, 0, "java大学");
            System.out.println("1234");	// 1234
        }
    
        public Student(String name, int age, String school) {
            this.name = name;
            this.age = age;
            this.school = school;
        }
    }
    

    测试类:

    public class Test {
        public static void main(String[] args) {
            Student s = new Student();
            System.out.println(s.school);	// java大学
        }
    }
    

    八、带有继承结构的标准的Javabean

    示例:

    在这里插入图片描述

    员工类:

    public class Employee {
        // 带有继承结构的标准的Javabean类
        private String id;
        private String name;
        private int salary;
    
        public Employee() {
        }
    
        public Employee(String id, String name, int salary) {
            this.id = id;
            this.name = name;
            this.salary = salary;
        }
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getSalary() {
            return salary;
        }
    
        public void setSalary(int salary) {
            this.salary = salary;
        }
    
        public void work() {
            System.out.println("员工正在工作");
        }
    
        public void eat() {
            System.out.println("员工正在吃米饭");
        }
    }
    

    经理类:

    public class Manager extends Employee{
        private double bouns;
    
        public Manager() {
        }
        // 全参构造: 父类 + 子类
        public Manager(String id, String name, int salary, double bouns) {
            super(id, name, salary);
            this.bouns = bouns;
        }
    
        public double getBouns() {
            return bouns;
        }
    
        public void setBouns(double bouns) {
            this.bouns = bouns;
        }
    
        @Override
        public void work() {
            System.out.println("管理其他人");
        }
    }
    

    厨师类:

    public class Cook extends Employee{
        @Override
        public void work() {
            System.out.println("炒菜");
        }
    }
    

    测试类:

    public class Test {
        public static void main(String[] args) {
            Manager m = new Manager("001", "小飞", 15000, 8000.0);
            System.out.println(m.getId() + ", " +  m.getName() + ", " +
                    m.getSalary() + ", " + m.getBouns());
            m.work();
            m.eat();
    
            Cook c = new Cook();
            c.setId("002");
            c.setName("小王");
            c.setSalary(9000);
            System.out.println(c.getId() + ", " +  c.getName() + ", " +
                    c.getSalary());
            c.work();
            c.eat();
        }
    }
    

    运行结果:

    在这里插入图片描述
    Over!

  • 相关阅读:
    帮助命令---学习Linux命令的第一步
    Java中如何判断字符串输入[hello,world]是不是字符串数组参数
    【pytorch】深度学习准备:基本配置
    全球 PC 卖不动,Windows 下滑 12%……近 18 万亿市值的微软,全靠 OpenAI 撑着?
    论文阅读之Syntax Encoding with Application in Authorship Attribution(2018)
    「开发者说」钉钉连接器+OA审批实现学校学生假勤场景数字化
    零基础Linux_23(多线程)线程安全+线程互斥(加锁)+死锁
    网工内推 | 合资公司网工,CCNP/HCIP认证优先,朝九晚六
    五、Spring Boot 整合持久层技术(5)
    Kafka CMAK KeeperErrorCode = Unimplemented
  • 原文地址:https://blog.csdn.net/2302_81034736/article/details/143418141