接口与抽象类

抽象类 —— 半成品类

抽象类就像毛坯房:框架搭好了,但有些房间没装修,留给你来完成

abstract 关键字修饰,里面可以有”只声明不实现”的抽象方法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 抽象类:不能直接 new,必须有子类来实现
public abstract class Shape {
String color; // 普通字段,有具体值

// 普通方法:有方法体,子类可以直接用
public void printColor() {
System.out.println("颜色是:" + color);
}

// 抽象方法:只有声明,没有方法体(没有大括号)
// 意思是"我知道形状一定有面积,但不同形状算法不一样,你自己实现去"
public abstract double area();
}

接口 —— 纯粹的契约

接口就像行业标准/规范:USB接口规定了形状和电压,但不管你内部怎么实现

interface 关键字定义,表示”你要具备这些能力”

1
2
3
4
5
6
7
8
9
10
11
12
13
// 接口:定义一组能力规范
public interface Flyable {
void fly(); // 接口方法默认是 public abstract,不用手动写

// Java 8 新增:默认方法,有方法体
default void land() {
System.out.println("降落中...");
}
}

public interface Swimmable {
void swim();
}

代码示例:抽象类和接口配合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 抽象类:提供基础骨架
public abstract class Animal {
protected String name;

public Animal(String name) {
this.name = name;
}

// 所有动物都会呼吸,直接实现
public void breathe() {
System.out.println(name + "在呼吸");
}

// 叫声不一样,留给子类实现
public abstract void makeSound();
}

// 接口:定义额外能力
public interface Flyable {
void fly();
default void land() {
System.out.println("安全降落");
}
}

public interface Swimmable {
void swim();
}

