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 中创建对象
  • 然后把 heapnew Student() 内存的地址赋值给 stackStudent 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() or Interface 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