通过之前对 JAVA基础 —— 面向对象 以及 JAVA基础 —— 面向对象内存图 的学习。
接下来我们将会进一步学习面向对象进阶的相关知识点。
static | JAVA基础 (面向对象进阶)—— Static |
继承 | JAVA基础 (面向对象进阶)—— 继承 |
多态 | JAVA基础 (面向对象进阶)—— 多态 |
包、final、权限修饰符、代码块、抽象类和抽象方法方法 | JAVA基础(面向对象进阶) —— 包、final、权限修饰符、代码块、抽象类和抽象方法方法 |
接口 | JAVA基础 (面向对象进阶)—— 接口 |
内部类 | JAVA基础 (面向对象进阶)—— 内部类 |
目录
一、前言
通过前面面向对象的学习,我们认识到了封装的概念:面向对象
对象代表什么,就得封装对应的数据,并提供数据对应的行为。
通过封装,我们可以把零散的数据和方法封装在一起,例如Student类。
但是,当我们又创建了Teacher类且拥有相同的数据和方法的时候,就会发现,代码发生冗余。
![]() | ![]() |
因此,我们就引入了 继承 的概念。
二、 内容概述
1. 继承概念
Java中提供了一个关键字extends,我们可以让一个类和另一个类建立起继承关系。
public class Student extends Person { }
Student称为子类(派生类),Person称为父类(基类或超类)
继承的格式:
public class 子类 extends 父类 { }
2. 使用继承的好处
- 可以把多个子类中重复的代码抽取到父类中了,提高代码的复用性。
- 子类可以在父类的基础上,增加其他的功能,使子类更强大。
3. 什么时候使用继承?
当类与类之间,存在相同(共性)的内容,并满足子类是父类中的一种,就可以考虑使用继承,来优化代码。
4. 继承的特点
- Java只能单继承:一个类只能继承一个直接父类
- Java不支持多继承,但是支持多层继承
- Java中所有的类都直接过间接继承与Object类。
- 子类只能访问非私有的成员
三、 子类继承父类内容
父类内容 | 类别1 | 子类能否继承 | 类别2 | 子类能否继承 |
构造方法 | 非私有 | 不能 | private | 不能 |
成员变量 | 能 | 能 | ||
成员方法 | 能 | 不能 |
1. 构造方法是否可以被继承
假设构造方法构造方法可以被继承,我们在父类中定义一个空参构造,再写一个有参构造
那么子类就可以直接拿过来使用。
![]() | ![]() |
但是我们发现方法名和子类名不一致,违背了构造方法定义规则。 |
public static void main(String[] args) {
//利用空参构造创建类对象
Zi z1 = new Zi();
//利用带参构造创建类对象
//报错: 子类不能继承父类方法
//如果能继承,子类应该可以有参构造
Zi z2 = new Zi("张三",23);
}
class Fu{
String name;
int age;
public Fu() {}
public Fu(String name,int age) {
this.name = name;
this.age = age;
}
}
class Zi extends Fu {
//如果一个类中没有构造方法,虚拟机会自动添加空参构造
}
2. 成员变量是否可以被继承
不管非私有还是私有,父类中的成员变量都可以继承。
但是私有的不能直接使用,可以采用对应的 get / set 方法进行。
内存图:
![]() | ![]() |
![]() |
3. 成员方法是否可以被继承
如果Java虚拟机设计为A类调用方法B然后B类调用方法C,那大错特错。
![]() | |
![]() | 虚方法表:将类中经常使用的方法拉出来 只有父类中的虚方法才能被子类继承 |
内存图:
![]() | ![]() |
![]() |
四、 继承中的访问特点
1. 成员变量的访问特点
![]() | 就近原则:谁离我近,我就用谁 |
现在局部位置找,本类成员位置找,父类成员位置找,逐级往上。 |
public class test01 {
public static void main(String[] args) {
Zi z = new Zi();
z.zishow();
}
}
class Fu{
String name = "fu";
}
class Zi extends Fu{
String name = "zi";
public void zishow() {
String name = "zishow";
System.out.println(name); // zishow
//this不在局部位置找 直接到本类成员位置找
System.out.println(this.name); // zi
//suoer表示父类
System.out.println(super.name); // fu
}
}
2. 成员方法的访问特点
- 直接调用满足就近原则:谁离我近,我就用谁
- super调用,直接访问父类
public class Test {
public static void main(String[] args) {
Student s = new Student();
s.lunch();
}
}
class Person {
public void eat() {
System.out.println("吃米饭,吃菜");
}
public void drink() {
System.out.println("喝开水");
}
}
// 留学生
class OverseaStudent extends Person {
public void lunch() {
this.eat();
this.drink();
super.eat();
super.drink();
}
public void eat() {
System.out.println("吃意大利面");
}
public void drink() {
System.out.println("喝凉水");
}
}
class Student extends Person {
public void lunch() {
// 隐含this. 方法调用需要调用者
// this.eat\this.drink
// 现在本类中查看eat 和 drin 方法
// 如果有就调用, 如果没有就会调用从父类继承下来的eat 和drink 方法
eat();
drink();
// 直接调用父类eat 和drink 方法
super.eat();
super.drink();
}
}
2.1 方法重写
方法的重写:
当父类的方法不能满足子类现在的需求时,需要进行方法的重写。
书写格式:
在继承体系中,子类出现了父类父类中一模一样的方法声明,我们就称子类这个方法是重写的方法。
@Override重写注解:
- @Override是放在重写后的方法上,校验子类重写时语法是否正确。
- 加上注解后如果有红色波浪线,表示语法错误。
- 建议重写方法都加@Override注解,代码安全,优雅!
//父类
class Person {
public void eat() {
System.out.println("吃米饭,吃菜");
}
public void drink() {
System.out.println("喝开水");
}
}
// 子类
class OverseaStudent extends Person {
public void lunch() {
}
@Override
public void eat() {
System.out.println("吃意大利面");
}
@Override
public void drink() {
System.out.println("喝凉水");
}
}
2.2 重写的本质
![]() | |
如果发生了重写,则会覆盖 |
2.3 方法重写注意事项和要求
- 重写方法的名称、形参列表必须与父类中的一致
- 子类重写父类方法时,访问权限子类必须大于等于父类 (暂时了解:空 < protected < public )
- 子类重写父类方法时,返回值类型类型必须小于等于父类。
- 建议:重写的方法尽量和父类保持一致
- 私有方法不能被重写
- 子类不能重写父类的静态方法,如果重写会报错的。
- 只有被添加到虚方法表中的方法才能被重写
2.3 利用方法的重写设计继承结构
现在有三种动物:哈士奇、沙皮狗、中华田园犬
暂时不考虑属性,只要考虑行为。
请按照继承的思想特点进行继承体系的设计。
三种动物分别有以下的行为:
哈士奇 吃饭(吃狗粮) 喝水、看家、拆家 沙皮狗 吃饭(吃狗粮、吃骨头) 喝水、看家 中华田园犬 吃饭(吃剩饭) 喝水、看家
//dog类
public class Dog {
public void eat() {
System.out.println("狗在吃狗粮");
}
public void drink() {
System.out.println("狗在喝水");
}
public void lookHome() {
System.out.println("狗在看家");
}
}
//哈士奇类
public class Husky extends Dog{
//哈士奇额外方法 - 拆家
public void breakHome() {
System.out.println("哈士奇又在拆家");
}
}
//沙皮犬类
public class Sharpei {
//沙皮狗吃饭 - 狗粮和骨头
//父类中的方法不能满足需求 - 需要对方法进行重写
@Override
public void eat() {
super.eat();
System.out.println("狗在啃骨头");
}
}
//中华田园犬类
public class ChineseDog extends Dog{
//中华田园犬吃饭 - 吃剩饭
//父类中的方法不能满足需求 - 需要对方法进行重写
// 而且完全用不到父类方法 所以用不到super
@Override
public void eat() {
System.out.println("吃剩饭");
}
}
//测试类
public class DogTest {
public static void main(String[] args) {
//创建对象并调用
Husky h= new Husky();
h.eat();
h.drink();
h.lookHome();
h.breakHome();
ChineseDog cd = new ChineseDog();
cd.eat();
cd.drink();
cd.lookHome();
}
}
运行结果:
3. 构造方法的访问特点
- 父类中的构造方法不会被子类继承
- 子类中所有的构造方法默认先访问父类的无参构造,再执行自己。
为什么?
- 子类在初始化之前,有可能会使用父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。
- 子类初始化之前,一定要调用父类构造方法先完成父类数据空间的初始化。
怎么调用父类构造方法的?
- 子类构造方法的第一行语句默认都是:super(),不写也存在,且必须放在第一行。
- 如果想调用父类有参构造,必须手动写super进行调用。
//Person.java
public class Person {
String name;
int age;
public Person(){
System.out.println("父类的无参构造");
}
public Person(String name,int age) {
this.name = name;
this.age = age;
}
}
//Student.java
public class Student extends Person{
public Student() {
//子类构造方法中隐藏的super()去访问父类的无参构造
super();
System.out.println("子类的无参构造");
//super()放下面会报错
}
public Student(String name,int age) {
//调用父类带参构造
super(name,age);
}
}
//测试类
public class Test {
public static void main(String[] args) {
//创建学生对象
Student stu1 = new Student();
//此时new Student()中没有数据 将调用子类无参构造
//结果:父类无参构造 子类无参构造
Student stu2 = new Student("zhangsan",23);
System.out.println(stu2.name + "," + stu2.age);
}
}
运行结果:
五、 this、super使用总结
- this:理解为一个变量,表示当前方法调用者的地址值;
- super:代表父类存储空间
关键字 访问成员变量 访问成员方法 访问构造方法 this this.成员变量
访问本类成员变量
this.成员方法( )
访问本类成员方法
this( )
访问本类构造方法
super super.成员变量
访问父类成员变量
super.成员方法( )
访问父类成员方法
super( )
访问父类构造方法
练习:带有继承结构的标准Javabean类
1.经理
成员变量:工号,姓名,工资,管理奖金成员方法:工作(管理其他人),吃饭(吃米饭)
2.厨师
成员变量:工号,姓名,工资
成员方法:工作(炒菜),吃饭(吃米饭)
//Employee.java
public class Employee {
private String id;
private String name;
private double salary;
public Employee() {}
public Employee(String id,String name,double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public void work() {
System.out.println("员工在工作");
}
public void eat() {
System.out.println("吃米饭");
}
}
//Manager.java
public class Manager extends Employee{
private double bouns;
//空参构造
public Manager() {}
//带全部参数构造
public Manager(String id,String name,double salary,double bouns) {
super(id,name,salary);
this.bouns = bouns;
}
public double getBouns() {
return bouns;
}
public void setBouns(double bouns) {
this.bouns = bouns;
}
@Override
public void work() {
System.out.println("管理其他人");
}
}
//Cooker.java
public class Cooker extends Employee{
public Cooker() {}
public Cooker(String id,String name,double salary) {
super(id,name,salary);
}
@Override
public void work() {
System.out.println("厨师在炒菜");
}
}
//测试类
public class Test {
public static void main(String[] args) {
// 创建对象并赋值调用
Manager m = new Manager("001", "张三", 15000, 8000);
System.out.println(m.getId() + "," + m.getName() +
"," + m.getSalary() + "," + m.getBouns());
m.work();
m.eat();
Cooker c = new Cooker();
c.setId("002");
c.setName("李四");
c.setSalary(8000);
System.out.println(c.getId() + "," + c.getName() + "," + c.getSalary());
c.work();
c.eat();
}
}
运行结果: