Java三大特性之一——继承(详细版)

文章目录

一、为什么要继承

首先我们来聊一聊什么是继承,在当今的时代里,我们继承前人的精神,将前人的精神发扬光大,并通过自己的方式将精神传承并发展,这就是继承,简单来说就是我们继承上一辈的人的财产,我们将财产进行使用,当然我们也会有我们自己的财产。
    在Java中,继承和现实中的继承有些类似,但又大相径庭,在Java中的继承本质上是对共性的抽取,从而实现代码的复用,达到简化代码的作用,提高代码的可读性以及代码 的编写效率。
    对狗抽象出一个类:

bash 复制代码
public class Dog{
    private String name;
    private String color;
    private int age;

    public void eat() {
        System.out.println(color + "的" + name + "正在吃饭");
    }
    public void bark() {
        System.out.println(color + "的" + name + "汪汪叫");
    }
}

对猫抽象出一个类

bash 复制代码
public class Cat {
    private String name;
    private String color;
    private int age;

    public void eat() {
        System.out.println(color + "的" + name + "正在吃饭");
    }
    public void mew() {
        System.out.println(color + "的" + name + "喵喵叫");
    }
}

仔细看这两个类,会发现两者都有相同的name , color , age属性,eat行为,单单抽象出两个类就具有相同的内容,如果我们后续还要抽象出过多的动物类,我们每次都需要重复的写相同的代码,不仅效率低下,而且还浪费大量的时间,此时我们就需要使用继承机制。

二、继承

2.1、什么是继承

Java中的继承是面向对象编程的一个重要特性,它允许一个类(子类,派生类)继承另一个类(父类,基类,超类)的属性和方法,通过继承机制,子类可以重用父类的代码,并在原有基础之上进行扩展和增加新的功能。
    继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程,继承提高了代码的复用性,减少了代码冗余,并且继承是实现多态的基础。继承主要解决的问题是:实现共性的抽取,并实现代码的复用。
    猫和狗都属于动物,那么我们可以选择创建一个动物类作为猫和狗的父类,并且让猫和狗继承动物类。

bash 复制代码
public class Animal {//动物类
    protected String name;
    protected String color;
    protected int age;

    public void eat() {
        System.out.println(name + "在吃饭。。。。");
    }
}

2.2、继承的语法

在Java中想要实现继承关系需要用到extends关键字

bash 复制代码
访问修饰限定符 class  子类  extends  父类 {
    子类内容
}

当我们设计好父类后就可以实现继承关系了

bash 复制代码
public class Dog extends Animal{
    public void bark() {
        System.out.println(color + "的" + name + "汪汪叫");
    }
}

此时狗这个类继承了Animal这个类,这也就意味着Dog类里面含有父类的全部,包括被private修饰的成员,那Dog类里面就不必要在创建父类里面有的成员,比如:name,age,color等,子类里面创建的成员和父类的成员一样时,此时就重复了,相当于子类里面有两个相同的成员,重复构造,多此一举。

其中的name,color,age,eat()都是子类Dog中没有到,那说明这些都是由Animal类中继承而来的。


1、子类会将父类中的成员变量或者成员方法继承到子类中

2、子类继承父类之后,必须要添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了。

2.3、父类成员的访问

在子类中可以直接访问父类的成员,但是被private修饰的成员子类也是不可访问的,父类的成员变量,成员方法都可以直接访问

bash 复制代码
public class Dog extends Animal{
    public void bark() {
        System.out.println(color + "的" + name + "汪汪叫");
    }
    protected int a;
    public void Test() {
        //访问子类本身的成员
        a = 20;
        bark();
        //访问父类的成员
        name = "大黄";
        color = "黑色";
        age = 5;
        eat();
    }
}

如果在子类与父类中出现同名的成员,编译器会优先访问子类本身的成员,和局部变量一样,优先访问内部的,如果需要在子类中访问父类的同名成员,那么需要使用super关键字。该关键字能够特指访问父类的成员,父类的成员变量,成员方法均可通过super关键字特指访问

