【Java SE】继承的详解(上)

简介: 继承(inheritance)机制:这个机制是面向对象程序设计中一个可以使代码复用的重要手段,他可以在原有类的特性上进行扩展,增加新的功能,由继承下来的类,称为派生类,从而实现代码复用

1、继承

1.1 继承的概念

这里我们回顾下之前 Student 类,如果今天我们要定义一个 Teacher 类,我们会发现,这两个类有很多共同点:首先学生有姓名性别年龄,老师也有姓名性别年龄,同时学生的行为有上课,老师的行为也有上课这一行为,但是呢,学生与老师又有不同的点,比如学生有成绩,而老师有工资,再比如学生有考试的行为,而老师却有改试卷的行为,所以我们再一想,他们共同的点是不是可以抽取出来形成一个新的类?

继承(inheritance)机制:这个机制是面向对象程序设计中一个可以使代码复用的重要手段,他可以在原有类的特性上进行扩展,增加新的功能,由继承下来的类,称为派生类,从而实现代码复用,就比如我们上面的 Student 和 Teacher 类,抽出共同的部分,然后继承共同的部分,达到代码复用:

通过上图我们发现 Student 和 Teacher 类都继承了 Person 类的属性,其中 Person 称为父类/基类/超类,而 Student,Teacher 可以称为 Person 的子类/派生类,继承之后,子类可以复用父类中的成员,而子类只需要关心自己新增加的成员即可,同时也可以看出,继承最大的作用就是实现代码复用和实现多态(多态下期讲)

1.2 继承的语法和简单使用

现在我们是有了这样的一个思路,如何用代码实现呢?这里我们需要了解 extends 关键字,具体的语法如下:

修饰符 class A extends B {
    // ... 
}

而如上代码中,我们就可以理解成,A类继承了B类,所以A是B的子类,B是A的父类,那我们讲我们上图中画的图用代码实现一下:

public class Person {
    public String name;
    public String sex;
    public int age;
    //老师和学生都需要吃饭和上课
    public void attendClass() {
        System.out.println(name + "正在上课");
    }
    public void eat() {
        System.out.println(name + "正在吃饭");
    }
}
class Student extends Person {
    //学生有成绩
    public float score;
    //学生需要考试
    public void examination() {
        System.out.println(name + "同学正在考试");
    }
}
class Teacher extends Person {
    //老师有工资
    public float salary;
    //老师需要改试卷
    public void modifyPaper() {
        System.out.println(name + "老师正在改试卷");
    }
}

我们先分析下上面的代码:首先 Student 和 Teacher 分别继承了 Person 这个类,也就是说他们继承了父类的成员,所以子类中有两部分,第一部分是父类的成员,第二部分是子类自己的成员,简而言之,就是通过子类实例化的对象的成员里面包含了父类成员。

在一个我们可以观察下他们抽取出来的部分,确实是如我们刚开始所说,把学生和老师共同拥有的属性和行为抽取出来形成一个人的类,接下来我们就用main方法去测试下他们:

class Test {
    public static void main(String[] args) {
        Student student = new Student();
        student.name = "张三";
        student.examination();
        Teacher teacher = new Teacher();
        teacher.name = "李四";
        teacher.modifyPaper();
        student.eat();
        teacher.eat();
    }
}

从这个打印结果也能证明我们前面的结论,子类实例化的对象确实继承了父类的属性和行为!

总结:

  • 子类会将父类中的成员继承到子类中
  • 子类继承父类后,必须要添加自己特有的成员,体现出与父类的不同,否则就没有必要继承了

1.3 子类中访问父类成员出现同名

上面的演示我们也看到了,子类是可以访问父类的成员的(建立在限定符允许的情况,后面讲),那如果出现子类的成员变量和父类成员变量同名怎么办呢?如果成员方法也同名怎么办?

我们简单对上面的学生类做修改:

class Student extends Person {
    //学生有成绩
    public float score;
    public String name = "123";
    //学生需要考试
    public void examination() {
        System.out.println(name + "同学正在考试");
    }
    public void eat() {
        System.out.println("执行了子类的eat");
    }
}
class Test {
    public static void main(String[] args) {
        Student student = new Student();
        student.examination();
        student.eat();
    }
}

这个代码里面的 name 变量和 eat 方法跟父类里的同名了:

如果同名执行的是父类的话,则打印的是:null同学正在考试 和 null正在吃饭 因为如果执行父类则 name 变量没有初始化,默认值是 null。

如果同名执行的是子类的话,则打印的是:123同学正在考试 和 执行了子类的eat 因为我们原地初始化了子类的 name 变量为"123",我们让main方法走起来:

总结:

  • 如果子类成员变量和父类成员变量同名,则优先访问子类的
  • 成员变量优先访问遵循就近原则,自己有优先自己的,自己没有则向父类中找,都没有则报错
  • 如果子类成员方法和父类成员方法同名(没有构成重载),则优先访问子类的
  • 如果子类成员方法和父类成员方法同名(构成了重载),则根据调用方法传递的参数选择合适的成员访问,都没有则报错

如果在子类中存在与父类相同的成员时,我非要访问父类的,怎么办?用super关键字,我们马上了解

2、 super关键字

2.1 如何使用 super 关键字

