1. 概述
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。
其中,多个类可以称为子类、派生类,单独那一个类称为父类、超类(superclass)或者基类。
继承描述的是事物之间的所属关系,这种关系是:is-a的关系。
例如,图中兔子属于食草动物,食草动物属于动物。
可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
定义
-
继承:就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
-
在继承的关系中,“子类就是一个父类”,也就是说,子类可以被当做父类看待。父类是员工,子类是讲师,那么”讲师就是一个员工“,关系:is - a.
好处
- 提高代码的复用性。
- 类与类之间产生了关系,是多态的前提。
2. 继承的格式
通过extends
关键字,可以声明一个子类继承另外一个父类
定义格式如下:
public class Fu {
// ...
}
public class Zi extends Fu {
public Zi() {
super();
}
}
从代码的层面来看,在子类的构造方法第一行代码默认就是父类的实例化。
所以子类实例化之前会先实例化父类 。
从设计模式来看,子类继承自父类,需要用到父类的“资源”(没被覆盖的函数以及可见的成员变量等)。
因此需要先实例化父类
3. 继承关系中:成员变量重名问题
如果子类、父类中出现重名的成员变量,这时的访问是有影响的。
在父类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式。
1. 直接通过子类对象访问成员变量名
- 等号左边是谁,就优先用谁,没有则向上找。
2. 间接通过成员方法访问成员变量
- 该方法属于谁,就优先用谁,没有则向上找。
Fu
package cn.luis.demo2;
public class Fu {
int numFu = 10;
int num = 100;
public void methodFu() {
// 使用的是本类当中的num
System.out.println(num);
}
public void method() {
System.out.println("父类重名方法执行");
}
}
Zi
package cn.luis.demo2;
public class Zi extends Fu {
int numZi = 20;
int num = 500;
public void methodZi() {
// 因为本类当中有num,所以使用的是本类当中的num
System.out.println(num);
}
public void method() {
System.out.println("子类重名方法执行");
}
}
测试类
package cn.luis.demo2;
public class Demo01ExtendsField {
public static void main(String[] args) {
// 第一:
// 创建父类对象
Fu fu = new Fu();
System.out.println(fu.numFu); // 只能使用父类的东西
System.out.println("===============");
Zi zi = new Zi();
System.out.println(zi.numFu); // 10
System.out.println(zi.numZi); // 20
System.out.println("===============");
// 等号左边是谁,就优先用谁
System.out.println(zi.num); // 优先子类,500, 若子类没有则向父类寻找。
System.out.println("===============");
// 这个方法是子类的,所以优先用子类的,没有再向上找
zi.methodZi(); // 500
// 这个方法是在父类中定义的,是属于父类的
zi.methodFu(); // 100
// 第二:
zi.method(); // 创建的是子类对象,所以优先使用子类方法。
fu.method();
}
}
结果:
10
===============
10
20
===============
500
===============
500
100
子类重名方法执行
父类重名方法执行
访问成员变量的关键词:super
- 局部变量:直接写变量名
- 本类的成员变量: this.成员变量名
- 父类的成员变量:super.成员变量名
代码:
class Fu {
int num = 10;
}
class Zi extends Fu {
int num = 20;
public void method() {
int num = 30;
System.out.println(num); // 30 局部变量
System.out.println(this.num); // 20 本类的成员变量
System.out.println(super.num); // 10 父类的成员变量
}
}
public class Demo02Extends {
public static void main(String[] args) {
Zi zi = new Zi();
zi.method();
}
}
结果:
30
20
10
注意事项
-
无论是成员变量还是成员方法,如果没有都是向上找父类,绝对不会向下找子类。
-
方法的重写特点: 创建的是子类对象,则优先使用子类方法。
4. 继承关系中:成员方法的重名问题
如果子类、父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写(Override)。
方法重写
子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
@Override
:写在方法前面,用来检测是不是正确的有效的覆盖重写。这个注解就算不写,只要满足要求,也是正确的覆盖重写方法。
代码如下:
public class Fu {
public void show() {
System.out.println(Fu show);
}
}
public class Zi extends Fu {
public void show() {
System.out.println(Zi show);
}
}
public class Demo01ExtendsField {
public static void main(String[] args) {
Zi zi = new Zi();
zi.show(); //Zi show
}
}
// 结果:Zi show
重写的应用
子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。比如新的手机增加来电显示头像的功能。
设计原则
- 对于已投入使用的类,尽量不要进行修改。
- 推荐定义一个新的类,来重复利用其中的共性内容,并且添加改动新内容。
代码:
- 兼容并拓展旧手机功能
/*
定义了一个手机
*/
public class Phone {
public void call() {
System.out.println("打电话!");
}
public void send() {
System.out.println("发短信!");
}
public void show() {
System.out.println("显示号码!");
}
}
/*
定义了一个新手机
*/
class NewPhone extends Phone {
@Override
public void show() {
// 把父类的show方法拿来用
super.show();
System.out.println("显示姓名");
System.out.println("显示头像");
}
}
package cn.luis.demo5;
public class Demo01Phone {
public static void main(String[] args) {
Phone phone = new Phone();
phone.call();
phone.send();
phone.show();
System.out.println("================");
NewPhone newphone = new NewPhone();
newphone.call();
newphone.send();
newphone.show();
}
}
结果:
打电话!
发短信!
显示号码!
================
打电话!
发短信!
显示号码!
显示姓名
显示头像
注意事项
-
子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
-
子类方法的权限修饰符要大于等于父类权限。
public > protect > (default)什么都不写 > private
-
子类方法的返回值范围要小于等于父类方法的返回值范围。
java.lang.Object
是java.lang.String
的父类
5. 继承关系中:构造方法的访问特点
当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?
构造方法的定义格式和作用
- 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
- 构造方法的作用是初始化成员变量的。
子类与父类构造方法关系
-
子类的构造方法中默认有一个
super()
,表示调用父类的构造方法。 -
子类必须先调用父类构造方法,再执行子类构造方法。
因为父类成员变量初始化后,才可以给子类使用。
所以
super()
必须是子类构造方法的第一个语句。 -
一个子类构造不能多次调用super构造。
-
子类构造方法可以通过super关键字来调用父类重载构造。
代码:
public class Fu {
public Fu() {
System.out.println("Fu无参构造方法执行...");
}
public Fu(int n) {
System.out.println("Fu有参构造方法执行...");
}
}
public class Zi extends Fu {
public Zi() {
//super(); // 调用父类构造方法
//super(10); // 重载父类构造方法
System.out.println("Zi构造方法执行...");
}
}
public class Demo01Construction {
public static void main(String[] args) {
Zi zi = new Zi();
}
}
结果:
Fu无参构造方法执行...
Zi构造方法执行...
6. super和this
父类空间优先于子类对象产生
在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。
代码体现在子类的构造方法调用时,一定先调用父类的构造方法。
含义
super:代表父类的存储空间标识,可以理解为父亲的引用。
this:代表当前对象的引用(谁调用就代表谁)。
内存图
用法
访问成员变量和成员方法
this.成员变量 本类的
super.成员变量 父类的
this.成员方法名() 本类的
super.成员方法名() 父类的
代码如下:
class Animal {
int num = 10;
public void eat() {
System.out.println("animal : eat");
}
}
class Cat extends Animal {
int num = 100;
@Override
public void eat() {
System.out.println("Cat : eat");
}
public void test() {
Sthis.num; // 100
super.num; // 10
this.eat(); // Cat : eat
super.eat(); // animal : eat
}
}
public class Demo02Extends {
public static void main(String[] args) {
Cat c = new Cat();
c.test();
Animal a = new Animal();
a.eat();
}
}
结果:
100
10
Cat : eat
animal : eat
访问构造方法
- 子类的每个构造方法中均有默认的
super()
,调用父类的空参构造。手动调用父类构造会覆盖默认的super()
。 super()
和this()
都必须是在构造方法的第一行,所以不能同时出现。
Fu
public class Fu {
public Fu() {
System.out.println("Fu无参构造方法执行");
}
public Fu(int n) {
System.out.println("Fu有参构造方法执行");
}
}
Zi
public class Zi extends Fu {
public Zi() {
this(123); // 本类的无参构造,调用本类的有参构造:Zi(int n)
}
public Zi(int n) {
this(1,2);
}
public Zi(int n, int m) {
super(13); // 必须为父类构造方法传递实参 (因为父类是有参构造函数)
// super(); // 若是无参,可省略,会默认给一个super()
}
}
7. 继承的特点
- Java只支持单继承,不支持多继承。
// 一个类只能有一个父类,不可以有多个父类
class C extends A{}
class C extends A,B...{}
-
Java支持多层继承(继承体系)
顶层父类是Object类,所有的类默认继承Object,作为父类。
class A{}
class B extends A{}
class C extends B{}
- 子类和父类是一种相对的概念。