bash 复制代码
public class Dog extends Animal{
    public void bark() {
        System.out.println(color + "的" + name + "汪汪叫");
    }
    protected String name;
    protected String color;
    protected int age;

    public void Test() {
        name = "小白";//访问子类本身的name
        super.name = "小黑";//访问父类的name

        color = "白色";//访问子类本身的name
        super.color = "黑色";//访问父类的name

        age = 3;//访问子类本身的name
        super.age = 5;//访问父类的name
    }
}

在子类方法中 或者 通过子类对象访问父类成员时:
1、如果访问的成员变量中子类有,则编译器中优先访问子类的成员变量
2、如果访问的成员变量中子类没有,则编译器访问父类继承下来的,如果父类也没有,则编译报错
3、如果访问的成员变量与父类中的成员变量同名,则优先访问子类自己的
4、通过子类对象访问与父类同名方法时,如果父类和子类同名方法的参数列表不同(构成重载),根据调用的方法传递的参数选择合适的方法进行访问,如果没有则编译报错;如果参数列表相同(构成重载),那么需要使用super关键字才可以特指访问父类的方法,否则默认访问子类的方法

简单来说就是:成员变量遵循就近原则,自己有就优先访问自己的,自己没有再去父类里面找。

2.4、super关键字

当场景需要或者程序员设计问题,子类和父类中有可能会出现名称相同的成员,如果想要在子类中访问父类的同名成员,那么就需要使用到super关键字,该关键字的主要作用是:在子类中特指访问父类成员.

访问父类的成员时需要使用super关键字,super关键字是获取子类对象中从父类继承下来的部分

如果子类和父类的方法构成重载,可以直接通过参数列表区分访问父类还是子类的方法

如果子类和父类 的方法构成重写,此时需要调用父类的方法则需要使用super关键字
    注意事项:

1、super关键字只能在非静态方法中使用,这也就意味着super关键字也是依赖于对象的。

2、在子类方法中,super用于访问父类的成员变量和成员方法。

2.5、子类构造方法

一般情况下,正常的类里面都会显示定义构造方法,便于对成员方法进行初始化,父类也属于类,同样可以显示定义构造方法进行初始化,子类也可以显示定义构造方法进行初始化成员变量,在子类中也可以对父类的成员变量进行初始化。
    当子类继承父类以后,将来一定会构造子类对象,构造子类对象就一定会调用子类的构造方法,对子类自己特有的成员进行初始化,从父类继承过来的成员也需要初始化,父类的成员只能通过父类的构造方法进行初始化,不能通过子类的构造方法进行初始化。

bash 复制代码
public class Animal {//动物类
    protected String name;
    protected String color;
    protected int age;

    public void eat() {
        System.out.println(name + "在吃饭。。。。");
    }
}
bash 复制代码
public class Dog extends Animal{
    protected int a;

    public void bark() {
        System.out.println(color + "的" + name + "汪汪叫");
    }

    public Dog(int a) {
        this.a = a;
    }
}

当父类中显示定义的是无参构造方法或者父类没有定义构造方法时,子类中显示定义了含参构造方法,或者是没有显示定义构造方法,或者是显示定义无参的构造方法,编译器会自动调用父类的构造方法,不需要我们手动调用。

这也就意味着,只要父类的构造方法是无参的构造方法,不管是程序员显示定义的还是编译器自动提供的,子类中的构造方法都会默认调用父类的构造方法,并且是先执行父类的构造方法才执行子类的构造方法 。
    在子类构造方法中,即便没有写有关于父类的代码,但是在构建子类对象时,先执行父类的构造方法,然后执行子类的构造方法,因为:之类对象中成员由两部分组成,父类继承下来的以及子类新增加的部分。父子父子,肯定是先有父再有子,所以在构造子类对象的时候,先要调用父类的构造方法,将此父类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整

