Java继承详解:理解类与类之间的关系,掌握面向对象编程的精髓!

本文围绕Java的继承机制展开,介绍了继承的概念、语法,阐述了在子类中访问父类成员变量和方法的规则,讲解了super关键字、子类构造方法、super和this的区别等内容,还提及了继承方式、final关键字以及继承与组合的关系,旨在帮助开发者掌握Java继承知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

                                                                                个人主页:秋风起,再归来~

                                                                                文章专栏:javaSE的修炼之路                                

                                                                        个人格言:悟已往之不谏,知来者犹可追

                                                                                        克心守己,律己则安!

目录

 1、继承

1.1 为什么需要继承

​编辑

1.2 继承概念

1.3 继承的语法 

1.4 父类成员访问

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

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

1.5 super关键字 

1.6 子类构造方法

1.7 super和this

1.8 再谈初始化

1.9 protect关键字

1.10 继承方式

1.11 final 关键字 

1.12 继承与组合

6、 完结散花


 1、继承

1.1 为什么需要继承

Java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是 现实世界错综复杂,事物之间可能会存在一些关联,那在设计程序是就需要考虑。

比如:狗和猫,它们都是一个动物。

        

使用Java语言来进行描述,就会设计出:

// Dog.java
public class Dog{
    string name;
    int age;
    float weight;
    
    public void eat(){
        System.out.println(name + "正在吃饭");
   }
    
    public void sleep(){
        System.out.println(name + "正在睡觉");
   }
    
    void Bark(){
System.out.println(name + "汪汪汪~~~");
   }
}
 
// Cat.Java
public class Cat{
    string name;
    int age;
    float weight;
    
    public void eat(){
        System.out.println(name + "正在吃饭");
   }
    
    public void sleep()
   {
        System.out.println(name + "正在睡觉");
   }
    
    void mew(){
        System.out.println(name + "喵喵喵~~~");
   }
}

那能否将这些共性抽取呢?

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

1.2 继承概念

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

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

上述图示中,Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类或超类Dog和Cat可以称为Animal的 子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。 从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态(后序讲)。

1.3 继承的语法 

在Java中如果要表示类之间的继承关系,需要借助extends关键字,具体如下:

修饰符 class 子类 extends 父类 {
   // ...  
}

对1.2中场景使用继承方式重新设计:

class Animal{
    String name;
    int age;
    public void eat(){
        System.out.println(name+"正在吃饭!");
    }
    public void sleep(){
        System.out.println(name+"正在睡觉!");
    }
}
class Dog extends Animal{
    public void bark(){
        System.out.println(name+"正在汪汪叫!");
    }
}

class Cat extends Animal{
    public void mew(){
        System.out.println(name+"正在喵喵喵!");
    }
}

public class Main {
    public static void main(String[] args) {
    Dog dog=new Dog();
    dog.name="小黄";
    dog.age=18;
    System.out.println("继承的特征:");
    System.out.println(dog.name);
    System.out.println(dog.age);
    dog.eat();
    dog.sleep();
    System.out.println("==============================");
    System.out.println("自己独有的特征:");
    dog.bark();
    }
}

 注意:

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

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

1.4 父类成员访问

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

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

1. 子类和父类不存在同名成员变量


class Base{
    int a;
    int b;
}
class Derived extends Base{
    int c;
    public void method(){
        a=10;
        b=20;
        c=30;
    }
    public void show(){
        System.out.println("a="+a);
        System.out.println("b="+b);
        System.out.println("c="+c);
    }
}
public class Test_1 {
    public static void main(String[] args) {
    Derived derived =new Derived();
    derived.method();
    derived.show();
    }
}

 2. 子类和父类成员变量同名

class Derived extends Base{
    int a;// 与父类中成员a同名,且类型相同
    char b; // 与父类中成员b同名,但类型不同
    int c;
    public void method(){
        a=10;// 访问父类继承的a,还是子类自己新增的a?
        b=20;// 访问父类继承的b,还是子类自己新增的b?
        c=30;// 子类没有c,访问的肯定是从父类继承下来的c
        // d = 103;  // 编译失败,因为父类和子类都没有定义成员变量b
    }
    public void show(){
        System.out.println("a="+a);
        System.out.println("b="+b);
        System.out.println("c="+c);
    }
}
public class Test_1 {
    public static void main(String[] args) {
    Derived derived =new Derived();
    derived.method();
    derived.show();
    }
}

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

1、如果访问的成员变量子类中有,优先访问自己的成员变量。

2、如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。

3、如果访问的成员变量与父类中成员变量同名,则优先访问自己的。

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

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

1. 成员方法名字不同

