Java——继承

一、引入继承

相同特征产生代码冗余,有如下俩个类(Java学生类和UI学生类)。

// JavaStudent.java
public class JavaStudent {
    private String number;   // 学号
    private String name;   // 姓名
    private int age;   // 年龄
    private String classes;   // 班级
    private String project;   // 项目

    // 考试
    public void exam(){
        System.out.println("学员考试");
    }

    // 登录
    public boolean login(String stuNo, String pwd){
        System.out.println("使用学号和密码登录");
        return true;
    }

    //...
}

// UIStudent.java 
public class UIStudent {
    private String number;   // 学号
    private String name;   // 姓名
    private int age;   // 年龄
    private String classes;   // 班级
    private String opus;   // 作品

    // 考试
    public void exam(){
        System.out.println("学员考试");
    }

    // 登录
    public boolean login(String stuNo, String pwd){
        System.out.println("使用学号和密码登录");
        return true;
    }

    //...
}

经过观察俩个类可以发现,它们都有一些相同特征(相同的属性、相同的方法),比如相同的属性有学号、姓名、年龄、班级;相同的方法有考试和登录。

针对上述发现问题,就有如下解决方案,就是把相同的属性和行为抽取出来,就可以降低重复代码的书写。抽取出来的共性代码单独封装到一个类中。

二、什么是继承

  • 继承是将多个类的相同属性和行为抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承单独这个类即可使用这些属性和行为。
  • 多个类称为子类(派生类),单独的这个类称为父类(基类、超类)。

三、继承的应用场景

当多个类中有相同的代码(属性、行为)时,把相同的代码抽取出来,封装到了一个单独的类中,这样做的好处就是提高代码的复用性(可以重复使用)。

但是这样做就面临一个问题,就是单独封装出来的类和之前被抽取代码的类,怎么建立关系(允许使用单独封装类中的成员)。接下来就解决这个问题。

四、如何使用

  • 使用继承需要在俩个或者多个类之间
  • Java使用关键字extends表示继承

五、语法格式

public class 子类名 extends 父类名{

}

六、is a 

在类与类之间建立继承关系时,必须符合 is a   // 是一个...

要符合现实生活中的认知

比如

  • Java学生是一个学生
  • 猫是一个动物
  • 冰箱是一个电器

如果是如下就不符合了

  • Java学生是一个电器

七、使用继承改造代码

新增一个Student类,把JavaStudent类和UIStudent类中的相同特征进行抽取,如下

// Student.java
public class Student {
    private String number;   // 学号
    private String name;   // 姓名
    private int age;   // 年龄
    private String classes;   // 班级

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    ...(其他属性get、set方法;Student空参构造方法、有参构造方法)

    // 考试
    public void exam(){
        System.out.println("考试");
    }

    // 登录
    public boolean login(String stuNo, String pwd){
        System.out.println("登录");
        return true;
    }
}

// JavaStudent.java public class JavaStudent extends Student{ private String project; // 项目 //...(project属性get、set方法、JavaStudent空参构造方法、有参构造方法) }

// UIStudent.java
public class UIStudent extends Student{
    private String opus;   // 作品
  //...(opts属性get、set方法、UIStudent空参构造方法、有参构造方法)
}

上述代码JavaStudent类和UIStudent类就继承了Student类,抽取完相同特征后,JavaStudent类和UIStudent类只保留了一些特有的特征。子类就可以访问父类的非私有成员。虽然此时JavaStudent类和UIStudent类不能直接访问父类的私有属性,但可以通过get、set方法进行访问。建立一个测试类验证,如下所示

public class Test {
    public static void main(String[] args) {
        JavaStudent javaStu = new JavaStudent("阿里云盘");
        javaStu.setNumber("10010");
        javaStu.setName("张三");
        javaStu.setAge(27);
        javaStu.setClasses("302");

        System.out.println(javaStu.getName() + "---" + javaStu.getProject());   // 张三---阿里云盘
        javaStu.exam();   // 使用父类的方法打印——考试
    }
}

八、继承的使用规范

工作中对于继承应该遵循如下规范

  • 多个类相同特征(共性属性、共性方法)放在父类中定义。
  • 子类扩展的属性和方法应该定义在本子类中。

九、父子类的对比

经过对比也可以发现如下特点

  • 子类和父类相比子类的功能更强大
  • 子类和父类先比父类的范围表示更广

  

十、继承后子类对象的内存原理

基于上述代码,当你在测试类(Test.java)中new一个子类(JavaStudent.java)的时候,系统就会在堆内存中开辟一块新的空间,这个空间就属于JavaStu这个对象,此时,这个空间一分为二,一块叫做父类成员空间——super,一块叫做子类成员空间——this。当我们这些考试方法时javaStu.exam(),会首先进入子类成员空间进行查找,结果是没有找到,接着会进入父类成员空间查找,结果找到了,就调用。

总结如下:

子类对象在创建时,在堆内存中开辟的空间包含了两部分内容

  • super空间:存储父类的相关成员
  • this空间:存储了子类自身的相关成员

子类对象在访问成员时

  • 优先使用子类自己的成员,当子类没有该成员时,去super空间中使用父类的成员,如果父类也没有,会一直向上查找,如果直至Object类都没有的话,会报错。

十一、继承的三个特点

  1. 单继承,只支持单继承,不支持多继承
  2. 一个父类可以有多个子类
  3. 多层继承,子类C继承父类B,父类B可以继承父类A。如果一个类没有直接继承关系,默认继承Object,因此,所有类都是Object类的子类。

