Java中的继承

文章目录

  • 前言
  • 一、为什么需要继承
  • 二、继承的概念
  • 三、继承的语法
  • 四、父类成员访问
    • 4.1子类中访问父类的成员变量
      • 1.子类和父类不存在同名成员变量
      • 2.子类和父类成员变量同名
    • 4.2子类中访问父类的成员方法
      • 1.成员方法名字不同
      • 2.成员方法,名字相同
  • 五、super和this关键字
  • 六、子类构造方法
  • 七、代码块的执行顺序
  • 八、继承方式
  • 九、final关键字
  • 十、继承和组合

前言

在前面的学习中我们已经讲解了面向对象的三大特性中的封装,接下来我们再来讲解三大特性中的继承

一、为什么需要继承

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

通过观察以上代码,我们发现在狗和猫这两个类中我们定义了一些重复的代码,那么我们就可以使用继承来进行共性的抽取,实现代码的复用

二、继承的概念

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

三、继承的语法

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

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

子类也可以叫做派生类,父类也可以叫做基类和超类
用继承重新设计狗和猫这两个类

class Animal {
    String name;
    int age;
    public void eat() {
        System.out.println(this.name+"正在吃");
    }
}
class Dog extends Animal {
    String color;
    public void bark() {
        System.out.println(this.name+"正在汪汪叫");
    }
}
class Cat extends Animal {
    public void sleep() {
        System.out.println(this.name+"正在睡觉");
    }
}
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        //dog类中并没有定义name和age,name和age属性肯定是从父类Animal中继承下来的
        System.out.println("名字:"+dog.name+" 年龄:"+dog.age+" 颜色:"+dog.color);
        //eat方法也是从父类Animal中继承下来的
        dog.eat();
        dog.bark();
        Cat cat = new Cat();
        //cat类中也没有定义任何成员变量,name和age属性肯定是从父类Animal中继承下来的
        System.out.println("名字:"+cat.name+" 年龄:"+cat.age);
        //这里的eat方法也是从父类Animal中继承下来的
        cat.eat();
        cat.sleep();
    }
}

注意:
1.子类会将父类中的成员变量或者成员方法继承到子类中了
2.子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了

四、父类成员访问

4.1子类中访问父类的成员变量

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

class Base {
    int a;
    int b;
}
class Derived extends Base{
    int c;
    public void fend(){
      a = 5; // 访问从父类中继承下来的a
      b = 10; // 访问从父类中继承下来的b
      c = 15; // 访问子类自己的c
    }
}

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

class Base {
    int a;
    int b;
    int c;
}
class Derived extends Base{
    int a; // 与父类中成员a同名,且类型相同
    char b; // 与父类中成员b同名,但类型不同
    public void method(){
      a = 100; // 访问子类新增的a
      b = 101; // 访问子类新增的b
      c = 102; // 子类没有c,访问的肯定是从父类继承下来的c
      // d = 103; // 编译失败,因为父类和子类都没有定义成员变量b
    }
}

在子类方法中 或者 通过子类对象访问成员时:
1.如果访问的成员变量子类中有,优先访问自己的成员变量
2.如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错
3.如果访问的成员变量与父类中成员变量同名,则优先访问自己的
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找

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

1.成员方法名字不同

class Base {
    public void methodA(){
        System.out.println("Base中的methodA()");
    }
}
class Derived extends Base {
    public void methodB() {
        System.out.println("Derived中的methodB()方法");
    }
    public void methodC(){
        methodB(); // 访问子类自己的methodB()
        methodA(); // 访问父类继承的methodA()
        // methodD(); // 编译失败,在整个继承体系中没有发现方法methodD()
    }
}

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

2.成员方法,名字相同

class Base {
    public void methodA(){
        System.out.println("Base中的methodA()");
    }
    public void methodB(){
        System.out.println("Base中的methodB()");
    }
}
class Derived extends Base{
    public void methodA(int a) {
        System.out.println("Derived中的method(int)方法");
    }
    public void methodB(){
        System.out.println("Derived中的methodB()方法");
    }
    public void methodC(){
        methodA(); // 没有传参,访问父类中的methodA()
        methodA(20); // 传递int参数,访问子类中的methodA(int)
        methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
    }
}