// 鸭子:继承 Animal + 实现两个接口(能飞能游泳)
public class Duck extends Animal implements Flyable, Swimmable {

public Duck(String name) {
super(name); // 调用父类构造器
}

@Override
public void makeSound() {
System.out.println(name + ":嘎嘎嘎");
}

@Override
public void fly() {
System.out.println(name + "扑棱着翅膀飞了一小段");
}

@Override
public void swim() {
System.out.println(name + "在水里游得很自在");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testDuck() {
Duck duck = new Duck("唐老鸭");
duck.breathe(); // 继承来的:唐老鸭在呼吸
duck.makeSound(); // 自己实现的:唐老鸭:嘎嘎嘎
duck.fly(); // 实现接口:唐老鸭扑棱着翅膀飞了一小段
duck.swim(); // 实现接口:唐老鸭在水里游得很自在
duck.land(); // 接口默认方法:安全降落

// 多态:用接口类型接收
Flyable f = duck;
f.fly(); // 只能调 Flyable 里定义的方法
}

Java 8 接口新特性

Java 8 之前接口只能有抽象方法,Java 8 开始接口变强了,加了两种新方法:

default 方法:有方法体,实现类可以直接用,也可以重写

为什么需要?假设一个接口已经有100个实现类了,你想给接口加个新方法,如果是抽象方法,100个类全得改。用 default 方法就不用,向下兼容

1
2
3
4
5
6
public interface Collection {
// Java 8 新增的 default 方法
default void forEach(Consumer action) {
// 有默认实现,所有 Collection 的实现类不用改代码就能用
}
}

static 方法:接口自己的工具方法,通过 接口名.方法名() 调用

1
2
3
4
5
6
7
8
public interface StringUtils {
static boolean isEmpty(String s) {
return s == null || s.length() == 0;
}
}

// 使用
boolean result = StringUtils.isEmpty(""); // true

注意:default 方法可能导致”菱形继承”冲突

1
2
3
4
5
6
7
8
9
10
interface A { default void hello() { System.out.println("A"); } }
interface B { default void hello() { System.out.println("B"); } }

// 同时实现 A 和 B,hello() 冲突了,必须手动选一个
class C implements A, B {
@Override
public void hello() {
A.super.hello(); // 手动选择调用 A 的版本
}
}

抽象类 vs 接口 对比表(必背)

特性 抽象类(abstract class) 接口(interface)
关键字 abstract class interface
能否有构造器 ✅ 可以 ❌ 不行
能否有普通字段 ✅ 可以 ❌ 只能有 public static final 常量
能否有普通方法 ✅ 可以 ✅ Java 8 起有 default 方法
能否有抽象方法 ✅ 可以 ✅ 默认就是抽象的
多继承 ❌ 单继承(只能 extends 一个) ✅ 多实现(可以 implements 多个)
成员修饰符 四种都行(参见 访问修饰符 方法默认 public,字段默认 public static final
设计意图 “是什么”(is-a 关系) “能做什么”(has-a 能力)

一句话区分

抽象类是模板——“你是我这类东西,按我的骨架来填”

接口是能力标签——“你能干这个事,具体怎么干你自己定”

什么时候用抽象类,什么时候用接口

用抽象类的场景

子类之间有大量相同的代码逻辑需要复用

需要定义非 public 的成员(protected / private 字段或方法)

需要构造器来初始化状态

例子:AbstractListArrayListLinkedList 的抽象父类,提供了很多公共实现

用接口的场景

定义一种跨继承体系的能力(不相关的类都需要某个能力)

需要多实现(一个类要具备多种能力)

定义 API 契约,让不同团队独立开发

例子:Comparable(排序能力)、Serializable(序列化能力)、Runnable(可运行能力)

实际开发原则能用接口就用接口,不够再考虑抽象类

实际场景举例

JDBC 接口 —— 接口最经典的应用

Java 定义了 ConnectionStatementResultSet接口

MySQL 驱动实现这些接口 → MySQL 版本的 Connection

Oracle 驱动实现这些接口 → Oracle 版本的 Connection

你的代码面向接口编程,换数据库只换驱动包,代码不用改

1
2
3
4
5
// 你写的代码:只依赖接口
Connection conn = DriverManager.getConnection(url);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
// 不管底层是 MySQL 还是 Oracle,代码一模一样

Comparable 接口 —— 让对象能排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Student implements Comparable<Student> {
String name;
int score;

public Student(String name, int score) {
this.name = name;
this.score = score;
}

@Override
public int compareTo(Student other) {
return this.score - other.score; // 按分数升序
}
}

@Test
public void testComparable() {
List<Student> list = new ArrayList<>();
list.add(new Student("张三", 85));
list.add(new Student("李四", 92));
list.add(new Student("王五", 78));

Collections.sort(list); // 因为实现了 Comparable,可以直接排序
for (Student s : list) {
System.out.println(s.name + ": " + s.score);
}
// 输出:王五:78, 张三:85, 李四:92
}

常见坑

坑1:抽象类不能直接 new

1
2
// Animal animal = new Animal(); // ❌ 编译报错!抽象类不能实例化
Animal animal = new Dog("旺财"); // ✅ 用子类来实例化

坑2:子类必须实现所有抽象方法,除非子类自己也是抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
abstract class Shape {
abstract double area();
abstract double perimeter();
}

// ❌ 编译报错:只实现了一个抽象方法
// class Circle extends Shape {
// double area() { return 3.14 * r * r; }
// // 漏了 perimeter()
// }

// ✅ 如果暂时不想全部实现,自己也声明为 abstract
abstract class PartialShape extends Shape {
double area() { return 0; }
// perimeter() 留给下一层子类
}

坑3:接口字段的陷阱

接口里写 int MAX = 100; 其实是 public static final int MAX = 100;

这是常量!赋值之后不能改

坑4:abstract 和 final 不能同时用

abstract 说”必须被重写”,final 说”不许被重写”,互相矛盾

同理 abstractprivate 也冲突(private 方法子类看不到,怎么重写?)

练习题

题1:下面的代码能编译吗?

1
2
3
4
5
6
interface Printable {
void print();
}
abstract class Document implements Printable {
// 没有实现 print() 方法
}

答案:可以。因为 Document 自己是 abstract 的,不需要实现所有接口方法,留给子类

题2:一个类能同时继承一个抽象类并实现多个接口吗?

答案:可以。class Duck extends Animal implements Flyable, Swimmable

Java 不支持多继承类,但支持多实现接口,这就是接口的核心价值

题3:Java 8 之后接口能有方法体了,那抽象类还有存在的必要吗?

答案:有。抽象类能有构造器、能有普通字段(有状态)、成员能用 private/protected。接口虽然有了 default 方法但仍然不能有实例字段和构造器


上一章 目录 下一章
访问修饰符 java基础 内部类与匿名类