当父类显示定义含参的构造方法时,编译器就不会在子类的构造方法中自动调用父类的构造方法,此时就需要我们手动调用了。那如何调用呢?这里涉及到super关键字的使用了

super关键字有三个用处:
1、通过super关键字调用父类的成员变量
2、通过super关键字调用父类的成员方法
3、通过super( )调用父类的构造方法

需要注意的是:如果父类显示定义含参构造方法时,子类中如果没有调用父类的构造方法时,编译会报错。必须要先执行父类的构造方法才可以执行子类的构造方法。因为当子类继承父类后,虽然得到了父类的全部成员,包括构造方法,当我们使用对象的时候,只会通过子类的实例化对象进行调用成员变量以及访问成员方法,此时只会调用子类的构造方法不会调用父类的构造方法,这也就意味着没办法通过实例化对象进行调用父类的构造方法。那父类的构造方法就没办法调用,但是又因为子类对象构造时,需要先调用父类的构造方法然后才可以执行子类的构造方法。那么只能够在子类中调用父类的构造方法,只需要使用super()即可调用父类构造方法。

需要注意的是:
1、super()的参数列表需要和父类构造方法的参数列表一致
2、super()必须要在构造方法中的第一条语句
3、子类的构造方法的参数列表需要含有父类构造方法的参数
4、super()只能在子类构造方法中出现一次,并且不能和this()同时出现
5、若父类显示定义无参或者默认的构造方法时,在子类构造方法第一行默认有隐含的super()调用,即调用父类的构造方法
6、如果父类构造方法是带有参数的,此时需要程序员为子类显示定义构造方法,并在子类构造方法中选择适合的父类构造方法调用,否则编译失败。

2.5、super关键字和this关键字

this关键字可以理解为对象的引用,当对象引用调用成员方法时,编译器会隐式的将对象引用传给方法,方法中的this变量进行接收,即方法里面的成员变量和成员方法都是通过this引用进行访问的
    super关键字可以特指访问父类的成员变量和成员方法
    相同点:
1、都是Java的关键字
2、只能在类的非静态方法中使用,用来访问非静态成员方法和变量
3、在构造方法中调用时,必须是构造方法中的第一条语句,两者不能同时存在
    不同点:
1、this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来的引用
2、this访问的是本类的方法和变量,super访问的是父类的方法和变量
3、在构造方法中,this()调用的是本类的构造方法,super()调用的是父类的构造方法,两者不能同时在一个构造方法中出现,会产生冲突
4、子类构造方法中一定会存在super()的调用,即便没有编译器也会自动调用,this()编译器不会自动调用

2.6、为什么说super关键字依赖于对象

个人想法是:super()的调用依赖于子类的构造方法,而子类的构造方法是在实例化对象的时候调用的,也就是说子类的构造方法是依赖于对象的出现才得以调用,只有当调用子类的构造方法时,才能够执行父类的构造方法,当父类的构造方法执行完毕之后才会继续执行子类的构造方法。这也就意味着父类的构造方法的调用super()是因为对象的出现才得以调用执行,间接的说明了super关键字是依赖于对象的。在没有实例化子类对象的情况下是没有办法调用父类构造方法。

2.7、再谈初始化

常用的代码块的作用基本上是对对应的变量进行初始化,而不同给代码块之间的执行顺序是不一样的。在没有继承的基础上代码块之间的执行顺序是:静态代码块,构造代码块,构造方法

当类与类之间有了基础关系,代码块之间的执行顺序是否有变化

代码块之间的顺序基本没有变化,值得注意的是:在继承关系上,是优先执行父类的静态代码块然后才执行子类的静态代码块,然后是先执行父类的构造代码块再执行父类的构造方法完成后才执行子类的构造代码块和构造方法。
    可能会有人觉得是执行静态完后,先执行父类的构造代码块后马上执行子类的构造代码块,但实际并非如此,父类的构造代码块是对父类的成员变量初始化,构造方法是对父类的成员变量进行赋值,在某种意义上父类的构造方法也是对变量的初始化,这也就意味着本质上两者是一体的,只是构造代码块先执行,构造方法后执行,构造方法可以将构造代码块的数据覆盖,所以是先执行完父类的构造代码块和构造方法才会去执行子类的构造代码块。

