【Java-Day09】继承

目录

面向对象三大特征:封装、继承、多态

一.什么是继承?继承的好处?

二.继承的特点

三.继承中的成员特点

[3.1 继承中成员变量的特点(成员变量的访问顺序)](#3.1 继承中成员变量的特点(成员变量的访问顺序))

继承中成员变量的访问特点:就近原则

this、super关键字

[3.2 继承中成员方法的特点(调用遵循就近原则,子类可重写父类方法)](#3.2 继承中成员方法的特点(调用遵循就近原则,子类可重写父类方法))

书写规则:

调用规则:

方法重写:

什么是方法重写?

方法重写使用场景:

方法重写的要注意:

举个例子:

[3.3 继承中构造方法的特点](#3.3 继承中构造方法的特点)

[3.4 带有继承结构的标准 Javabean 类(继承的练习)](#3.4 带有继承结构的标准 Javabean 类(继承的练习))

四.继承的内存结构,继承的底层原理

[4.1 继承结构的内存图解](#4.1 继承结构的内存图解)

[4.2 子类继承父类时,父类中的构造方法、成员变量、成员方法到底哪些能被子类继承,哪些不能直接使用?](#4.2 子类继承父类时,父类中的构造方法、成员变量、成员方法到底哪些能被子类继承,哪些不能直接使用?)

[1 构造方法不能被继承,但可以用super关键字调用](#1 构造方法不能被继承,但可以用super关键字调用)

[2 成员变量可以被子类继承](#2 成员变量可以被子类继承)

[3 成员方法要看修饰符](#3 成员方法要看修饰符)

虚方法:

虚方法的继承规则

通过虚方法表继承

五.Java中的权限修饰符


面向对象三大特征:封装、继承、多态

一.什么是继承?继承的好处?

继承:

继承 是类与类之间的一种父子关系 ,Java中提供关键字 extends,用于建立类与类之间的关系。

继承的好处:

  • 可以把多个子类中重复的代码抽取到父类中 了,子类可以得到父类的属性和行为,提高代码的复用性
  • 子类可以在父类的基础上,增加其他的功能,使子类更强大

继承的格式:

public class 子类 extends 父类 { }

如何设计继承结构:

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

  • 第一步:利用画图法解决
  • 第二步:分类
  • 第三步:抽取共性的内容不断往上抽取(从下往上画)
  • 第四步:写代码(从上往下写)

二.继承的特点

Java只支持单继承,不支持多继承,但支持多层继承

**单继承:**一个子类只能继承一个父类

**不能多继承:**子类不能同时继承多个父类

多层继承:子类 直接继承的父类叫做直接父类 ,间接继承的爷爷叫做间接父类

Java中的顶级父类 为:**Object,**每一个类都直接或者间接的继承于 Object

三.继承中的成员特点

3.1 继承中成员变量的特点(成员变量的访问顺序)

在 Java 继承中,如果子类和父类中出现了同名成员变量 ,访问时遵循**"就近原则"**。

继承中成员变量的访问特点:就近原则

  • 先在局部位置找,本类成员位置找,父类成员位置找,逐级往上(类似于js中的原型链)

this、super关键字

写法 访问位置
name 就近原则,先从局部变量位置找,再找本类成员变量,再找父类成员变量,逐级往上
this.name 本类成员变量位置找,再找父类成员变量,逐级往上
super.name 从**父类成员变量找,**逐级往上

其中this关键字 代表当前对象自己,通常用在本类里,super关键字代表父类里的东西,通常用在子类继承父类时。

this内存的角度:表示当前方法调用者地址值

this代码的角度:利用this可以直接调用本类成员(比如:成员变量,成员方法,构造方法等)

super关键字:代表使用父类中的内容

|-----------|-----------------------------|---------------------------------|-----------------------------|
| 关键字 | 成员变量 | 成员方法 | 构造方法 |
| this | this.成员变量 访问本类成员变量 | this.成员方法(..) 访问本类成员方法 | this(...) 访问本类构造方法 |
| super | super.成员变量 访问父类成员变量 | super.成员方法(..) 访问父类成员方法 | super(...) 访问父类构造方法 |

java 复制代码
class Fu {
    String name = "Fu";
}

class Zi extends Fu {
    String name = "Zi";

    public void show() {
        String name = "show";

        System.out.println(name);       // 访问局部变量
        System.out.println(this.name);  // 访问子类成员变量
        System.out.println(super.name); // 访问父类成员变量
    }
}

public class Test {
    public static void main(String[] args) {
        Zi z = new Zi();
        z.show();  //输出 show Zi Fu
    }
}

3.2 继承中成员方法的特点(调用遵循就近原则,子类可重写父类方法)

书写规则

  • 把多个子类中共性的成员方法抽取到父类当中。(抽取共性

调用规则

  • 遵守就近原则:子类对象调用方法时,先在子类中找,子类没有再去父类中找。

方法重写:

什么是方法重写?

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

重写的方法上需要加**@override 注解**,校验重写的语法是否正确。

在子类中,重写父类方法需要保证方法申明保持一致:

  • 方法名相同;
  • 参数列表相同,包括参数个数、参数类型、参数顺序;
  • 返回值类型相同,或者返回值类型满足兼容关系;
  • 子类方法访问权限不能比父类更小
  • 方法体可以不同。

子类重写父类方法后,可以通过**super.方法名()**调用父类方法。

方法重写使用场景

如果父类的方法不能满足子类的要求了,子类中可以把该方法再重写一遍

方法重写的要注意:
  1. 如果父类里面的代码,我一行都不想用,此时把子类中的方法体重新完整写一遍即可
  2. 如果父类里面的代码我还想用,此时我只是在父类的基础上再加其他的逻辑。此时可以先通过super关键字调用父类的方法得到一个结果,再对这个结果进行操作即可

关于 final 关键字修饰的重写:

1. final 修饰类

final 修饰的类不能被继承,也就是说该类不能作为父类。(不能被继承就谈不上重写了)

2. final 修饰方法

final 修饰的方法可以被子类继承和调用,但不能被子类重写。

3. final 修饰变量

final 修饰的成员变量在子类可访问的情况下可以被继承使用,但不能被重新赋值。

final 修饰的局部变量不能被重新赋值,并且局部变量不存在继承问题。
不能被重写的方法总结:

1. private 私有方法不能被重写

private 方法只能在本类内部访问,子类无法直接访问父类的 private 方法,因此谈不上重写。如果子类中定义了一个同名方法,它只是子类自己的新方法,不是方法重写。

2. static 静态方法不能被重写

static 方法属于类,不属于对象,而方法重写是基于对象的行为。

如果子类中定义了和父类同名的静态方法,这种情况叫方法隐藏,不叫方法重写。

3. final 最终方法不能被重写

final 方法表示最终方法,子类可以继承和调用它,但不能修改它的实现。

举个例子:
java 复制代码
//父类智能设备
public class SmartDevice {
    private String name;
    private int price;

    public double totalPrice(){
        if(price>=0 && price <1000){
            return price;
        } else if (price < 5000) {
            return price*0.9;
        } else if (price < 10000) {
            return price*0.8;
        } else {
            return price*0.7;
        }
    }
    ....//get/set方法
}
//子类手机
public class Phone extends SmartDevice{
    //重写注解
    @Override
    public double totalPrice(){
        //用super关键字拿到父类的结果再操作
        return super.totalPrice()*0.9;
    }
}
//测试类主函数
public class Test {
    public static void main(String[] args) {
        Phone p1 = new Phone();
        p1.setName("oppo");
        p1.setPrice(3999);
        System.out.println(p1.totalPrice()); //输出:3239.19
    }
}

3.3 继承中构造方法的特点

  • 子类不能继承 父类的构造方法,只能通过super关键字调用。
  • 子类的构造方法第一行有一个默认的super(),先访问父类的无参构造。
  • 如果想要访问父类的带参构造,super必须手动写上,不能省略。子类的带参构造函数的参数是父类加子类的参数 ,用super写父类,子类自身的参数自己赋值。
  • 创建对象 的时候,先执行父类 的构造方法,再执行子类的构造方法。

可以使用快捷键生成子类构造函数,此时会弹出两次弹框,第一次询问写父类构造,第二次询问子类本身构造。

java 复制代码
//父类
public class Person {
    String name;
    int age;
    //父类无参构造
    public Person() {
        System.out.println("父类的空参构造执行了~");
    }
    //父类带参构造
    public Person(String name, int age) {
        System.out.println("父类的带参构造执行了~");
        this.name = name;
        this.age = age;
    }
}
//子类
public class Student extends Person{
    String grade;
    //子类不会继承父类的构造函数,需要自己写构造函数,用super继承父类
    //子类构造方法
    // 1.空参构造
    public Student() {
        System.out.println("子类student的空参构造执行了");
    }
    //2.带全部参数构造(父+子)
    public Student(String name,int age,String grade){
        //父类中的属性:通过super(参数)的形式传递给父类的构造方法赋值
        super(name,age);
        //子类中的属性:自己赋值
        this.grade = grade;
        System.out.println("子类student的带参构造执行了");
    }
}
//主函数
public class Test {
    public static void main(String[] args) {
        Student s = new Student("李华",18,"7");
    }
}

3.4 带有继承结构的标准 Javabean 类(继承的练习)

书写一个完整的继承体系,要求私有化成员变量、get/set方法、构造方法、其他的成员方法

本科学生:

属性:姓名、年龄、年级

行为:吃饭、睡觉、学习(攻读学士学位)

专业课老师:

属性:姓名、年龄、学科

行为:吃饭、睡觉、教书(教专业课知识)

硕士研究生:

属性:姓名、年龄、年级

行为:吃饭、睡觉、学习(攻读硕士学位)

通识课老师:

属性:姓名、年龄、学科

行为:吃饭、睡觉、教书(教通识课知识)

过了一段时间,硕士研究生住宿条件升级,在豪华版学生公寓睡觉

java 复制代码
//Person父类
public class Person {
    //属性
    private String name;
    private int age;
    //行为
    public void eat(){
        System.out.println(name + "要吃饭");
    }
    public void sleep(){
        System.out.println(name + "要睡觉");
    }
    //构造函数
    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //get/set
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
//Student子类
public class Student extends Person {
    //属性
    private int grade;
    //行为
    public void study(){
        System.out.println(super.getName()+"要学习");
    }
    //构造函数
    public Student() {
    }

    public Student(String name, int age, int grade) {
        super(name, age);
        this.grade = grade;
    }
    //get/set
    public int getGrade() {
        return grade;
    }

    public void setGrade(int grade) {
        this.grade = grade;
    }
}

//本科类
public class Benke extends Student{
    public Benke() {
    }

    public Benke(String name, int age, int grade) {
        super(name, age, grade);
    }

    @Override
    public void study(){
        System.out.println(super.getName()+"学习本科知识");
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        Benke s1 = new Benke("李华",18,2);
        s1.study();
        s1.study();
        s1.sleep();
    }
}

四.继承的内存结构,继承的底层原理

子类能调用 不等于 子类能继承

4.1 继承结构的内存图解

程序在刚开始运行的时候,把测试类的字节码文件(class文件)加载到方法区 当中,然后执行Main方法,此时main方法进入栈内存,开始执行第一行代码。

  • 第一行代码用到了 Student 类创建对象 ,这时本来应该把 Student 的字节码文件放到内存当中,但是它发现 Student 继承了父类 Person ,在加载class文件时,他是先加载父类,再加载子类到方法区。
  • 然后开始创建对象,先执行等号左边 表示在栈里声明一个变量,变量名字叫stu,数据类型是Student,他表示:这个变量以后只能存储 Student 这个对象内存地址
  • 再执行等号右边,new 关键字,代表在 里开辟一块空间,这块空间就是对象,他有内存地址。 这个对象还会记录下面字节码文件里方法的内存地址
  • 继承的子类对象 中,堆里开辟的空间会分成两块 ,一块用来存父类继承 下来的数据 ,一块用来存本类自身的数据。然后进行数据初始化(0,null)
  • 最后进行等号赋值 时,就是把右边堆里的对象地址 赋值给左边栈里的 stu 变量。
  • 第二行代码打印 stu 变量打印的是对象的内存地址。
  • 第三四行代码给对象里的属性进行赋值先找子类 里面有没有自己独有的属性,有就给子类里的属性赋值。没有再找继承下来的父类里的属性进行赋值。

注意:

1.加载字节码文件的顺序不同:

  • 没有继承时,加载字节码文件是创建谁就加载谁的字节码文件。
  • 有了继承之后,加载字节码文件是先加载父类字节码文件,再加载子类字节码文件。

2.对象内部的数据不同:

  • 有了继承之后,子类对象的内存被一分为二,一半存父类继承下来的属性,一半存自身独有的属性。

4.2 子类继承父类时,父类中的构造方法、成员变量、成员方法到底哪些能被子类继承,哪些不能直接使用?

1 构造方法不能被继承,但可以用super关键字调用

构造方法名字必须和类名一致。

  • 子类不能继承父类的构造方法。
  • 但是子类创建对象时,会先调用父类构造方法,再执行子类构造方法。

2 成员变量可以被子类继承

父类的私有成员变量会随着父类部分存在于子类对象中,但子类不能直接访问调用,需要通过 public 的 get/set 方法间接访问。

3 成员方法要看修饰符

虚方法可以被继承。

虚方法:

虚方法就是普通的成员方法,非final、非static、非private修饰的方法。

Java中的顶级类是Object类,Object类有5个虚方法:clone(),equals(),finalize(),hashCode(),toString()

虚方法的继承规则

在一个继承结构中,顶级父类会把自己的虚方法 都写到一张虚方法表中,在继承中会把自己的虚方法表交给自己的子类,子类会在继承的虚方法表上加上自己独有的虚方法。

当创建子类的对象时,调用这个对象里的show方法,就是在这个虚方法表里找show方法的内存地址,然后再把show方法加载到栈里运行。

如果在A类里重写了show方法,**方法重写本质上时替换虚方法中的地址,**以后再调用show方法,调用的就是新地址里的方法。

通过虚方法表继承

先在方法区保存父类的字节码文件,字节码文件里包含父类的所用成员方法信息,虚方法表里的方法可以被继承 ,通过虚方法表去继承父类。当访问子类里没有的虚方法,会一级一级向上找虚方法表。像private修饰的方法不在父类虚方法表中,但是是存在于父类的类里。

内容 子类能不能用 说明
构造方法 不能继承 但可以用 super() 调用
public 成员变量 可以继承可以访问 正常使用
private 成员变量 可以继承,不能直接访问 可以通过 get/set 间接访问
public 普通方法/虚方法 可以继承 子类可以直接调用
private 私有方法 不能被继承,不能被调用,不能重写 子类看不见
static 静态方法 不能被继承,可以被调用,不能重写 叫方法隐藏
final 最终方法 不能被继承,可以被调用,不能重写 不能重写

五.Java中的权限修饰符

权限修饰符作用范围从小到大(private < 默认权限 < protected < public

修饰符 同一个类 本包中其他类 不同包下的子类 不同包下的无关类
private
空着 / 缺省 / 默认
protected
public
相关推荐
西安邮电大学1 小时前
Kafka保证消息顺序性
java·后端·kafka
迈巴赫车主1 小时前
蓝桥杯21247弹跳鞋java
java·开发语言·数据结构·算法·职场和发展·蓝桥杯
xinhuanjieyi1 小时前
JavaFX WebView 不支持 Brotli (br) 压缩编码警告修复
java
SoftLipaRZC2 小时前
C语言数据在内存中的存储:整型与浮点型的秘密
c语言·开发语言
Adair_z2 小时前
[SEO艺术重读] 第13篇 SEO教育与研究
java·网络·数据库
悟乙己2 小时前
python DoWhy 库使用案例: SaaS 公司的客服案例
开发语言·python
就叫_这个吧2 小时前
JavaScript基础数据类型、运算符、数组、函数的定义及DOM方式应用
开发语言·前端·javascript
basketball6162 小时前
Golang:基本输入输出使用方法总结
开发语言·golang·xcode
Shingmc32 小时前
【Linux】多路转接之epoll
linux·运维·服务器·开发语言·网络