由于场景的需要,可能会出现父类和子类出现相同名字的成员,如果我们要在子类中访问相同名字的父类成员,直接访问是不可以做到的,这里就可以使用 super 关键字,我们把上面的代码稍作修改:

class Student extends Person {
    //学生有成绩
    public float score;
    public String name = "123";
    //学生需要考试
    public void examination() {
        System.out.println(super.name + "同学正在考试");
    }
    public void eat() {
        System.out.println("执行了子类的eat");
    }
    public void test() {
        super.eat();
    }
}
class Test {
    public static void main(String[] args) {
        Student student = new Student();
        student.examination();
        student.test();
    }
}

如果说 super 可以当子类和父类成员出现同名加上 super 会帮我们访问父类的话,则会打印 null同学正在考试null正在吃饭 我们来看看是不是真的帮我们访问的父类成员:

注意:

  • super 只能在非静态方法中使用,因为只有产生了对象才能访问实例成员变量和成员方法,而静态成员变量和静态成员方法是不依赖于对象的
  • 在子类方法中,访问父类的成员变量或者成员方法
  • super 调用构造方法后面讲

2.2 子类构造方法

之前我们了解过,当创建对象的时候,先要调用类的构造方法,当构造方法调用完毕,对象才真正产生了,那么在我们实例化子类的时候,子类既然要继承父类的属性,需先调用父类的构造方法,然后再执行子类的构造方法,所以当我们什么构造方法都没写的时候,编译器会默认提供无参的构造方法。

既然实例化子类的时候需要先调用父类的构造方法,所以编译器默认给子类提供的构造方法就是这样的:

public Student() {
        super();
    }

也就是说,子类构造方法中,如果用户没有写,编译器会默认调用父类的无参构造方法: super();而且 super();必须是构造方法中的第一条语句,并且只能出现一次。

这里我们做个测试,证明没有写自己写 super();的时候编译器会默认调用:

public class Person {
    public String name;
    public String sex;
    public int age;
    public Person() {
        System.out.println("父类构造方法执行!");
    }
}
class Student extends Person {
    public float score;
    public Student() {
        System.out.println("子类构造方法执行!");
    }
}

总结:子类虽然没有调用父类构造方法,但编译器还是默认带上了, 同时也正如我们所说,先调用父类在调用子类,因为子类对象中的成员有两部分组成,分别是父类继承下来的部分以及子类自己增加的部分,字面意思父子,肯定是现有父后有子,所以在子类实例化的时候,必须先调用父类的构造方法,将父类继承下来的成员构造完整之后,在调用子类的构造方法,将子类自己的成员初始化完整。

注意:

  • 在子类构造方法中,编译器虽然隐含了super() 调用,但是如果父类构造方法是有参的,则需要子类构造方法里显式提供合适父类构造方法的调用
  • super(...) 只能在子类构造方法出现一次,不能和 this 同时出现

2.3 super 和 this 的区别

这个其实也是面试会问到的一个题,下面我们就来看下他们的区别:

相同点:

  • 都是Java中的关键字
  • 都只能在类的非静态方法中使用,用来访问非静态成员和变量
  • 在构造方法中出现的话,必须是构造方法中的第一条语句,而且他俩不能同时出现

不同点:

  • this 是当前对象的引用,而 super 是从父类继承下来部分成员的引用,不能说是父类对象的引用,因为实例化子类的时候并没有创建父类的对象
  • this 用来访问本类的方法和属性,super 用来访问父类的方法和属性(都是非静态)
  • 在构造方法中,this 用来调用本类的构造方法,super 用来调用父类的构造方法
  • 子类构造方法中,super 一定会出现,this 如果不写则没有

图示例:


相关文章
|
3月前
|
Java 程序员
Java中的继承和多态:理解面向对象编程的核心概念
【8月更文挑战第22天】在Java的世界中,继承和多态不仅仅是编程技巧,它们是构建可维护、可扩展软件架构的基石。通过本文,我们将深入探讨这两个概念,并揭示它们如何共同作用于面向对象编程(OOP)的实践之中。你将了解继承如何简化代码重用,以及多态如何为程序提供灵活性和扩展性。让我们启程,探索Java语言中这些强大特性的秘密。
|
15天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
13 3
|
15天前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
27 2
|
15天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
27 2
|
15天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
26 1
|
2月前
|
Java 编译器
封装,继承,多态【Java面向对象知识回顾①】
本文回顾了Java面向对象编程的三大特性:封装、继承和多态。封装通过将数据和方法结合在类中并隐藏实现细节来保护对象状态,继承允许新类扩展现有类的功能,而多态则允许对象在不同情况下表现出不同的行为,这些特性共同提高了代码的复用性、扩展性和灵活性。
封装,继承,多态【Java面向对象知识回顾①】
|
26天前
|
Java 测试技术 编译器
Java零基础-继承详解!
【10月更文挑战第4天】Java零基础教学篇,手把手实践教学!
29 2
|
1月前
|
Java 编译器
在Java中,关于final、static关键字与方法的重写和继承【易错点】
在Java中,关于final、static关键字与方法的重写和继承【易错点】
20 5
|
1月前
|
Java
java继承和多态详解
java继承和多态详解
39 5
|
1月前
|
Java 编译器
【一步一步了解Java系列】:子类继承以及代码块的初始化
【一步一步了解Java系列】:子类继承以及代码块的初始化
20 3