首页 > 编程学习 > 类与对象(十七)----继承extends

类与对象(十七)----继承extends

发布时间:2022/10/2 6:26:06

继承是什么?

面向对象三大巨头知识点----继承
有一个大黄猫类:
属性有:名字,年龄,颜色
方法有:吃鱼,跳,跑
还有一个大白猫类:
属性有:名字,年龄,颜色
方法有:吃猫罐头,跳,跑
可以看到除了吃的方法不一样,其他的几乎都是一样的,那么如果使用代码写出来,就会重复的工作,代码耦合度较高。两只猫或许浪费的时间不多,但如果是成百上千个这种猫类,就比较繁琐。
因此,为了缓解代码的耦合度,可以使用继承这个特性来解决。

继承的概念

继承可以解决代码复用,让编程更接近人类思维,当多个类存在相同的属性和方法时,可以从这些类中抽象出父类,在父类中定义相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends关键字来声明继承符类即可

  • 当子类继承父类时,就会自动拥有父类定义的属性和方法
  • 父类又叫 超类 基类
  • 子类又叫派生类

继承的基本语法

class 子类名 extends 父类名{
}
继承示意图
开头说的那个案例:我们可以将耦合度高的属性和方法抽象出来,重新定义一个父类Cat类,后面所有的猫类不管是黄猫 白猫 小猫 大猫,都继承Cat类。这样就不用再次定义重复的属性和方法了

  • 当子类继承父类后也可以再定义自己的特有的属性和方法。
  • 当子类既有父类的属性和方法,也定义了自己特有的属性和方法再继承给别的类时,别的类就会同时有顶级父类和继承父类的所有属性和方法
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/a3b323282fce48728b14f7514497e218.png

C可以再往下继承,属性方法继续叠加。注意不同分支继承下去的,属性不会继承(例如P类不会有C类的属性)

继承入门案例

以开头说到的猫类问题,使用继承解决代码复用性高的问题
有一个大黄猫类:
属性有:名字,年龄,颜色
方法有:吃鱼,跳,跑

还有一个大白猫类:
属性有:名字,年龄,颜色
方法有:吃猫罐头,跳,跑
根据以上信息发现可抽象出来的属性有:名字,年龄,颜色 方法有跳,跑
因此可以定义一个父类 Cat类 然后创建大黄猫类和大白猫类继承Cat类

//父类
public class Cat {
    String name;
    int age;
    String color;
    public void run(){
        System.out.println("正在跑~~");
    }
    public void jump(){
        System.out.println("正在跳~~");
    }
}
//子类
public class WhiteCat extends Cat{
    public void eat(){
        System.out.println("吃罐头");
    }
}
//子类2
public class YellowCat extends Cat{
    public void eat(){
        System.out.println("吃鱼");
    }
}
//测试类
public class TestExtends {
    public static void main(String[] args) {
        WhiteCat xiaobai = new WhiteCat();
        xiaobai.name = "大白猫";
        xiaobai.age = 4;
        xiaobai.color = "白色";
        System.out.println("白猫信息如下:"+xiaobai.name+xiaobai.color+xiaobai.age+"岁");
        xiaobai.eat();
        xiaobai.run();
        YellowCat xiaohuang = new YellowCat();
        xiaohuang.name = "大黄猫";
        xiaohuang.age = 5;
        xiaohuang.color = "黄色";
        System.out.println("黄猫信息如下:"+xiaobai.name+xiaobai.color+xiaobai.age+"岁");
        xiaohuang.eat();
        xiaohuang.run();

    }
}

输出结果:
在这里插入图片描述
可以看到,虽然两只猫类没有定义属性,还是可以输出赋值,这就是因为继承了Cat的属性和方法。同时也定义了自己的方法eat。

  • 注意点:子类继承父类属性是copy的形式,连同父类赋的值一起copy。需要自己修改成自己想要的值。
    假设将父类Cat类的name属性赋值cat112。然后在测试类中实例化,先不修改自己的name直接输出看看,然后修改再输出看看
