[!quote] 继承 继承 可以使子类具有父类的属性和方法,还可以在子类【
派生类】中 重新定义 / 追加 属性和方法public class 父类 {……} public class 子类 extends 父类 {……}
- 提高代码的复用性
- 提高了代码的维护性【修改一处可变多处】
[!hint] 什么时候使用继承 ? 当 A 和 B 两个类有包含关系时
WARNING
- 在访问子类的构造方法之前,会先访问父类的无参构造方法
- 父类的私有方法,子类是不能重写的
方法重写,多继承
[!hint] 方法重写
java// 父类 public class fu{ public void winbee(){ System.out.println("123"); } }java// 子类 public class zi extends fu { public void winbee(){ //子类重写了winbee()方法 super.winbee(); //调用父类的winbee()方法 System.out.println("456"); } }
- 方法重写里子类方法的访问权限不能比父类低【
public> 默认 >private】
[!hint] 无法多继承,但可以多层继承【子 extends 父,父 extends 爷】
为什么 Java 不允许多继承 ?
因为会出现”致命方块“问题
如果有一个 eat() 方法,从动物类一直继承到了马类,驴类。那当骡子类调用 eat() 方法的时候,那是调用马的还是驴的 ?
关键字
[!quote]
thisthis表示的是当前类javapublic class student { private int age; public void setAge(int a) { this.age = a; // 当前类的 age } }
[!quote]
supersuper表示的是父类javapublic class student extends fu{ private int age; public void setAge(int a) { super.age = a; // 父类的 age } }
拓展和应用
💛 多态
[!quote] 多态 多态 就是一个对象可以表现出不同的形态
javaanimal a = new cat();
- 使用前提
- 有 继承/实现 关系
- 有方法重写
- 有父类引用指向子类对象:
animal a = new cat();
[!hint] 编译器根据引用类型【
animal】来判断有哪些方法可以调用,而不是对象实体【cat】
- 多态中,访问引用的变量访问的是父类的变量,访问引用的方法访问的是子类的方法
// 父类
public class fu {
int age = 1;
public void winbee() {
System.out.println("123");
}
}
// 子类
public class zi extends fu {
int age = 2;
public void winbee() {
System.out.println("456");
}
}
// 测试类
public class demo {
public static void main(String[] args) {
fu fz = new zi();
System.out.println(fz.age); //访问的是父类的age,输出父类的age
fz.winbee(); //访问的是父类的winbee()方法,输出的是子类的winbee()方法
}
}
---
1
456向下转型
[!quote] 向下转型 向下转型 可以访问父类中没有,子类中有的方法
javapublic static void main(String[] args) { animal a = new cat(); cat c = (cat) a; // 向下转型 c.play(); }
作用
[!hint] 让结构更加清晰 我们来写一个俄罗斯方块游戏:
- 首先我们知道,我们需要一个房子,这个房子里有方块,方块接触到地面或者碰到其他方块会停下来
- 然后房子上会出现另一个方块,在下落的过程中你可以翻转,如果好的话,可以减少几行
- 方块有 6 种,它们的形态各异
解决办法 1:
我们可以在一个方块类中写上 6 种方块的翻转方法,但是你可能要在一个方块类中写上几千行代码,这明显不好
解决办法 2:
我们也可以一个方块类中写上一个空的翻转方法,然后在 6 个子类中重写翻转方法,这样结构清晰,子类长条方块也是方块,子类正方形方块也是方块。而且方块类中可能有各种方块的共有参数
[!hint] 类元素数组
javaAnimal animals = new Animal[3]; // 数组里是animal的子类对象 animals[0] = new Dog(); animals[1] = new Cat(); animals[2] = new Wolf(); for(animal a : animals){ a.eat(); a.play(); }
💛 抽象类
[!quote] 抽象类 使用
abstract声明的,没有方法体的方法叫 “抽象方法”,有抽象方法的类叫 “抽象类”javapublic abstract class animal { //抽象类 public abstract void eat(); //抽象方法 }
- 子类继承抽象类时,必须重写该抽象方法
- 抽象类不能实例化,但是可以通过一个非抽象的子类来间接实例化
[!hint] 作用
- 抽象方法的意义在于定义一个接口或协议,之后的子类必须实现这些抽象方法,这个协议可以确保所有的子类都具有某种同类型的特征
- 结构清晰【
当别人看到一个类是抽象类时,就会很关心它的抽象方法。也会知道一定会有子类去重写这个抽象方法。而且会去找引用,一定有多态的体现】:根据多态的俄罗斯方块例子我们知道,方块类里的翻转方法并不重要【因为它一定会被重写】。那竟然是空方法的话,我们就可以把它写成抽象方法,把方块类写成抽象类- 引起重视:把那些像东西一样差不多的类写成抽象类,而像水杯一样的类就会不抽象,前后形成反差,引起重视
- 顺应继承的逻辑:如果不用抽象类,继续用普通类的多态。那么我们就可以实例化出一个方块类对象,但是根据继承的逻辑来说,”方块是方块“,这就很奇怪,所以我们让方块类抽象化(让它不能被实例化)
[!hint] 有了接口,为什么还要抽象类 ?https://www.mianshiya.com/bank/1787463103423897602/question/1780933294427238401#heading-2 抽象类 位于接口和普通类之间,用于定义模板性的方法,让其多个子类可以不用写重复逻辑
- 接口中不能有成员变量,接口中的普通方法也不能有方法体,但是抽象类可以
- 抽象类虽然不能被实例化,但是可以有构造方法
// 抽象类
public abstract class Animal {
protected String name;
public Animal(String name) { this.name = name; }
public abstract void makeSound();
public void eat() {
System.out.println(name + " is eating.");
}
}
// 接口
public interface Swimmable {
void swim();
}
// 子类
public class Dog extends Animal implements Swimmable {
public Dog(String name) { super(name); }
@Override
public void makeSound() {
System.out.println("Woof! Woof!");
}
@Override
public void swim() {
System.out.println(name + " is swimming.");
}
}
---
在这个例子中,Animal 是一个抽象类,它提供了一个构造函数和一个具体的方法 eat(),同时强制子类实现 makeSound() 方法。而 Swimmable 是一个接口,强制实现类实现 swim() 方法。Dog 类既继承了 Animal 抽象类,也实现了 Swimmable 接口。好的,这里有一个具体的例子展示了抽象类可以实现的功能,而接口无法实现。
场景描述
假设我们需要设计一个框架来管理数据库连接。我们希望:
- 有一个
private变量来存储数据库连接 - 提供一个
protected方法供子类访问数据库连接 - 实现一个公共方法来进行连接操作
抽象类的实现
// 抽象类定义
public abstract class Database {
// 私有成员变量
private Connection connection;
// 构造函数
public Database() {
this.connection = createConnection();
}
// 抽象方法,供子类实现
protected abstract Connection createConnection();
// 受保护的方法,供子类使用
protected Connection getConnection() {
return connection;
}
// 公共方法,所有外部类都可以调用
public void connect() {
System.out.println("Connecting to database...");
}
}
// 具体类 MySQLDatabase 继承自 Database
public class MySQLDatabase extends Database {
@Override
protected Connection createConnection() {
System.out.println("Creating MySQL connection...");
return new MySQLConnection();
}
// 子类可以使用 protected 方法
public void customMySQLFunction() {
Connection conn = getConnection();
// 执行 MySQL 特有的一些操作
}
}
// 测试类
public class Main {
public static void main(String[] args) {
MySQLDatabase mySQLDB = new MySQLDatabase();
mySQLDB.connect();
mySQLDB.customMySQLFunction();
}
}为什么接口无法实现相同的功能?
接口在 Java 中有以下限制:
- 接口中的方法默认是
public,无法定义protected方法 - 接口不能包含
private成员变量 - 接口不能有构造函数
如果尝试使用接口
// 尝试使用接口
public interface Database {
// 接口不能有构造函数、private 变量或 protected 方法
Connection createConnection();
void connect();
}
// MySQLDatabase 实现接口
public class MySQLDatabase implements Database {
private Connection connection;
// 构造函数
public MySQLDatabase() {
this.connection = createConnection();
}
@Override
public Connection createConnection() {
System.out.println("Creating MySQL connection...");
return new MySQLConnection();
}
@Override
public void connect() {
System.out.println("Connecting to database...");
}
// 这个方法不能是 protected 的,因为接口中的所有方法默认是 public
public void customMySQLFunction() {
// 无法使用 protected 的 getConnection 方法
// 因为接口中不允许定义这样的受保护方法
}
}在接口版本中,我们无法将 createConnection 和 getConnection 方法设为 protected,也无法包含 private 成员变量。这使得接口在这种情况下不如抽象类灵活和具有封装性。
总结
抽象类可以包含不同访问控制级别的方法和变量(如 private、protected),并且可以提供部分实现和状态管理。而接口主要用于定义行为规范,所有方法默认都是 public 的,不能包含 private 成员变量。因此,抽象类在需要部分实现和封装时比接口更为合适。
如果有一个 eat() 方法,从动物类一直继承到了马类,驴类。那当骡子类调用 eat() 方法的时候,那是调用马的还是驴的 ?