说明:
1.通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
2.通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;

五、super和this关键字

super关键字,该关键字主要作用:在子类方法中访问父类的成员

class Base {
    public int a = 1;
    public int b = 2;
}
class Derived extends Base {
    public int a = 10;
    public int c = 20;
    public void print() {
        System.out.println(super.a);
        System.out.println(this.a);
        System.out.println(this.b);
        System.out.println(super.b);
        System.out.println(this.c);
    }
}
public class Test {
    public static void main(String[] args) {
        Derived derived = new Derived();
        derived.print();
    }
}



优先访问子类的,子类没有再访问父类

注意:
1.只能在非静态方法中使用
2.在子类方法中,访问父类的成员变量和方法
相同点:
1.都是Java中的关键字
2.只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3.在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
不同点:
1.this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
2.在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
3.在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现
4.构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是this(…)用户不写则没有

六、子类构造方法

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

class Base {
    public Base() {
        System.out.println("Base::test()");
    }
}
class Derived extends Base {
    public Derived() {
        System.out.println("Derived::test()");
    }
}
public class Test {
    public static void main(String[] args) {
        Derived derived = new Derived();
    }
}


在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法,因为:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整
注意:
1.若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法
2.如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
3.在子类构造方法中,super(…)调用父类构造时,必须是子类构造函数中第一条语句。
4.super(…)只能在子类构造方法中出现一次,并且不能和this同时出现

七、代码块的执行顺序

在没有继承关系时的执行顺序

class Person {
    public Person() {
        System.out.println("构造方法");
    }
    static {
        System.out.println("静态方法");
    }
    {
        System.out.println("实例方法");
    }
}
public class Test {
    public static void main(String[] args) {
        Person person1 = new Person();
        System.out.println("********");
        Person person2 = new Person();
    }
}


1.静态代码块先执行,并且只执行一次,在类加载阶段执行
2.当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行
有继承关系时的执行顺序

class Person {
    public Person() {
        System.out.println("Person:构造方法");
    }
    static {
        System.out.println("Person:静态方法");
    }
    {
        System.out.println("Person:实例方法");
    }
}
class Student extends Person {
    public Student() {
        System.out.println("Student:构造方法");
    }
    static {
        System.out.println("Student:静态方法");
    }
    {
        System.out.println("Student:实例方法");
    }
}
public class Test {
    public static void main(String[] args) {
        Student student1 = new Student();
        System.out.println("********");
        Student student2 = new Student();
    }
}    


结论如下:
1、父类静态代码块优先于子类静态代码块执行,且是最早执行
2、父类实例代码块和父类构造方法紧接着执行
3、子类的实例代码块和子类构造方法紧接着再执行
4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行

八、继承方式

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

class A {
    ......
}
class B extends A {
    ......
}

多层继承

class A {
    ......
}
class B extends A {
    ...... 
}
class C extends B {
    ......
}

不同类继承同一个类

class A {
    ...... 
}
class B extends A {
    ......
}
class C extends A {
    ......
}

注意:Java中不支持多继承
时刻牢记, 我们写的类是现实事物的抽象. 而我们真正在公司中所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以我们真实项目中所写的类也会有很多. 类之间的关系也会更加复杂.
但是即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.
如果想从语法上进行限制继承, 就可以使用 final 关键字

九、final关键字

final关键可以用来修饰变量、成员方法以及类
1.修饰变量或字段,表示常量(即不能修改)
2.修饰类:表示此类不能被继承
String字符串类,就是用final修饰的,不能被继承
3.修饰方法:表示该方法不能被重写(后序介绍)

十、继承和组合

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。简单说组合其实是一种思想
继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
组合表示对象之间是has-a的关系,比如:汽车

class Tire{
// ...
} /
/ 发动机类
class Engine{
// ...
} /
/ 车载系统类
class VehicleSystem{
// ...
}
class Car{
    private Tire tire; // 可以复用轮胎中的属性和方法
    private Engine engine; // 可以复用发动机中的属性和方法
    private VehicleSystem vs; // 可以复用车载系统中的属性和方法
    // ...
}

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
青葱年少的头像青葱年少普通用户
上一篇 2023年12月14日
下一篇 2023年12月14日

相关推荐