十二、继承中变量访问特点

 在子类方法中访问一个变量满足——就近原则

  1. 先子类局部范围找
  2. 然后子类成员范围找
  3. 然后父类成员范围找,如果父类范围内还没有找到则报错
  4. 父类中私有的成员子类不能直接访问

如果局部变量、本类成员变量、父类成员变量重名,应该按照如下进行区分

  1. 局部变量直接访问
  2. 本类成员变量,使用this访问——this.本类成员变量
  3. 父类成员变量,使用super访问,super.父类成员变量
  4. 使用this.变量名时,如果子类没有找到,也会去父类进行查找

十三、继承中方法访问特点

通过子类对象对象访问一个方法也满足——就近原则

  1. 子类成员范围找
  2. 父类成员范围找
  3. 不能直接访问父类中私有成员

如果子父类中出现同名参数的方法,先优先使用子类的,要访问父类相同方法可以使用super关键字,如super.父类成员方法

十四、方法的重写

1、方法的应用体现

方法的应用体现有2种,如下

1.1、重载

有如下特点

  • 在同一个类中
  • 方法名相同
  • 参数列表不相同
  • 和方法返回值没有任何关系

1.2、重写

有如下特点

  • 在父子类中
  • 方法名相同
  • 参数列表相同
  • 方法返回值相同

 2、什么是方法重写

重写就是子类对父类的方法逻辑进行重新编写,当然,这个方法得是子类可访问的实例方法。

3、什么时候需要方法重写

当子类需要父类的功能,但父类的该功能不能完全满足自己的需求时,子类可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的功能。

4、如何快速重写一个方法

声明不变,重新实现,重写时可以定义一个声明一样的方法,对方法体进行重新定义。

如下,我有一个手机类,该类定义了一个方法就是打电话

public class Phone {
    public void call(){
        System.out.println("通话中...");
    }
}

一段时间过后,该方法已经不能完全满足我的需求了,比如,我希望增加视频通话功能,此时,就可以新建一个类(NewPhone),继承自Phone类,对打电话功能(call)进行重写,如下 

// NewPhone.java
public class NewPhone extends Phone{
    public void call(){
        System.out.println("打开摄像头....");

        // 考虑到代码的复用性,可以使用super调用父类中的方法
        super.call();
    }
}

// Test.java 
public class Test {
    public static void main(String[] args) {
        NewPhone newPhone = new NewPhone();
        newPhone.call();   // 打开摄像头....通话中...
    }
} 

5、@Override注解

@Override注解是放在重写后的方法上,作为重写是否正确的校验注解,加上该注解后如果重写错误,编译阶段会出现错误提示。建议重写方法都加上@Override注解,代码安全、优雅。使用方式如下

public class NewPhone extends Phone{
    @Override
    public void call(){
        System.out.println("打开摄像头....");

        // 考虑到代码的复用性,可以使用super调用父类中的方法
        super.call();
    } 
}

6、方法重写时的注意事项和要求

  1. 重写方法的名称和形参列表必须与被重写方法名称和参数列表一致
  2. 私有方法不能被重写
  3. 子类重写父类方法时,子类方法访问权限必须大于或者等于父类方法权限(缺省 < protected < public)

十五、继承中构造方法的使用

1、父类的构造方法会继承到子类吗

不会。构造方法其方法名一定和类名一样,一个类不能存在一个和自己类名不同名的构造方法,但是,子类是可以使用父类的构造方法的,来初始化父类成员变量。

2、子类该如何访问父类的构造方法

在子类构造方法的第一句,使用super关键字访问。

  • super():访问父类无参构造方法(默认存在)
  • super(参数):访问父类有参构造方法,需要初始化父类中成员变量使用

3、继承后子类构造器的执行流程是怎样的

子类中任何一个构造方法执行时,都会先访问父类中的构造方法,然后再执行自己。如果子类构造方法没有显式调用父类构造方法,会默认调用父类的无参构造方法。

所以,如果父类中没有无参数构造器,只有由参构造器,子类构造的话会报错,因为子类默认是调用父类无参构造器的。如下

// Fat.java
public class Fat {
    private String name;

    public Fat(String name) {
        this.name = name;
    }
}

// Son.java
public class Son extends Fat{
    public Son() {   // 报错
    }

    public Son(String name) {
        super(name);
    }
}

如何解决上述问题,如下

  • 为父类添加无参构造方法
  • 在子类构造方法中,通过super,手动调用父类的有参构造方法

十六 、super、this关键字使用总结

1、使用场景梳理

super、this同属于一个对象区别在于

  • this——代表本类对象的引用
  • super——代表父类存储空间的标识

它们的使用格式如下

关键字 访问成员变量 访问成员方法 访问构造方法
this this.成员变量   // 访问本类成员变量 this.成员方法(...)   // 访问本类成员方法 this(...)   // 访问本类构造器
super super.成员变量   // 访问父类成员变量 super.成员方法(...)   // 访问父类成员方法 super(...)   // 访问父类构造器

2、this调用本类构造方法特点

  • 与super访问父类构造方法语法相似。需要在本类构造方法的第一句位置,格式——this(参数)
  • this不能调用自身构造方法,否则会陷入死递归
  • this和super调用构造方法不能共存
  • this调用本类构造方法的意义是——代码的复用,减少代码的冗余

如下

public class Cat {
    private String name;
    private int age;

    public Cat() {
        this("小白", 3);
    }

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // get、set
}

  

原文地址:https://www.cnblogs.com/xulinjun/p/14747684.html