public class TestExtends {
    public static void main(String[] args) {
        WhiteCat xiaobai = new WhiteCat();
        YellowCat xiaohuang = new YellowCat();
        System.out.println(xiaobai.name);
        xiaobai.name = "白色";
        System.out.println(xiaobai.name);
        System.out.println(xiaohuang.name);


    }
}

输出结果
在这里插入图片描述
可以看到没有自己修改前,输出的值是父类自己的值。修改后才变成了自己的。没有修改的黄猫的name还是父类的值
因此得出结论:子类继承父类的属性连同赋的值会随着变量一起copy到对象中,需要自己修改。

继承细节

继承细节一:父类的不同修饰符,子类该如何访问。以及父类子类不同包的解决方案

  • 子类继承了父类的所有属性和方法,权限内的属性和方法可以直接在子类访问。权限外的需要通过父类提供public修饰的方法去访问。
    列如 父类中分别定义了:
    public int n1;
    protected int n2;
    int n3;
    private int n4

    那么其中n1和n2,不管子类和父类同不同包,都可以直接访问。
    n3因为是默认修饰符,只有父类和子类在同一个包内,才可以访问。不在同包,子类不可以直接访问,需要通过父类通过的公开方法才能访问。
    n4 不管同包还是不同包,子类都只能通过父类提供的公开方法访问

对于父类的的私有属性,或者是子类没有权限访问的属性。都可以通过跟封装一样的set/get方法来获取和进行修改。

继承细节二:子类继承父类时,构造器的细节

重点要素:子类继承父类时,必须先调用一次父类的构造器(默认调用父类的无参构造器)

  • 子类继承父类时,首先会调用父类的构造器,然后再调用自己的构造器。
    案例演示:
//父类
public class Boos {
    public Boos(){//父类的无参构造器
        System.out.println("父类的无参构造器被调用");
    }
}
//子类
public class Ceo extends Boos{
    public Ceo(){
        System.out.println("子类Ceo的无参构造器被调用");
    }
}
//测试类
public class Test02 {
    public static void main(String[] args) {
        Ceo ceo = new Ceo();
    }
}

输出效果
在这里插入图片描述

  • 因为子类继承父类时会默认调用父类的无参构造器,但是又因为当在类中自己手写了有参构造器,无参构造器就会消失这个特性。所以会导致当父类有有参构造器而无参构造器没有写出来时,子类继承父类时就会报错:找不到父类的无参构造器。

所以为了解决这个问题有两种方法:

  1. 将父类的无参构造器再写出来
  2. 在子类的构造器中使用super关键字代入对应的父类有参构造器的参数,以达到让编译器使用父类的有参构造器的切换。

案例演示:

//父类
public class Boos {
public String name;
    public Boos(String name){//父类的有参构造器
    this.name = name;
        System.out.println("父类的有参构造器被调用");
    }
}
//子类
public class Ceo extends Boos{
    public Ceo(){
    super("张三");
        System.out.println("子类Ceo的无参构造器被调用");
    }
}
//测试类
public class Test02 {
    public static void main(String[] args) {
        Ceo ceo = new Ceo();
    }
}

因为子类继承父类时,通过super触发了父类的有参构造器。所以父类的name被赋值了张三,然后再传给子类Ceo所有属性和方法,因此ceo的name也是张三

  • 当父类和子类都有相同参数的有参构造器,实例化子类对象时会发生什么
  1. 例如一个父类Boss类,手写出他的有参构造器用于构造器修改属性
//父类
public class Boos {
public String name;
    public Boos(String name){//父类的有参构造器
    this.name = name;
        System.out.println("父类的有参构造器被调用");
    }

2.子类同时也写出子类的有参构造器用于构造器修改属性

//子类
public class Ceo extends Boss{
    public Ceo(String name){
        super("张三");
        System.out.println("子类此时的name是:"+this.name);
        this.name = name;
        System.out.println("子类Ceo的有参构造器被调用");
    }
}
  1. 然后在测试类中实例化子类,并输出子类的name属性
public class Tesst03 {
    public static void main(String[] args) {
        Ceo c1 = new Ceo("李四");
        System.out.println(c1.name);
    }
}

输出结果:
在这里插入图片描述
通过输出结果和debug可以看到,通过实例化子类,首先调用的是父类的构造器,将参数穿入父类的构造器,变成张三,然后再调用子类的构造器传入参数变成李四。
通过以上案例测试可以清晰的发现,实例化子类传入的实参( Ceo c1 = new Ceo(“李四”);)是传入子类的构造器的,而父类实际的实参需要在子类的构造器中由super定义。不通用