和没有继承关系一样,静态代码块在整个工程里面只会执行一次。随着类的加载而加载
    结论:
1、父类静态代码块优先于子类静态代码块执行,且是最早执行
2、父类实例代码块和父类构造方法紧接执行
3、子类的实例代码块和子类的构造方法紧接再执行
4、第二次实例化子类对象时,父类和子类的静态代码块都不会再执行

2.8、protected

bash 复制代码
import demol2.Animal;
public class Dog extends Animal {
    protected int a;

    public void bark() {
        System.out.println(color + "的" + name + "汪汪叫");
    }

    public Dog(String name, String color, int age, int a) {
        super(name, color, age);
        this.a = a;
        System.out.println("子类的构造方法调用了");
    }
        public void ac() {//此时相当于是在没有继承关系的类里面实例化Animal对象,再通过对象的引用访问成员变量
        Animal animal = new Animal("动物","颜色",20);
        animal.
    }
}

不同包的情况下,父类中被protected修饰的成员变量可以被不同包底下的子类访问,可以同过super关键字或者子类对象进行访问,不可以使用父类对象引用进行访问。
    在子类和父类是不同包的情况下,在子类中创建父类的对象,再通过父类对象的引用访问父类的变量,编译会报错,变量本身确实是属于父类的,但是子类已经继承父类,子类本身是包含有父类继承下来的变量的,所以语法不支持在子类中通过父类的对象的引用去访问父类的变量,此时相当于是在没有继承关系的情况下实例化Animal对象,不同包之间通过对象引用进行访问被protected修饰的成员变量,则编译报错

2.9、继承方式

在Java在,类之间支持单继承,多层继承,不同继承同一个类,不支持一个类继承多个类

Java中不支持多继承,一般不希望出现超过三层的继承关系。
    继承实现了对共性的抽取,并实现代码复用,但同时让类于类之间产生了关系,类的耦合性变强了,当父类出现变化时子类也不得不跟着变化,削弱了子类的独立性,所以不建议出现超过三层的继承关系,层次太深的继承,当总父类出现变化,牵一发而动全身,所有子类都需要变化,代码的编写效率就降低了。
    如果不想被继承,可以使用final修饰类,表示当前类不支持被继承
    final的作用:
1、修饰变量,表示常量,不可更改
2、修饰类,表示当前类不可被继承
3、修饰方法,表示当前方法不支持被重写

2.10、继承与组合

组合不是一种思想,而是一种编写代码的手段,组合是一种表达类之间关系的方式,也能够达到重用代码的效果

仅仅是将一个类的实例作为另外一个类的成员变量

组合和继承都可以实现代码的复用,应该使用继承还是组合,根据场景来选择,一般能用组合尽量使用组合

相关推荐
zwjapple2 分钟前
typescript里面正则的使用
开发语言·javascript·正则表达式
小五Five3 分钟前
TypeScript项目中Axios的封装
开发语言·前端·javascript
小曲程序3 分钟前
vue3 封装request请求
java·前端·typescript·vue
前端每日三省5 分钟前
面试题-TS(八):什么是装饰器(decorators)?如何在 TypeScript 中使用它们?
开发语言·前端·javascript
好看资源平台14 分钟前
网络爬虫——综合实战项目:多平台房源信息采集与分析系统
爬虫·python
凡人的AI工具箱18 分钟前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
陈王卜21 分钟前
django+boostrap实现发布博客权限控制
java·前端·django
小码的头发丝、21 分钟前
Spring Boot 注解
java·spring boot
java亮小白199726 分钟前
Spring循环依赖如何解决的?
java·后端·spring
飞滕人生TYF32 分钟前
java Queue 详解
java·队列