《JavaSE》---12.<面向对象系列之(继承)>

2024-09-24 14:52:28 浏览数 (2)

前言

本篇博客主要讲解Java基础语法中的 面向对象系列之 继承的概念、继承的代码示例、父类、子类、继承中成员变量和成员方法的访问、方法的重写、继承关系中构造方法的创建、继承关系中代码的执行顺序(包含代码块)、继承方式、继承与组合。


大家好,本人是普通一本的在校大学生一枚,目前在学习java。之前也学了一段时间,但是没有发布博客。本人现在已经大二结束了,开学就大三了,时间过的真的很快。我会利用好这个暑假,来复习之前学过的内容,并整理好之前写过的博客进行发布。如果博客中有错误或者没有读懂的地方。热烈欢迎大家在评论区进行讨论!!! 喜欢我文章的兄弟姐妹们可以点赞,收藏和评论我的文章。喜欢我的兄弟姐妹们以及也想复习一遍java知识的兄弟姐妹们可以关注我呦,我会持续更新滴,并且追求完整。 望支持!!!!!!一起加油呀!!!!

语言只是工具,不能决定你好不好找工作,决定你好不好找工作的是你的能力!!!!!

学历本科及以上就够用了!!!!!!!!!!!!!!!!!!!!!!!!!!!!


前面我们已经讲过了类和对象,封装等知识。在此基础上,面向对象程序的三大特征封装,继承,多态。就剩下继承和多态了。本文就是为了让大家了解并熟悉什么是继承,那么我们为什么要用到继承呢?这是由于在我们定义的许许多多的类中,难免有许多类具有相似之处,例如狗类,猫类,兔子类这些类都具有相似之处。我们可以去定义一个动物类(父类)然后让这些狗类,猫类,兔子类(这些子类)去继承这个父类。得到父类已经定义好的内容,这样我们创建新的类(子类)时就不用再重新去定义这些变量了。这就被称作继承,需要满足条件子类是父类。比如狗是动物,猫是动物,那么狗和猫类就可以去继承动物类了,这将用到关键字extends。

这就如同我们在附录中讲到的staric关键字来定义静态变量一样。当实例化出来的许许多多的对象有共同之处时,我们就用static关键字来定义静态变量,让这个每个对象都具有的属性不再依赖于对象,而是依赖于类。这样我们就不需要给每个实例化出来的对象都去初始化这个每个对象都拥有的变量了。而继承就是让共有的变量和方法由父类提供给子类,此时子类就不需要再去定义这些变量和方法。话不多说,我们直接进入正题。


一、继承是什么?

1.1继承的概念

继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类

继承呈现了面向对象程序设计的层次结构, 体现了 由简单到复杂的认知过程。

在我看来,继承是类与类之间的一种关系。像父亲和儿子一样。不过继承是父类有的都有。父类没有的自己再创新。不仅包含父类的所有特性,还有自己的特性。

1.2继承的语法