  • 因此单想要指定调用调用父类的某个构造器时,需在子类的构造器中显示调用:super(参数列表);

super关键字语句只能写在构造器中,且只能是第一条语句

因为super的这个特性和this关键字在构造器中调用另一条构造器的特性相同,都只能是第一条语句,因此this();和super();只能存在一个,否则会冲突。
那么 可以实验以下super关键字不显写出来,而是让子类默认的去调用父类无参构造器,然后再写this看看会不会冲突。

//父类
public class Boos {
    public String name;
    public Boos(){//父类的有参构造
        System.out.println("父类的无参构造器被调用");
    }
}
//子类
public class Ceo extends Boos{
    public Ceo(){
        this("张三");
        System.out.println("子类Ceo的无参构造器被调用");
    }
    public Ceo(String name){
        this.name = name;
        System.out.println("子类Ceo的有参构造器被调用");
    }
}
//测试类
public class Test02 {
    public static void main(String[] args) {
        Ceo ceo = new Ceo();
        System.out.println(ceo.name);

    }
}

输出结果
在这里插入图片描述
可以看到没有编译错误,也就是说如果要在子类构造器中使用this调用其他构造器,那么就不能写super指定调用父类构造器,只能让编译器默认调用父类的无参构造器。

java所有的类都是Object类的子类,Object类是所有类的基类

  • 父类构造器的调用不限于直接父类,它会一直向上追溯直到Object类(顶级父类),然后从Object的构造器开始,依次往下进行调用构造器
    举例说明:
    例如 上面案例的Boos类和它的子类Ceo类,Boos类也是继承于Object类。当实例化子类Ceo时,因为子类中有默认调用的父类构造器,所以会找到Boos的构造器,同理Boos的构造器中同样也有对Object的默认调用构造器。然后从object–>Boos–>Ceo依次进行构造器的调用

在这里插入图片描述

继承细节三:子类最多只能直接继承一个父类,java是单继承机制

现在有三个类 A类 B类 C类
要求让 B类同时有C类A类的属性和方法
根据java中单继承的规定,不可以直接B类继承C类的同时又继承A类,所以我们可以A类继承C类,然后B类继承C类。这样就实现了要求

在这里插入图片描述
ps:虽然java不允许多继承,但是可以实现多个接口

继承不可以滥用,必须满足is a逻辑关系

例如 Person is a Muisc 人是一个音乐,显然是错误的 Cat is a Animal 猫是一个动物,就满足了is a逻辑 Cat类就可以继承Animal类

继承的本质分析

当实例化一个子类的时候内存发生了什么?
先看以下代码
Son - extends-Father-extends-GrandPa

//爷爷类
public class GrandPa {
    String name = "大头爷爷";
    String hobby = "旅游";
}
//父类
public class Father extends GrandPa{
    String name = "大头爸爸";
    int age = 31;
}
//子类
public class Son extends Father{
    String name = "大头儿子";
}
//测试类实例化子类
Son son = new Son();

此时在内存中依次执行了哪些操作:

  1. 在方法区依次加载:Object类 GrandPa类 Father类 Son类
  2. 在堆中开辟空间,并在此空间内划分不同区域依次存放各个继承类的属性
  3. 将此空间的地址给到Son
    图示:
    在这里插入图片描述
    从示意图可以看到在堆中开辟的只有一个空间,而在这个空间中有不同的区域划分,以防止同名的属性冲突。
  • 那么问题就随之而来了如果在测试类实例化son类后,输出son.name。到底输出的是哪个的name呢。
Son son = new Son();
System.out.println(son.name);

输出结果为 大头儿子。
在继承中,查找每个属性是按照查找关系来返回信息的
步骤会如下:

