继承和多态
继承
-
Java类中只支持单继承,即一个类只能有一个直接父类。
-
类与类之间有三种关系,分别是“is a”,“use a”,“has a”,继承表达的是“is a”的关系,如Dog is a Pet,学生 is a 人,苹果 is a 水果等。
[访问修饰符] class 类名 extends 父类名{
}
// 访问修饰符:public【项目任何地方都可以访问类】 缺省【当前包中】
// 访问修饰符:
// public : 项目任何地方都可以访问
// protected : 同包下任意类可以访问【其他包子类也可以访问 static 】
// private : 只能自身访问
// package-access : 同包下任意类可以访问
java基本概念
- 子类可以继承父类的非私有属性和方法。子类可以使用父类的属性和方法,无需重新编写相同的代码。
- 子类可以添加自己的属性和方法。子类可以增加父类中没有的属性和方法,从而增加代码的灵活性和可扩展性。
- 子类的构造方法可以调用父类的构造方法。在子类的构造方法中使用super关键字可以调用父类的构造方法,从而初始化父类的属性。
- Object类是所有类的根类。每个类都是Object类的子类,因此可以使用Object类中定义的一些通用方法,如equals()、hashCode()、toString()等。
- Java中的类只支持单一继承,每个类只能有一个父类(直接父类)。
- 子类可以成为其他类的父类,从而建立多级继承关系。但是,过多的继承可能会导致代码难以理解和维护
继承是java的三大特征之一
继承就是子类继承父类的特征和行为,来实现充实自身内容
格式:使用extends 关键字申明一个类是从另一个类继承而来
现有类称为父类、超类、基类,新类称为子类、扩展类
继承的作用:代码复用,增加软件的可扩展性
继承分为单继承、多继承,java中类只支持单继承,也就是一个类只能有一个直接父类
继承的应用
Java中继承是一种面向对象编程的核心概念,它可以让子类继承父类的属性和方法,并且还可以在此基础上进行扩展和重写,提高了代码的复用性和可维护性。
public class Pet {
private String name;
private int age;
public String color;
protected double weight;
public Pet(){}
public Pet(String name, int age, String color, double weight,
String voice) {
this.name = name;
this.age = age;
this.color = color;
this.weight = weight;
this.voice = voice;
}
String voice;
private void privateMethod () {
System.out.println("这是一个私有方法");
}
public void eat() {
System.out.println("这是一个eat方法");
}
protected void sleep() {
System.out.println("这是一个protected方法");
}
void packageAccessMethod() {
System.out.println("这是一个packageAccess方法");
}
}
使用继承和重写实现子类
public class Dog extends Pet {
// Dog is a Pet Dog 是子类 Pet 是父类 子类是对于父类的扩展
private String lookHouse;
public Dog() {
}
public Dog(String name, int age, String color, double weight,
String voice, String lookHouse) {
super(name, age, color, weight, voice);
this.lookHouse = lookHouse;
}
public void lookingHouse() {
System.out.println("狗狗看家");
}
}
使用super关键字调用父类成员
通过super访问父类成语语法规则如下:
调用父类构造方法: super([实参列表]);
调用父类属性和方法:super.<父类字段名/方法名>
super只能出现在子类(子类的实例方法或构造方法)中,而不是其他位置
super用于访问父类成员,如父类的属性、方法、构造方法。
具有访问权限的限制,如无法通过super访问父类private成员。
super用在子类构造函数中时,必须是子类构造函数的第一行代码
方法重写
在子类中可以根据需求对从父类继承的方法进行重新编写,这称为方法的重写或方法的覆盖(overriding)。方法重写必须满足如下要求
-
在继承关系中。
-
重写方法与被重写方法必须有相同的方法名称。
-
重写方法与被重写方法必须有相同的参数列表。
-
重写方法的返回值类型必须和被重写方法的返回值类型相同或是其子类。
-
重写方法不能缩小被重写方法的访问权限。
-
不能用子类的非静态方法重写(覆盖)父类的静态方法,否则编译报错。
-
不能重写父类中的最终方法。
-
不能用子类的静态方法重写父类的实例方法。
方法隐藏
父类和子类拥有相同名字的属性或者方法(方法隐藏只有一种形式,就是父类和子类存在签名相同的静态方法)时,父类的同名的属性或者方法形式上不见了,实际是还是存在的。
隐藏是对于静态方法和成员变量(静态变量和实例变量)而言的:
当发生隐藏的时候,声明类型是什么类,就调用对应类的方法,而不会发生动态绑定
属性只能被隐藏,不能被覆盖
变量可以交叉隐藏:子类实例变量/静态变量可以隐藏父类的实例/静态变量
不能用子类的静态方法隐藏父类中的非静态方法,否则编译报错。
子类实例化过程
先有父类再有子类 加载类 A 时,要先看类 B 是否被加载了(类 B 是否是第一次使用), 如果是第一次使用 类 B 则先加载类 B 到内存, 进行静态初始化(static 变量, static初始化器 <clinit>), 此时类 B 被加载到 了内存中。 然后再加载子类 A,进行静态初始化(static 变量, static初始化器)。此时类 A 也 被加载到内存中。 ps: 类只有在第一次使用时进行加载及静态初始化 实例化(new 对象) A: 现有父类对象再创建子类对象 类 B 的实例初始化(实例初始化器, 构造 <init>), 此时父类对象就创建完成了。 类 A 的实例初始化(实例初始化器, 构造 <init>), 此时对象就创建完成了。
super关键字
使用
super表示父类引用
super([参数列表])调用父类的构造方法,在调用构造方法时必须放到方法的第一行。
super.属性表示调用父类的属性,super.方法([参数列表])表示调用父类的方法。
注意:
super只能出现在子类(实例方法,构造方法)中
super用于调用父类的成员,方法、属性、构造方法
super([参数列表])在调用父类构造方法时,要写在构造方法代码的第一行
super不能调用父类private修饰的属性和方法
final关键字
final可以用来修饰变量(包括类属性、对象属性、局部变量和形参)、方法(包括类方法和对象方法)和类。
修饰变量
final 修饰变量:final 修饰的变量称为常量,一旦被初始化,就无法再被修改。
常量必须在声明时或在构造函数中初始化。
常量的命名一般采用大写字母和下划线,例如:final int MAX_SIZE = 100;
修饰方法
使用final修饰方法,该方法不能被子类重写
final 修饰的方法称为不可覆盖方法,子类无法重写该方法。
通常情况下,不可覆盖方法是指类中的一些不能被子类重写的方法,
例如 Object 类中的 notify() 和 wait() 方法
修饰类
final 修饰的类称为不可继承类,表示该类不能被其他类继承。
一般情况下,不可继承类是指一些工具类或常量类
例如:java.lang.Math 类和 java.lang.String 类。
Object类
Object 类 是 所 有 类 的 父 类 。 在 Java 中 , 所 有 的 Java 类 都 直 接 或 间 接 地 继 承 了java.lang.Object 类。Object类是所有Java类的祖先。在定义一个类时,没有使用extends 关键字,也就是没有显式地继承某个类,那么这个类直接继承Object类。所有对象都继承这个类的方法。
String toString() 返回当前对象本身的有关信息,返回字符串对象
boolean equals(Object) 比较两个对象是否是同一个对象,若是,返回true
Object clone() 生成当前对象的一个副本,并返回
int hashCode() 返回该对象的哈希码值
void finalize() 在垃圾收集器将对象从内存中清除出之前做必要的清理工作。(过时方法)
void wait() 线程等待
void wait(int) 线程等待
void wait(long, int) 线程等待
void notify() 线程唤醒
void notifyall() 线程唤醒
Class getClass() 获取类结构信息,返回Class对象
-
重写equals()方法可以让两个对象的内容相同时被判定为相等。
-
重写hashCode()方法使得具有相同内容的对象具有相同的哈希码。
-
toString()方法的重写使得我们在打印对象时获得了有意义的信息。
-
这些方法都是Object类中定义的,但我们通过重写它们,让它们在子类中有了更具体的实现。
在Java中,java.util.Objects类提供了许多静态方法,用于处理Java对象的常见操
作。以下是一些常用方法:
equals(Object a, Object b) 比较两个对象是否相等,避免了空指针异常;
hash(Object… values) 生成对象的哈希码,也可用于多个字段组成的复合键的哈希码生成;
requireNonNull(T obj) 检 查 对 象 是 否 为 null , 如 果 是 null 则抛出NullPointerException 异常;
isNull(Object obj) 检查对象是否为 null,返回一个 boolean 值;
nonNull(Object obj) 检查对象是否不为 null,返回一个 boolean 值;
toString(Object o) 返回对象的字符串表示形式,如果对象为 null 则返回"null;
this和super的区别
方法的区别:
this访问本类中的方法,如果本类没有这个方法则访问父类中的方法。
super访问父类中的方法
构造的区别:
this调用本类构造构造,必须放在构造方法的首行。
super调用父类构造,必须放在子类构造方法首行。
其他区别:this表示当前对象。super不能表示当前对象
this. 属性和super.属性
this.属性:调用的当前对象的属性,如果没有就向上一级类查找
super.属性:直接调用的是父类中的属性。
this(参数)和super(参数)方法
this(参数):调用(转发)的是当前类中的构造器;
super(参数):用于确认要使用父类中的哪一个构造器
使用
this在实例方法中可以使用,在static方法中不能使用,每个类都有this
super在实例方法中可以使用,在static方法中不能使用,只能出现在子类中
多态
多态一词的通常含义是指能够呈现出多种不同的形式或形态。而在程序设计术语中,
它意味着一个特定类型的变量,可以引用不同类型的对象,并且能自动地调用引用的
对象的方法。
也就是根据用到的不同对象类型,响应不同的操作。方法重写是实现多态的基础。
public class Pet {
public void toHospital() {
System.out.println("宠物去医院");
}
}
class Dog extends Pet {
@Override
public void toHospital() {
System.out.println("狗去医院");
}
}
class Bird extends Pet {
@Override
public void toHospital() {
System.out.println("鸟去医院");
}
}
class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.toHospital();
Bird bird = new Bird();
bird.toHospital();
Pet pet;
pet = new Dog();
pet.toHospital();
pet = new Bird();
pet.toHospital();
}
}
多态意味着在一次方法调用中根据包含的对象的实际类型(即实际子类的对象)来决定应该调用哪个子类的方法,而不是由用来存储对象引用的变量的类型决定的。当调用一个方法时,为了实现多态操作,这个方法即是在父类中声明过的,也必须是在子类中重写过的方法。
使用
方法重写是多态的基础;
多态中,变量引用的是哪个对象,就执行的是哪个对象中相对应的方法;
类型转换
向上转型
子类向父类转换称为向上转型。
向上转型的语法格式如下:
父类类型 引用变量名 = new 子类类型();
例如: Pet pet = new Dog();
Pet pet = new Dog(); // 子类转换成父类,也称为父类引用指向子类对象
pet.toHospital(); // pet 对象调用的是Dog类的toHospital() 方法,体现了多态
多态就是说一个父类可能有多个子类,每个子类都重写了父类的方法(每个子类都有不同的方法实现),当父类引用调用方法时,父类引用指向哪个子类,就调用哪个子类的方法,形成了父类引用调用相同的方法名称时,有不同的输出形态。
子类继承父类,并且子类重写父类的方法。
父类引用指向子类对象(父类引用可以指向任意一个子类的对象)。
父类引用调用方法时,实际上调用的是子类的方法(父类指向哪个子类就调用哪个子类的方法),不同的子类有不同的方法实现,体现出同一个方法在不同子类中的不同形态的表现。
此外,调用的时候应当注意:
父类只能调用子类从父类继承的方法或重写的方法。
父类不能调用子类新增的方法
向下转型
前面已经提到,当向上转型发生后,将无法调用子类新增的方法。但是如果需要调用子类新增的方法,可以通过把父类转换为子类实现。
将一个指向子类对象的父类引用赋值给一个子类的引用,即将父类类型转换为子类类型,称为向下转型,此时必须进行强制类型转换。
向下转型的语法规则如下。
子类类型 引用变量名 = (子类类型) 父类类型的引用变量;
例如: Dog dog = (Dog)pet;
instanceof 运算符
在向下转型过程中,如果不是转换为真实的子类类型,会出现类型转换异常。所以Java中提供了instanceof运算符来进行类型转换的判断。
-
使用instanceof时,对象的类型必须和instanceof后面的参数所指定的类有继承关系,否则会出现编译错误。
-
在使用instanceof时,先判断获取的对象是否是某一类型,再做强制类型转换,然后再对对象进行操作。这是传统的写法,在Java 16的增强之后,对于instanceof的判断以及类型转换可以合二为一了。
-
此外,在Java中,可以使用getClass()方法来进行类型转换的判断。getClass()方法是Object类的一个方法,所有Java类都继承自Object类,因此它可以在任何对象上调用。
getClass()返回的是运行时类的Class对象,所以比较时要使用==运算符,而不是使用equals()方法。另外,使用getClass()方法进行类型转换的判断通常用于处理特定类型的对象,而不是用于处理继承层次结构的类型判断。在处理继承层次结构时,通常会使用instanceof运算符来进行更灵活和安全的类型检查。
多态的应用
可替换性:多态对已存在的代码具有可替换性。
可扩充性:多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承
性,以及其他特性的运行和操作。实际上新加子类更容易获得多态。
接口性:多态是父类向子类提供了一个共同接口,由子类来具体实现。
灵活性:多态在应用中体现了灵活多样的操作,提高了使用效率。
简化性:多态简化了应用软件的代码编写和修改过程,尤其在处理大量对象的运算和
操作时,这个特点尤为突出和重要。
在多态的程序设计中,一般有以下两种主要的应用形式
- 使用父类作为方法的参数
public void goHospital(Pet pet) {
pet.toHospital();
}
- 使用父类作为方法的返回值
public Pet donateAnimal(String type) {
Pet pet = null;
if ("dog".equals(type)) {
pet = new Dog();
} else if ("bird".equals(type)) {
pet = new Bird();
} else {
pet = new Pet();
}
return pet;
}