class Base2{
    public void methodA(){
        System.out.println("父类中的方法methodA!");
    }
}
class Derived2 extends Base2{
    public void methodB(){
        System.out.println("子类中的方法methodB!");
    }
    public void show(){
        methodA();//访问父类中的方法
        methodB();//访问子类中的方法
        // methodD();// 编译失败,在整个继承体系中没有发现方法methodD()
    }
}
public class Test_2{
    public static void main(String[] args) {
        Derived2 derived =new Derived2();
        derived.show();
    }
}

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

2. 成员方法名字相同 

class Base2{
    public void methodA(){
        System.out.println("父类中的方法methodA!");
    }
    public void methodB(){
        System.out.println("父类中的方法methodB!");
    }
}
class Derived2 extends Base2{
    public void methodB(){//方法名与参数列表与父类完全相同构成重写
        System.out.println("子类中的方法methodB!");
    }
    public void methodA(int a){//参数列表不同,构成重载
        System.out.println("子类当中的methodA!");
    }
    public void show(){
        methodA();//没有传参,访问父类中的methodA()
        methodA(30);//传递int参数,访问子类中的methodA(int)
        methodB();// 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
    }
}
public class Test_2{
    public static void main(String[] args) {
        Derived2 derived =new Derived2();
        derived.show();
    }
}

【说明】

通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到 则访问,否则编译报错。

通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用 方法适传递的参数选择合适的方法访问,如果没有则报错;

问题:如果子类中存在与父类中相同的成员时,那如何在子类中访问父类相同名称的成员呢?

1.5 super关键字 

由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成 员时,该如何操作?直接访问是无法做到的,Java提供了super关键字,该关键字主要作用:在子类方法中访问父 类的成员。

class Base2{
    int a;
    int b;
    public void methodA(){
        System.out.println("父类中的方法methodA!");
    }
    public void methodB(){
        System.out.println("父类中的方法methodB!");
    }
}
class Derived2 extends Base2{
    int a=1;
    int b=2;
    public void methodB(){//方法名与参数列表与父类完全相同构成重写
        System.out.println("子类中的方法methodB!");
    }
    public void methodA(int a){//参数列表不同,构成重载
        System.out.println("子类当中的methodA!");
    }
    public void method3(){
        super.a=10;
        super.b=20;
    }
    public void show(){
        methodA();//没有传参,访问父类中的methodA()
        methodA(30);//传递int参数,访问子类中的methodA(int)
        super.methodB();//super关键字访问到父类的方法methodB
        methodB();// 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
        System.out.println("父类a="+super.a);
        System.out.println("子类a="+a);
        System.out.println("父类b="+super.b);
        System.out.println("子类b="+b);
    }
}
public class Test_2{
    public static void main(String[] args) {
        Derived2 derived =new Derived2();
        derived.method3();
        derived.show();
    }
}

 在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可。

【注意事项】

1. 只能在非静态方法中使用

2. 在子类方法中,访问父类的成员变量和方法。 super的其他用法在后文中介绍。

1.6 子类构造方法

父子父子,先有父再有子,即:子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。

class Base3{
    public Base3(){
        System.out.println("父类构造方法!");
    }
}
class Derived3 extends Base3{
    public Derived3(){
        // super();// 注意子类构造方法中默认会调用基类的无参构造方法:super(),
        // 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
        // 并且只能出现一次
        System.out.println("子类构造方法!");
    }
}
public class Test_3 {
    public static void main(String[] args) {
        Derived3 derived3=new Derived3();
    }
}

在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执 行子类的构造方法,因为:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子 肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整 ,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。

注意:

1. 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构 造方法

2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的 父类构造方法调用,否则编译失败。

3. 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句

4. super(...)只能在子类构造方法中出现一次,并且不能和this同时出现  

1.7 super和this

super和this都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语 句,那他们之间有什么区别呢?

【相同点】

1. 都是Java中的关键字

2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段

3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在

【不同点】

1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成 员的引用

2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性

3. 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造 方法中出现

4. 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有  

1.8 再谈初始化

我们简单回顾一下几个重要的代码块:实例代码块和静态代码块。在没有继承关 系时的执行顺序。

public class Test_4 {
    public Test_4(){
        System.out.println("构造方法执行!");
    }
    {
        System.out.println("实例代码块执行!");
    }
    static{
        System.out.println("静态代码块执行!");
    }
    public static void main(String[] args) {
        Test_4 test1=new Test_4();
        System.out.println("——————————————————");
        Test_4 test2=new Test_4();
    }
}

1. 静态代码块先执行,并且只执行一次,在类加载阶段执行

2. 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行  

【继承关系上的执行顺序】

