抽象类&接口&Object类
9.1 抽象类
9.1.1 基本概念
9.1.1.1 抽象类的定义
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类是用abstract关键字修饰的类,不能被实例化,只能当作父类被继承,并且子类必须实现父类的所有抽象方法。
已经有了普通父类,为什么还需要抽象类呢?普通父类无法强制子类重写方法,而抽象类就像一个规范标准,里面定义的抽象方法能通过编译器强制子类实现关键逻辑,避免 “遗漏核心方法” 的开发错误 —— 这和final关键字 “预防误修改” 的设计思想一致,都是利用编译器校验降低风险。
9.1.1.2 抽象类的特性
- 不能实例化:不能用
new 抽象类名()创建对象,只能被继承 - 可以包含普通成员:可以有普通方法,成员变量,构造方法等
- 包含抽象方法的类必须是抽象类;子类必须重写所有抽象方法,除非子类也是抽象类
- 子类只能继承一个抽象类
9.1.1.3 基础语法
// 定义抽象类
public abstract class Animal {
// 普通成员变量
protected String name;
// 构造方法(供子类调用)
public Animal(String name) {
this.name = name;
}
// 普通方法(可复用的通用逻辑)
public void eatFood() {
System.out.println(name + "在吃通用食物");
}
// 抽象方法(约束子类必须实现)
public abstract void move();
}
// 子类继承抽象类,必须重写抽象方法
public class Dog extends Animal {
// 调用父类构造方法
public Dog(String name) {
super(name);
}
// 重写抽象方法
@Override
public void move() {
System.out.println(name + "在跑");
}
// 可选:重写普通方法,定制化逻辑
@Override
public void eatFood() {
System.out.println(name + "在啃骨头");
}
}
// 测试类
public class TestAnimal {
public static void main(String[] args) {
// 抽象类不能实例化:Animal animal = new Animal("动物"); // 编译报错
Animal dog = new Dog("旺财");
dog.move(); // 输出:旺财在跑
dog.eatFood(); // 输出:旺财在啃骨头
}
}
9.1.1.4 初学者避坑指南
- ❌ 错误 1:试图实例化抽象类 → 抽象类的核心是 “模板”,只能被继承,不能直接创建对象。
- ❌ 错误 2:子类未重写所有抽象方法 → 除非子类也是抽象类,否则编译器会强制报错。
- ❌ 错误 3:抽象方法加
private修饰 → 抽象方法需要被子类重写,private会导致子类无法访问,编译报错(抽象方法只能用public/protected)。 - ✅ 正确:抽象类可以有构造方法 → 用于子类初始化父类成员变量,子类构造方法会默认调用
super()。
9.1.2 进阶拓展
模板方法模式
核心思想:抽象类定义 “核心流程框架”,子类实现流程中的可变步骤,固定步骤由抽象类复用。
// 抽象模板类:定义泡茶/泡咖啡的通用流程
public abstract class DrinkTemplate {
// 模板方法:固定流程(用final防止子类重写)
public final void makeDrink() {
boilWater(); // 固定步骤1:烧水
brew(); // 可变步骤:冲泡(子类实现)
pourInCup(); // 固定步骤2:倒入杯中
addCondiment(); // 可变步骤:加调料(子类实现)
}
// 固定方法:所有饮品通用
private void boilWater() {
System.out.println("烧开水(100℃)");
}
private void pourInCup() {
System.out.println("倒入杯中");
}
// 抽象方法:子类定制
public abstract void brew();
public abstract void addCondiment();
}
// 子类:泡茶
public class Tea extends DrinkTemplate {
@Override
public void brew() {
System.out.println("冲泡茶叶");
}
@Override
public void addCondiment() {
System.out.println("加冰糖");
}
}
// 子类:泡咖啡
public class Coffee extends DrinkTemplate {
@Override
public void brew() {
System.out.println("冲泡咖啡粉");
}
@Override
public void addCondiment() {
System.out.println("加牛奶和糖");
}
}
// 测试
public class TestTemplate {
public static void main(String[] args) {
DrinkTemplate tea = new Tea();
tea.makeDrink(); // 按固定流程泡茶
DrinkTemplate coffee = new Coffee();
coffee.makeDrink(); // 按固定流程泡咖啡
}
}
9.1.3 与普通类对比
|
特性 |
抽象类 |
普通类 |
|
实例化 |
❌ 不能实例化 |
✅ 可以实例化 |
|
抽象方法 |
✅ 可包含抽象方法 |
❌ 不能包含抽象方法 |
|
继承要求 |
子类必须实现所有抽象方法 |
无强制要求 |
|
设计目的 |
作为基类定义通用模板 |
直接表示具体对象,也可以做模板 |
9.2 接口
9.2.1 接口的基本概念
9.2.1.1 定义
接口是一种完全抽象的类型,只包含抽象方法和常量。Java8及之后里面可以包含静态方法和默认方法。接口就像功能证书:比如可飞行证书,任何类只要实现该证书的要求(重写fly()方法),就具备飞行能力;证书不关心你是谁,只关心你是否能飞。
9.2.1.2 接口的特性
所有方法默认public abstract,所有变量默认public static final
一个类可以实现多个接口(弥补 Java 单继承的不足)
不能用new 接口名()创建对象,只能由实现类实例化
接口仅定义规范,无需初始化成员
9.2.1.3 语法规则
接口的定义规则与类基本相同,将class关键字换成interface关键字。接口里面的变量只能是public static final 修饰,因为是fianl修饰,后续不能改变变量值,所以在定义之初就给它赋值。
// 定义接口1:可运行
public interface Runnable {
void run(); // 默认public abstract
}
// 定义接口2:可飞行
public interface Flyable {
void fly(); // 默认public abstract
// 常量(默认public static final)
int MAX_HEIGHT = 10000;
}
// 实现多个接口
public class Bird implements Runnable, Flyable {
@Override
public void run() {
System.out.println("鸟在地面走");
}
@Override
public void fly() {
System.out.println("鸟在" + MAX_HEIGHT + "米以下飞行");
}
}
// 测试类
public class TestInterface {
public static void main(String[] args) {
Bird sparrow = new Bird();
sparrow.run(); // 输出:鸟在地面走
sparrow.fly(); // 输出:鸟在10000米以下飞行
}
}
9.2.1.4 初学者避坑指南
- ❌ 错误 1:接口方法加
private修饰 → 接口方法默认public,子类重写时必须用public(否则权限缩小,编译报错)。 - ❌ 错误 2:拼写错误 → 如
implements Runable(正确是Runnable)、pubic static final(正确是public static final)。 - ❌ 错误 3:试图在接口中写方法体 → Java8 前接口方法不能有方法体,Java8 后需用
default/static修饰。 - ✅ 正确:多实现时,接口方法名重复需重写一次 → 若多个接口有同名方法,实现类只需重写一次即可。
9.2.2 接口的实现
接口不能直接使用,必须有实现类来实现该接口,并且实现该接口中的所有抽象方法。并且一个类可以实现多个接口,弥补了继承关系中单继承的不足。接口的实现使用implements关键字,使用起来和继承差不多
public interface Runable {
void run();
}
public interface Flyable {
void fly();
}
//实现一个接口
public class Dog implements Runable {
@Override
public void run() {
System.out.println("狗在走");
}
}
//实现多个接口
public class Bird implements Runable, Flyable {
@Override
public void run() {
System.out.println("鸟在走");
}
@Override
public void fly() {
System.out.println("鸟在飞");
}
}
9.2.3 接口 vs 抽象类
|
特性 |
接口 |
抽象类 |
|
实例化 |
不可实例化 |
不可实例化 |
|
继承 / 实现 |
类使用 implements,可多实现 |
类使用 extends,单继承 |
|
成员变量 |
只能是 public static final 常量 |
可以是普通变量 |
|
方法实现 |
可以有默认方法和静态方法(Java 8+) |
可以有非抽象方法 |
|
设计目的 |
定义行为规范(“是什么”) |
抽取共性(“是不是”) |
9.2.4 进阶学习
- 默认方法
核心作用:为接口新增方法时,不影响已有实现类(避免所有实现类被迫重写新方法)。
语法:用default修饰,有方法体;
调用:可通过实现类对象调用,子类可重写。
public interface Flyable {
void fly();
// 默认方法:新增功能,不影响已有实现类
default void stopFly() {
System.out.println("默认:慢慢降落");
}
}
// 子类可选重写默认方法
public class Plane implements Flyable {
@Override
public void fly() {
System.out.println("飞机高速飞行");
}
// 重写默认方法
@Override
public void stopFly() {
System.out.println("飞机:打开减速伞降落");
}
}
- 静态方法
核心作用:为接口提供工具方法,只能通过 “接口名。方法名” 调用,不能通过实现类对象调用。
public interface Flyable {
void fly();
// 静态方法:工具方法
static void checkFlySafety() {
System.out.println("检查飞行安全:机翼正常、燃油充足");
}
}
// 测试
public class TestStaticMethod {
public static void main(String[] args) {
Flyable.checkFlySafety(); // 输出:检查飞行安全:机翼正常、燃油充足
// Plane.checkFlySafety(); // 编译报错:静态方法只能通过接口名调用
}
}
- 接口冲突解决
当一个类实现多个接口,且接口有同名默认方法时,必须手动重写该方法解决冲突:
// 接口A
public interface A {
default void show() {
System.out.println("A的默认方法");
}
}
// 接口B
public interface B {
default void show() {
System.out.println("B的默认方法");
}
}
// 实现类必须重写show()
public class C implements A, B {
@Override
public void show() {
A.super.show(); // 选择调用A的默认方法
// 或 B.super.show(); // 选择调用B的默认方法
// 或自定义逻辑:System.out.println("C的自定义方法");
}
}
- 设计模式应用
接口是策略模式的核心:定义策略规范,不同实现类对应不同策略,灵活切换。
// 策略接口:排序规则
public interface Comparator {
int compare(T o1, T o2); // 返回值:0=相等,正数=o1>o2,负数=o1 {
@Override
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}
// 策略2:按姓名字母序
public class NameComparator implements Comparator {
@Override
public int compare(Person p1, Person p2) {
return p1.getName().compareTo(p2.getName());
}
}
// 测试:灵活切换排序策略
public class TestStrategy {
public static void main(String[] args) {
Person p1 = new Person(20, "张三");
Person p2 = new Person(18, "李四");
// 按年龄排序
Comparator ageComparator = new AgeComparator();
System.out.println(ageComparator.compare(p1, p2)); // 输出:2(p1年龄更大)
// 按姓名排序
Comparator nameComparator = new NameComparator();
System.out.println(nameComparator.compare(p1, p2)); // 输出:正数("张三"字母序在"李四"后)
}
}
9.3 Object类
Object类是所有类的父类,如果一个类没有显示继承另外一个类那么他就等价于继承了Object类,所有类都直接或间接的继承了Object类对于Object类的学习主要是掌握核心方法的运用
9.3.1 初学者必备
9.3.1.1 核心方法速览
|
方法 |
核心作用 |
|
toString() |
返回对象的字符串表示(默认:类名 @哈希值,建议重写) |
|
equals(Object obj) |
判断两个对象是否 “相等”(默认:比较内存地址,建议重写) |
|
hashCode() |
返回对象的哈希值(默认:基于内存地址,重写 equals 时必须重写) |
|
getClass() |
返回对象的运行时类(Class 对象),不可重写 |
|
clone() |
创建对象的拷贝(默认浅拷贝,需实现 Cloneable 接口) |
9.3.1.2 简单重写示例(toString+equals)
public class Person {
private int age;
private String name;
// 构造方法
public Person(int age, String name) {
this.age = age;
this.name = name;
}
// 重写toString:自定义对象字符串格式
@Override
public String toString() {
return "Person{age=" + age + ", name='" + name + "'}";
}
// 重写equals:按属性判断相等(而非内存地址)
@Override
public boolean equals(Object obj) {
// 1. 自反性:自己和自己相等
if (this == obj) return true;
// 2. 空值判断:obj为null则不相等
if (obj == null) return false;
// 3. 类型判断:不是Person类则不相等
if (!(obj instanceof Person)) return false;
// 4. 强转并比较属性
Person other = (Person) obj;
return this.age == other.age && this.name.equals(other.name);
}
// getter/setter
public int getAge() { return age; }
public String getName() { return name; }
}
// 测试
public class TestObject {
public static void main(String[] args) {
Person p1 = new Person(18, "张三");
Person p2 = new Person(18, "张三");
// 测试toString
System.out.println(p1); // 输出:Person{age=18, name='张三'}(而非默认的Person@1b6d3586)
// 测试equals
System.out.println(p1 == p2); // false(内存地址不同)
System.out.println(p1.equals(p2)); // true(属性相同)
}
}
9.3.2 进阶拓展
底层原理
hashCode():默认返回对象的内存地址经过哈希算法处理后的整数(不同 JVM 实现略有差异);equals():默认实现是this == obj,即比较两个对象的内存地址;getClass():返回对象的Class实例,代表类的元数据(类名、方法、字段等),是反射的基础。
hashCode & equals 契约(必须遵守)
JDK 规定:重写equals时,必须重写hashCode,否则违反以下契约:
- 若
a.equals(b) = true,则a.hashCode() = b.hashCode(); - 若
a.hashCode() ≠ b.hashCode(),则a.equals(b) = false; - 若
a.hashCode() = b.hashCode(),a.equals(b)不一定为 true(哈希碰撞)。
违反后果:HashMap/HashSet 等集合无法正常工作(相等的对象被存入不同位置)。
public class Person {
// (省略构造方法、toString、equals)
// 重写hashCode:基于equals比较的属性生成
@Override
public int hashCode() {
return java.util.Objects.hash(age, name); // 工具类生成哈希值,避免手动计算
}
}
clone 深浅拷贝
- 浅拷贝:默认实现,仅拷贝对象的基本类型属性,引用类型属性拷贝 “地址”(原对象和拷贝对象共享引用对象);
- 深拷贝:拷贝对象的所有属性(包括引用类型),原对象和拷贝对象完全独立。
// 引用类型:地址
class Address {
private String city;
public Address(String city) { this.city = city; }
// 为深拷贝做准备:实现Cloneable并重写clone
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// 实现Cloneable接口(标记接口,无抽象方法)
public class Person implements Cloneable {
private int age;
private Address address; // 引用类型
public Person(int age, Address address) {
this.age = age;
this.address = address;
}
// 浅拷贝(默认)
/*
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // 仅拷贝address的地址
}
*/
// 深拷贝(重写clone,递归拷贝引用对象)
@Override
protected Object clone() throws CloneNotSupportedException {
Person clone = (Person) super.clone();
clone.address = (Address) address.clone(); // 拷贝引用对象
return clone;
}
}
// 测试
public class TestClone {
public static void main(String[] args) throws CloneNotSupportedException {
Address addr = new Address("北京");
Person p1 = new Person(18, addr);
Person p2 = (Person) p1.clone();
// 深拷贝:修改p2的地址,p1的地址不变
((Address) p2.address).city = "上海";
System.out.println(((Address) p1.address).city); // 浅拷贝输出上海,深拷贝输出北京
}
}
反射应用(getClass () 的进阶用法)
通过getClass()获取Class对象,可动态操作类的成员(创建对象、调用方法、修改字段),是框架(Spring/MyBatis)的核心底层技术:
public class TestReflection {
public static void main(String[] args) throws Exception {
Person p1 = new Person(18, "张三");
Class> clazz = p1.getClass(); // 获取运行时类
// 1. 动态创建对象(无参构造)
// Person p2 = (Person) clazz.newInstance(); // Java9已过时
Person p2 = (Person) clazz.getConstructor().newInstance();
// 2. 动态调用方法
java.lang.reflect.Method setNameMethod = clazz.getMethod("setName", String.class);
setNameMethod.invoke(p2, "李四"); // 调用p2.setName("李四")
// 3. 动态修改字段(即使是private)
java.lang.reflect.Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true); // 突破private访问限制
ageField.set(p2, 20); // 修改p2的age为20
System.out.println(p2); // 输出:Person{age=20, name='李四'}
}
}