  1. 首先查看子类自己有没有这个属性
  2. 如果子类有,且可以访问,则就返回信息
  3. 如果子类没有这个属性,就看父类(Father类)有没有也
  4. 如果父类(Father类)也没有,就继续向上查找GrandPa类,再没有就Object类。
    也就是依向上查找,并且一旦找到之后如果可以访问就直接返回,如果是不可以访问的修饰符,也不会向上查找另一个了,就直接就地报错没有权限。
  • 就像如果要输出子类son的age,最终会输出 31
    在这里插入图片描述
    查找 hobby
    在这里插入图片描述
    如果再查找过程中发现查找的属性 找到了 但是修饰符是private无法访问,就会报错,只能通过父类提供的公开访问方法访问

继承练习

练习一:

以下代码会输出什么:

class A{
    A(){
        System.out.println("a");
    }
}
class B extends A{
    B(){
        this("abc");
        System.out.println("b");
    }
    B(String name){
        System.out.println("b name");
    }
}
//测试类
B b = new B();

a
b name
b

练习二:

练习当子类中有this调用其他构造器时super的位置
看下面代码会输出什么

class A{
    A(){
        System.out.println("A类的无参构造");
    }
}
class B extends A{
    B(){
        System.out.println("B类的无参构造");
    }
    B(String name){
        System.out.println("B类的有参构造");
    }
}
class C extends B{
C(){
	this("hha");
	System.out.println("C类的无参构造器");
}
C(String name){
	super("554");
	System.out.println("C类的有参构造器");
}
}
//测试类
C c = new C();

执行步骤分析
1.进入c的构造器,此时发现this调用的另一个构造器中有super指向B类的有参构造器。
2.跟着this到C的有参构造器,通过super到B类的有参构造器在执行B类的有参构造之前再跟随默认的super到A类的无参构造器
3.到A类的无参构造器 输出—A类的无参构造
4.再返回到B类的有参构造器 输出 ----B类的有参构造
5.再返回到C类的有参构造器输出—C类的有参构造器
6.this执行完毕返回到C类的无参构造器输出—C类的无参构造器
所以结果为:
A类的无参构造
B类的有参构造
C类的有参构造
C类的无参构造器
总之:实例化子类时,依次从顶级父类开始依次往下执行构造器,当父类重载了构造器时,根据子类构造器中的super参数匹配执行具体的构造器,子类没有传入参数,默认执行无参构造器。

练习三:

编写Computer类,包含Cpu 内存,硬盘等属性,创建getDetails方法用于输出Computer的详细信息
编写EtPc子类继承Computer类,添加特有属性-品牌brand
编写RcPc子类继承Computer类,添加特有属性-颜色color
编写Test04类在main方法中创建EtPC和RcP对象,分别给对象中特有的属性赋值,且给Computer类继承来的属性赋值,并使用方法打印输出信息。

//Computer类
public class Computer {
    String Cpu;
    String Ram;
    String Disk;
    Computer(String Cpu,String Ram,String Disk){
        this.Cpu = Cpu;
        this.Ram = Ram;
        this.Disk = Disk;
    }
    public String getDetails(){
        return "CPU="+Cpu+"内存="+Ram+"硬盘="+Disk;
    }
}
//EtPc子类
public class EtPC extends Computer{
    private String brand;
    EtPC(String Cpu,String Ram,String Disk,String brand){
        super(Cpu,Ram,Disk);
        setBrand(brand);
    }
    public String printInfo(){
        return getDetails()+"品牌="+brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
}
//RcPc子类
public class RcPc extends Computer{
    private String color;
    RcPc(String Cpu,String Ram,String Disk,String color){
        super(Cpu,Ram,Disk);
        setColor(color);
    }
    public String printInfo(){
        return getDetails()+"颜色=" + color;
    }
    public void setColor(String color) {
        this.color = color;
    }
}
//测试类
public class Test04 {
    public static void main(String[] args) {
        EtPC e1 = new EtPC("i9-12600K","32g","1t","联想");
        System.out.println(e1.printInfo());
        RcPc r1 = new RcPc("i5-12600k","16g","500g","黑色");
        System.out.println(r1.printInfo());
    }
}

Copyright © 2010-2022 kler.cn 版权所有 |关于我们| 联系方式|豫ICP备15888888号