修饰限定符 class 子类 extends 父类 { // 独属于子类的内容... }

例如我们下面代码所演示的class Dog extends Animal此时,Dog就叫做子类,Animal就叫做父类

1.3父类子类的别名

父类:被子类继承的类,也被称作基类、超类

子类:继承父类的类。也被称作派生类

1.4继承的作用

继承主要解决的问题是:共性的抽取,实现代码复用

从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态 ( 后面讲 ) 。

1.4继承的代码实践

我们定义一个父类Animal类,然后创建子类如狗类,猫类,去继承这个Animal类。之后我们用狗类的输出来了解继承的作用。

代码示例:
代码语言:javascript复制
class Animal{//这是我们创建的父类Animal
    //父类成员变量
    String name;
    int age;

    //父类成员方法
    public void eat(){
        System.out.println(name   "正在吃饭");
    }
    public void sleep(){
        System.out.println(name   "正在睡觉");
    }
}
class Dog extends Animal{//这是我们创建的子类狗类。让它去继承Animal
    void wangWang(){
        System.out.println(name   "喜欢汪汪叫!");
    }
}

class Cat extends Animal{//这是我们创建的子类猫类。让它去继承Animal
    void miAo(){
        System.out.println(name   "喜欢喵喵叫!");
    }
}

public class GJiCheng {
    public static void main(String[] args) {
        //用Dog类实例化一个对象
        Dog dog = new Dog();
        //初始化对象
        dog.name = "阿七";
        dog.age = 16;
        //让dog引用父类的成员变量
        System.out.println("所属种类:狗");
        System.out.println("姓名:" dog.name);
        System.out.println("年龄:" dog.age);
        //让dog引用父类的成员方法
        dog.eat();
        dog.sleep();
        //dog引用自己的的成员方法
        dog.wangWang();
    }
}
输出结果:

所属种类:狗 姓名:阿七 年龄:16 阿七正在吃饭 阿七正在睡觉 阿七喜欢汪汪叫!

代码阐释:

由于我们想要定义两个类。一个狗类和一个猫类...由于狗和猫很相似,都属于动物类。如果我们给每个类都写上具体的成员变量和成员方法,那么就太麻烦了。因此我们定义了一个Animal类,之后用extends关键字,让狗类和猫类去继承这个Animal类,这样的话。狗类和猫类就不需要再去写那么多的成员变量和方法了,只需要去补充Animal类中自己没有的属性就好了。因此我写出了如上的简单的代码。

我们看main方法中,我们主要初始化并打印了狗类所创建的对象。我们首先对狗类进行实例化对象,接着用dog去调用父类的成员变量和成员方法以及自己的成员方法,我们可以看到结果,可以成功调用通过编译,并打印出我们想要的结果。

小结:

1. 通过继承 子类会将父类中的成员变量,成员方法继承到子类中,可以通过子类实例化对象,然后去引用父类成员变量和成员方法。

注意:

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

2.存储在对象当中的成员变量,优先存储的是继承父类的成员变量,下来存储的才是自己的成员变量,成员方法同理


二、继承中访问成员变量与方法(this与super的用法)

2.1当父类成员变量、方法与子类成员变量、方法名字不同时

实例化对象后,用对象的引用.点号 变量名 或 方法 引用到哪个成员变量或者成员方法,那么访问的就是哪个成员变量或成员方法。

2.2当父类成员变量、方法与子类成员变量、方法名字相同时

如果单纯用对象的引用. 成员变量 或 成员方法那么只能访问到子类自己的哪个变量或方法。如果想访问到从父类继承下来的成员变量或成员方法,那么我们需要用关键字super点号 成员变量 或 成员方法来引用。这样我们就可以访问到从父类继承下来的那个与子类成员变量或成员方法了

口说无凭:

2.1、2.2代码示例:
代码语言:javascript复制
class Animal{//这是我们创建的父类Animal
    //父类成员变量
    public String name = "父类name";
    public int age = 99;  //注意父类中有姓名和年龄

    //父类成员方法
    public void eat(){
        System.out.println(this.name   "父类正在吃饭");
    }
    public void sleep(){
        System.out.println(this.name   "父类正在睡觉"); //父类中还有吃饭和睡觉
    }
}
class Dog extends Animal{//这是我们创建的子类狗类。让它去继承Animal
    public String name = "子类name";
    public int height = 30;

    public void show(){
        System.out.println(name);                              //1.子类的
        System.out.println(this.name);                         //2.子类的
        System.out.println(super.name);                        //3.父类的
        System.out.println("父类成员变量age:" age);       //4.父类的
        System.out.println("父类成员变量age:" this.age);    //5.父类的
        System.out.println("子类成员变量height:" height);      //6.子类的
        System.out.println("子类成员变量height:" this.height); //7.子类的
        eat();            //8.    //子类
        this.eat();       //9.    //子类
        super.eat();      //10.父类
        sleep();          //11.父类
        this.sleep();     //12.父类
        super.sleep();    //13.父类 
        wangWang();       //14.子类
        this.wangWang();  //15.子类
    }
    public void eat(){
        System.out.println(this.name   "子类正在吃饭");
    }
    void wangWang(){
        System.out.println(this.name   "子类喜欢汪汪叫!");
    }
}

public class GJiCheng {
    public static void main(String[] args) {
        //用Dog类实例化一个对象
        System.out.println("--------------------------------");
        Dog dog1 = new Dog();
        dog1.show();
        System.out.println("--------------------------------");
    }
}

输出结果:

-------------------------------- 1.子类name 2.子类name 3.父类name 4.父类成员变量age:99 5.父类成员变量age:99 6.子类成员变量height:30 7.子类成员变量height:30 8.子类name子类正在吃饭 9.子类name子类正在吃饭 10.父类name父类正在吃饭 11.父类name父类正在睡觉 12.父类name父类正在睡觉 13.父类name父类正在睡觉 14.子类name子类喜欢汪汪叫! 15.子类name子类喜欢汪汪叫! --------------------------------

让我们再详细对比并分析一下打印输出的结果:

1. System.out.println(name); 输出:子类name 2. System.out.println(this.name); 输出:子类name 3. System.out.println(super.name); 输出:父类name 分析: ①②这个成员变量,父类有一个,子类也有一个。有两个name,一个是从父类继承的,一个是自己的。结果打印出了自己的。是因此我们可以知道,如果父类和子类成员变量相同时,会优先在子类中找变量。子类有就先输出子类自己的成员变量。如果子类没有这样成员变量才会输出父类的成员变量。因此1.和2.打印出来的是子类当中的name ③由super引用的成员变量只会在继承父类的成员变量中找。因此会访问从父类继承过来的那个成员变量,因此③打印出来的是父类name

4. System.out.println("父类类成员变量height:" age); 输出:父类成员变量age:99 5. System.out.println("父类成员变量height:" this.age); 输出:父类成员变量age:99 分析:由于子类中只有一个从父类继承来的成员变量age,因此会输出父类成员变量age:99,合情合理

6. System.out.println("子类成员变量height:" height); 输出:子类成员变量height:30 7. System.out.println("子类成员变量height:" this.height); 输出:子类成员变量height:30 分析:这是子类自己的成员变量,输出这个结果合情合理

8. eat(); 输出:子类name子类正在吃饭 9. this.eat(); 输出:子类name子类正在吃饭 10. super.eat(); 输出:父类name父类正在吃饭 分析:这个成员方法,父类有一个,子类也有一个。

11. sleep(); 输出:父类name父类正在睡觉 12. this.sleep(); 输出:父类name父类正在睡觉 13. super.sleep(); 输出:父类name父类正在睡觉 分析:这是从父类继承来的成员方法。这样输出合情合理。既可以用this,也可以用super,也可以什么都不加。

14. wangWang(); 输出:子类name子类喜欢汪汪叫! 15. this.wangWang(); 输出:子类name子类喜欢汪汪叫! 分析:这是子类自己的成员方法,这样输出合情合理, 注意:这个就不能用super关键字引用了

小结:

1.this访问的时候,既可以访问从父类继承下来的,也可以访问子类自己的

2.super只能访问从父类继承过来的。在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可。

3.当父类和子类有相同的成员变量或成员方法时,可以用super去访问从父类继承过来的,不用super则访问的是子类自己的那个成员变量或成员方法。

4.super只是一个关键字。提高代码的可读性,让别人轻松知道这是访问父类中的

注意: 当父类与子类有相同的方法名但是参数不同的情况下,我们传入什么参数匹配到那个方法,那么就会访问到哪个方法.(方法的重载)

方法的重写(规则)

  • 方法签名必须相同:包括方法名、参数列表和返回类型必须与父类的方法一致。
  • 访问权限不能更低:子类重写的方法不能比父类方法的访问权限更低。例如,如果父类方法是 public,子类方法也必须是 public
  • 返回类型可以是子类型:从Java 5开始,允许子类重写方法的返回类型是父类方法返回类型的子类型,这种情况称为协变返回类型。
  • 抛出的异常不能更广泛:子类重写的方法抛出的异常不能比父类方法抛出的异常更广泛,但可以抛出更少的异常或者不抛出异常。
  • @Override 注解:用于明确表示该方法是对父类方法的重写,这有助于编译器进行检查。

5.当父类与子类都有一个方法名相同,参数列相同,返回值类型相同的方法时,这其实就构成了方法的重写.上述代码中的eat方法就实现了方法的重写

在此情况下,如果不用super,那么最终访问到的方法就是子类的方法.与父类的方法无关.

注:

如果一个父类和子类,它们的方法名和参数列表相同,但返回类型不同。这将导致编译错误。

为了避免这种情况,需要确保返回类型一致或者通过使用协变返回类型来处理。 协变返回类型(了解即可) 协变返回类型允许子类的方法返回类型是父类方法返回类型的子类型。

6.super在当前类中使用那么super一定是子类.

注意:super只能在非静态方法中使用,不能在static方法中使用,因为super,this是一样的,是依赖于对象的,而static不依赖于对象,而是类被加载时就被创建出来了.


三、继承中子类的构造方法

当我们要在继承关系下写构造方法时。

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

①父类子类都无构造方法时

父类没有构造方法的情况下,子类也没有构造方法的情况下。系统会默认存在一个参数列表为空,执行内容为空的构造方法。这个在类与对象的构造方法中我们也提到过。与之不同的是,在子类的默认的构造方法中会执行super()来继承父类的构造方法。

代码语言:javascript复制
父类子类都没有构造方法的话系统会默认提供,不过是隐藏的如下:
父类中:public Animal(){
       }
子类中:public Dog() {
            super();
       }

②父类无构造方法,子类有构造方法时

父类没有构造方法的情况下,可以在子类中单独定义自己的构造方法。

代码语言:javascript复制
父类:没有构造方法

子类:public Dog(String name) {
          this.name = name;
      }
此时可以编译通过

③父类中是无参数的构造方法,子类可有构造方法也可以没有构造方法

④父类中有带参数构造方法,子类必须写构造方法

父类有构造方法时。子类会继承父类的构造方法。此时必须要在子类中定义一个构造方法,并且在首行用super();来调用父类的构造方法,帮助父类构造,再构造子类自己的构造方法。这样才可以顺利编译。

代码语言:javascript复制
class Animal{//这是我们创建的父类Animal
    //父类成员变量
    public String name = "父类name";
    public int age = 99;
    
    //父类构造方法
    public Animal(String name) {
        this.name = name;
    }

    //父类成员方法
    public void eat(){
        System.out.println(this.name   "父类正在吃饭");
    }
    public void sleep(){
        System.out.println(this.name   "父类正在睡觉");
    }
}
class Dog extends Animal{//这是我们创建的子类狗类。让它去继承Animal
    public String name;
    public int height = 30;

    public Dog(String name) {//当父类有构造方法时
        super(name);//一定要先帮助父类进行初始化(放在第一行)
        this.name = name;再写子类自己的
    }
}

如上代码中父类Animal与子类Dog中的构造方法。若子类中没有构造方法去用super调用,那么就会编译报错,很好理解,既然子类继承的父类,那么在子类中,就一定要去调用父类的构造方法,帮助从父类继承下来的成员变量进行初始化。

小结:

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

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

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

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


四、继承关系中代码的执行顺序(加上代码块)

我们在《Java》------面向对象系列之static关键字这个文章中讲到过代码块

在那篇文章中我们知道

① 静态代码块先执行,并且只执行一次,在类加载阶段执行 ② 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行

那么加上继承的关系后,又是怎样的执行顺序呢?

父类的静态代码块 > 子类的静态代码块 > 父类的实例代码块 > 父类构造方法 > 子类的实例代码块 > 子类的构造方法

注意:

第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行,静态代码块只执行一次,它用于初始化静态成员变量。因为它是依赖于类的,因此只需要执行一次就能让每个对象都拥有它的值。

小结:

父类的静态代码块 > 子类的静态代码块 > 父类的实例代码块 > 父类构造方法 > 子类的实例代码块 > 子类的构造方法


五、继承方式

1.单继承:A类,B类继承A类

2.多层继承:A类,B类继承A类,C类继承B类...

3.不同类继承同一个类:A类,B类继承A类,C类继承A类...

4.多继承:A类,B类,C类继承A类和B类(Java中不可以这样,C 中可以)


六、继承与组合

和继承类似 , 组合也是一种表达类之间关系的方式 , 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法

( 诸如 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 { // 将汽车中包含的:轮胎、发送机、车载系统全部继承下来 }

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


总结

  • 继承就是对共性的抽取,达到代码的复用。
  • 通过继承子类会将父类中的成员变量,成员方法继承到子类中,可以通过子类实例化对象,然后去引用父类成员变量和成员方法。
  • this访问的时候,既可以访问从父类继承下来的,也可以访问子类自己的
  • super只能访问从父类继承过来的。在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可。
  • 当父类和子类有相同的成员变量或成员方法时,可以用super去访问从父类继承过来的,不用super则访问的是子类自己的那个成员变量或成员方法。
  • super只是一个关键字。提高代码的可读性,让别人轻松知道这是访问父类中的
  • 若父类中用户定义了无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用。
  • 如果父类构造方法是带有参数的,此时需要用户自己为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
  • 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。
  • super(...)只能在子类构造方法中出现一次,并且不能和this同时出现。(局限性)
  • 父类的静态代码块 > 子类的静态代码块 > 父类的实例代码块 > 父类构造方法 > 子类的实例代码块 > 子类的构造方法

目前我了解的继承就这么多了,如果还有什么不足欢迎大家的批评与指正,后面我也会持续优化的

0 人点赞