java 中的继承与多态
I.继承(Inheritance)
把多个类中相同的成员提取出来定义到一个独立的类中, 然后让这个类和该独立的类产生一个关系,这些类就具备了这些内容, 这个关系叫继承。
1.在 Java 中表示继承
- 用关键字
extends
表示 - 格式:
class ChildClass extends FatherClass {}
2.继承的优点
- 提高了代码的复用性
- 提高了代码的维护性
- 让类与类产生了一个关系,这个关系是多态的前提
3.继承的缺点
- 让类的耦合性增强,这个某个类的改变就会影响其他和该类相关的类
- 原则:低耦合, 高内聚
- 耦合: 类与类的关系
- 内聚: 自己完成某件事的能力
- 打破了封装性(子类可以访问父类成员)
4.Java中类继承的特点
- Java中类只支持单继承
- Java中可以多层继承(层级继承体系)
Object
类是继承体系的根类,它是所有类的父类(也叫超类)
5.继承的注意事项
- 子类
不能继承
父类的私有
成员 - 子类
不能继承
父类的构造方法
,但是可以通过super
去访问 - 不要为了部分功能去继承
6.继承的使用时机
- 继承体现的是
is a
的关系 - 采用假设法: 子类xxx是父类
7.Java继承中的成员关系(非私有成员)
- 成员变量:
- 子类的成员变量名称和父类成员变量名称不一样,访问对应的
- 子类的成员变量名称和父类成员变量名称一样,如何访问? 就近原则
- 在子类方法的局部范围找,有就使用
- 在子类的成员范围找,有就使用
- 在父类的成员范围找,有就使用(非私有成员)
- 找不到就报错
- 构造方法:
- 子类的
所有
构造方法默认会先
去访问父类的无参构造方法
- 子类所有构造方法第一条语句默认的是
super()
- 原因是因为子类会继承父类中的数据,可能还会使用父类的数据,所以在子类初始化之前,一定要先完成父类数据的初始化(通过执行父类的构造方法)
- 子类所有构造方法第一条语句默认的是
- 父类中如果没有无参构造方法
- 子类通过
super(args)
去明确调用带参构造方法 - 子类通过
this()
去调用子类的其他构造方法,但其他构造方法一定有一个访问了父类的构造方法 - 让父类提供无参构造
- 子类通过
- 子类的
- 成员方法:
- 子类的成员方法名称和父类成员方法名称不一样,访问对应的
- 子类的成员方法名称和父类的方法名称一样,如何访问?就近原则
- 在子类方法的局部范围找,有就使用
- 在子类的成员范围找,有就使用
- 在父类的成员范围找,有就使用(非私有成员)
- 找不到就报错
- 注意子类想要访问父类的私有成员,需要通过 public 的带参构造方法或者 public 的 getter/setter 方法去访问
II. 方法重写(override) 和方法重载(overload)的区别
1.方法重写
方法重写: override,子类中出现了和父类中方法声明一模一样的方法,包括方法名,参数列表和返回值类型
override特点:(先找子类本身,再找父类)
- 如果子类父类方法名不同,就掉用对应的方法
- 如果子类父类方法名相同,最终使用的是子类自己的
override应用:当子类需要父类的功能,而功能主体有自己特有内容时,可以重写父类中的方法。这样既沿袭了父类的功能,又定义了子类特有的内容
override注意事项:
- 父类中私有方法不能被重写,因为父类的私有成员子类根本无法继承
- 子类重写父类方法时,访问权限只能扩大而不能降低(private -> protected -> public)
- 父类有静态方法,子类必须通过静态方法进行重写(本质并不是重写,在多态中有详细讲解)
2.方法重载
方法重载: overload,本类中出现方法名一样,参数列表不同的方法。方法重载与返回值类型无关,方法重载可以改变返回值类型。
overload特点:调用方法时,JVM会通过参数列表来选择调用不同的同名方法
III. this 关键字 和 super 关键字的区别
1. this 关键字 & super 关键字
- this关键字代表当前类的引用对象
- super关键字代表父类存储空间的标识(父类在堆heap内存中的标识),可以理解成访问父类的引用
2. 使用场景区别
- 成员变量:
- this.成员变量:访问本类的成员变量
- super.成员变量:访问父类的成员变量(只能直接访问非私有成员变量)
- 构造方法:this 和 super 必须出现在构造方法中的第一条语句上
- this(…):本类的构造方法
- super(…):父类的构造方法 (非私有构造方法)
- 成员方法:
- this.成员方法:本类的成员方法
- super.成员方法: 父类的成员方法 (非私有成员方法)
III. 类的初始化过程
1.一个类的初始化过程
Student s = new Student();
s 的初始化过程:
- 把
Student.class
文件加载到内存的方法区,加载 static 成员到方法区的静态区,执行 static 代码块 - 在内存栈空间
stack
中给引用变量Student s
开辟一个空间 - 在内存堆空间
heap
中给new Student()
开辟一个空间 - 给
new Student()
中的成员变量进行初始化:- 默认初始化:
private int a;
(null,0, false) - 显示初始化:
private int a = 1;
- 通过构造方法传进来的参数进行成员变量初始化
- 默认初始化:
- 将静态区的 static 成员的地址赋值给
heap
中的成员 - 在方法区中创建
new Student()
的方法区,包括构造方法和非静态方法,将方法的地址标记赋值给new Student()
中的构造方法和成员方法 - 初始化完毕,执行构造方法在 heap 中创建对象
- 然后把
heap
中new Student()
内存的地址赋值给stack
中Student s
变量
2.子父类的构造执行过程
- 一个类的静态代码块,构造代码块,构造方法的执行流程: 静态代码块 > 构造代码块 > 构造方法
- 静态的内容随着类的加载而加载, 静态代码块的内容会优先执行
- 子类初始化之前会先进行父类的初始化
//初始化顺序
父类静态代码块
子类静态代码块
父类构造代码块
父类构造方法
子类构造代码块
子类构造方法
3.分层初始化
如果有 GrandFatherClass, FatherClass, 和 ChildClass 三个类,在初始化子类的时候,先初始化 GrandFatherClass的数据(成员), 完毕后再初始化 FatherClass(成员), 最后再初始化 ChildClass(成员)。
虽然子类的构造方法中默认有一个 super()
, 但是在初始化子类的时候,并不是按照子类中的构造方法的语句顺序执行。而是按照分层初始化进行的。 子类构造方法中的 super()
仅仅表示要先初始化父类的数据,再初始化子类的数据
IV. Final 关键字
Final 关键字可以修饰类,方法和变量。
1.特点:
- Final修饰的类,不可被继承
- Final修饰的方法, 不可被重写
- Final修饰的变量, 是一个常量
2.特性
- Final修饰局部变量
- 基本类型变量: 变量的值不可以改变
- 引用类型变量: 变量的地址值不可以改变,但是引用的对象中的成员可以改变
- 初始化时机
- 只能初始化一次
- 赋值时机:
- 在成员范围定义 final 变量的时候直接进行赋值
- 在成员范围定义 final变量,但是在构造方法中赋值
V. 多态 (Polymorphism)
多态,是同一个对象在不同时刻体现出来的不同状态,叫做多态。
1.多态的前提:
- 有继承(extends)或实现(implements) 关系
- 有方法重写(override)
- 有父类或者父接口引用指向子类对象:
Father f = new Children()
orInterface if = new InterfaceImpl()
2.多态的分类
- 具体类多态
class Father {}
class Children extends Father {}
Father f = new Children();
- 抽象类多态
abstract class Father {}
class Children extends Father {}
Father f = new Children();
- 接口多态
interface Father {}
class Children implements Father {}
Father f = new Children();
3.多态中成员访问特点
- 成员变量:编译看左边, 运行看左边。即访问的成员变量必须在等号左边的类或接口中有声明
- 构造方法:子类的构造方法都会默认访问父类构造方法
- 成员方法:编译看左边,运行看右边。即访问的成员方法必须在等号左边的类或接口中有声明,但运行时是执行等号右边的类中的方法(因为成员方法有重写)
- 静态成员:编译看左边,运行看左边。因为静态方法是属于类的,所以等号左边声明哪个类,就访问哪个类的静态成员
4.多态的优点
- 提高代码的维护性 (继承体现)
- 提高代码的扩展性 (多态体现)
5.多态弊端
- 父类不能使用子类的特有功能
- 现象:子类可以当作父类使用,父类不能当作子类使用
6.多态中的转型
- 向上转型: 从子类到父类
Father f = new Children();
- 向下转型: 从父类到子类
Children c = (Children) f;
多态的向下转型必须要保证在堆内存的实际对象一定是子类的对象,否则就会报错。可以用 instanceof
关键字来避免
if(f instanceof Children)
Children c = (Children) f;
Share this on