class Base4{
    public Base4() {
        System.out.println("父类构造方法执行!");
    }
    {
        System.out.println("父类实例代码块执行!");
    }
    static{
        System.out.println("父类静态代码块执行!");
    }
}
class Derived4 extends Base4{
    public Derived4() {
        System.out.println("子类构造方法执行!");
    }
    {
        System.out.println("子类实例代码块执行!");
    }
    static{
        System.out.println("子类静态代码块执行!");
    }
}
public class Test_4 {
    public static void main(String[] args) {
        Derived4 test1=new Derived4();
        System.out.println("——————————————————");
        Derived4 test2=new Derived4();
    }
}

 通过分析执行结果,得出以下结论:

1、父类静态代码块优先于子类静态代码块执行,且是最早执行

2、父类实例代码块和父类构造方法紧接着执行

3、子类的实例代码块和子类构造方法紧接着再执行

4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行

1.9 protect关键字

在类和对象中,为了实现封装特性,Java中引入了访问限定符,主要限定:类或者类中成员能否在类外或者其 他包中被访问。

// 为了掩饰基类中不同访问权限在子类中的可见性,为了简单类B中就不设置成员方法了
// extend01包中
public class B {
    private int a;
    protected int b;
    public int c;
    int d;
}
 
// extend01包中
// 同一个包中的子类
public class D extends B{
    public void method(){
        // super.a = 10;     // 编译报错,父类private成员在相同包子类中不可见
        super.b = 20;         // 父类中protected成员在相同包子类中可以直接访问
        super.c = 30;         // 父类中public成员在相同包子类中可以直接访问
        super.d = 40;         // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问
   }
}
 
// extend02包中
// 不同包中的子类
public class C extends B {
    public void method(){
        // super.a = 10;     // 编译报错,父类中private成员在不同包子类中不可见
        super.b = 20;        // 父类中protected修饰的成员在不同包子类中可以直接访问
super.c = 30;        // 父类中public修饰的成员在不同包子类中可以直接访问
        //super.d = 40;     // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问
   }
}
 
// extend02包中
// 不同包中的类
public class TestC {
    public static void main(String[] args) {
        C c = new C();
        c.method();
        // System.out.println(c.a);   // 编译报错,父类中private成员在不同包其他类中不可见
        // System.out.println(c.b);   // 父类中protected成员在不同包其他类中不能直接访问
        System.out.println(c.c);      // 父类中public成员在不同包其他类中可以直接访问
        // System.out.println(c.d);   // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问
   }
}

 注意:

父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类中了 什么时候下用哪一种呢? 我们希望类要尽量做到 "封装", 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.

因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要 用 public.

另外, 还有一种 简单粗暴 的做法: 将所有的字段设为 private, 将所有的方法设为 public. 不过这种方式属于是 对访问权限的滥用, 还是更希望友友们能写代码的时候认真思考, 该类提供的字段方法到底给 "谁" 使用(是类内 部自己用, 还是类的调用者使用, 还是子类使用

1.10 继承方式

在现实生活中,事物之间的关系是非常复杂,灵活多样,比如:

但在Java中只支持以下几种继承方式: 

 注意:

1. Java中不支持多继承。 时刻牢记, 我们写的类是现实事物的抽象. 而我们真正在公司中所遇到的2. 项目往往业务比较复杂, 可能会涉及到 一系列复杂的概念, 都需要我们使用代码来表示, 所以我们真实项目中所写的类也会有很多. 类之间的关系也会 更加复杂.

3. 但是即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层 次太多, 就需要考虑对代码进行重构了.  

4. 如果想从语法上进行限制继承, 就可以使用 final 关键字(被final修饰的类不能被继承!)

1.11 final 关键字 

final关键可以用来修饰变量、成员方法以及类。

1. 修饰变量或字段,表示常量(即不能修改)

2. 修饰类:表示此类不能被继承

我们平时是用的 String 字符串类, 就是用 final 修饰的, 不能被继承.

3. 修饰方法:表示该方法不能被重写(什么是重写在后面多态会详细介绍)

1.12 继承与组合

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法 (诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。

继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物

组合表示对象之间是has-a的关系,比如:汽车

汽车和其轮胎、发动机、方向盘、车载系统等的关系就应该是组合,因为汽车是有这些部件组成的。

// 轮胎类
class Tire{
    // ...
}
 
// 发动机类
class Engine{
    // ...
}
 
// 车载系统类
class VehicleSystem{
    // ...
}
 
class Car{
    private Tire tire;          // 可以复用轮胎中的属性和方法
    private Engine engine;      // 可以复用发动机中的属性和方法
    private VehicleSystem vs;   // 可以复用车载系统中的属性和方法
    
    // ...
}
 
// 奔驰是汽车
class Benz extend Car{
    // 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}

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

2、 完结散花

好了,这期的分享到这里就结束了~

如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~

如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~

我们下期不见不散~~

评论 133
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋风起,再归来~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值