第16章 抽象类与接口

第十六章 抽象类与接口

16.1 抽象类

什么抽象类?先别被名字吓到

抽象类(Abstract Class)是什么?说白了,它就是一个"不完整的类"。就像你设计一辆汽车的图纸,图纸上可能只画了"这里放发动机"、“这里放方向盘”,但具体发动机长什么样、方向盘什么形状,图纸上并没有画出来——因为那不是你的职责,你只管设计汽车的"架子"。

在 Java 中,用 abstract 关键字来声明一个抽象类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 抽象类:不能直接实例化,只能被继承
public abstract class Animal {
    // 抽象方法:没有方法体,子类必须实现
    public abstract void makeSound();

    // 普通方法:抽象类也可以有具体实现的方法
    public void sleep() {
        System.out.println("动物在睡觉...");
    }

    // 成员变量
    protected String name;

    // 构造方法:抽象类可以有构造方法
    public Animal(String name) {
        this.name = name;
    }
}

抽象类最大的特点就是:不能直接 new 出来。你不能这样写:

1
Animal animal = new Animal(); // 编译错误!抽象类不能实例化

但是你可以继承它:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

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

然后这样用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog("小白");
        dog.makeSound();  // 输出:小白说:汪汪汪!
        dog.sleep();      // 输出:动物在睡觉...

        // 向上转型:父类引用指向子类对象(多态的经典操作)
        Animal animal = new Dog("小黑");
        animal.makeSound();  // 输出:小黑说:汪汪汪!
    }
}

抽象类的"宪法规定"

抽象类可不是想怎么写就怎么写的,它有几条铁律:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public abstract class Base {
    // ✓ 抽象方法:没有方法体,以分号结尾
    public abstract void doSomething();

    // ✓ 普通方法:有具体实现
    public void normalMethod() {
        System.out.println("这是普通方法");
    }

    // ✓ 构造方法:可以有
    public Base() {
        // 初始化逻辑
    }

    // ✓ 成员变量:可以有各种修饰符
    protected String field;

    // ✗ 错误!抽象类不能被 final 修饰(否则它就没法被继承了)
    // public abstract final class Invalid {}
}

抽象类的应用场景

什么时候该用抽象类?想象一下这些场景:

 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
52
53
54
55
56
57
58
59
60
61
62
// 场景:开发一个游戏,需要不同的角色
public abstract class GameCharacter {
    protected String name;
    protected int hp;
    protected int attack;

    public GameCharacter(String name, int hp, int attack) {
        this.name = name;
        this.hp = hp;
        this.attack = attack;
    }

    // 所有角色都有攻击行为,但具体怎么攻击各不相同
    public abstract void attack();

    // 所有角色都能移动,这是共同的
    public void move() {
        System.out.println(name + "正在移动...");
    }

    // 获取角色信息(通用逻辑)
    public void showInfo() {
        System.out.println("名称:" + name + ",生命值:" + hp + ",攻击力:" + attack);
    }
}

public class Warrior extends GameCharacter {
    public Warrior(String name) {
        super(name, 100, 30);
    }

    @Override
    public void attack() {
        System.out.println(name + "发动猛击!造成" + attack + "点伤害!");
    }
}

public class Mage extends GameCharacter {
    public Mage(String name) {
        super(name, 60, 50);
    }

    @Override
    public void attack() {
        System.out.println(name + "释放魔法!造成" + attack + "点伤害!");
    }
}

public class Main {
    public static void main(String[] args) {
        GameCharacter warrior = new Warrior("亚瑟");
        GameCharacter mage = new Mage("甘道夫");

        warrior.showInfo();
        warrior.attack();
        warrior.move();

        mage.showInfo();
        mage.attack();
        mage.move();
    }
}

输出:

名称:亚瑟,生命值:100,攻击力:30
亚瑟发动猛击!造成30点伤害!
亚瑟正在移动...
名称:甘道夫,生命值:60,攻击力:50
甘道夫释放魔法!造成50点伤害!
甘道夫正在移动...

16.2 接口

接口是什么?

如果说抽象类是一份"不完整的图纸",那接口(Interface)就是一份"行为规范说明书"。

接口只告诉你能做什么,不告诉你怎么做。就像一份美食指南告诉你"这家店提供川菜、粤菜",但不会告诉你厨师是怎么炒菜的。

在 Java 8 之前,接口里只能是抽象方法public abstract,可以省略);Java 8 开始接口可以有默认方法静态方法;Java 9 开始接口还可以有私有方法

接口的基本用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 定义一个接口
public interface Flyable {
    // 常量(默认是 public static final,可以省略)
    int MAX_SPEED = 1000;

    // 抽象方法(默认是 public abstract,可以省略)
    void fly();

    // Java 8+:默认方法
    default void land() {
        System.out.println("安全着陆");
    }

    // Java 8+:静态方法
    static void checkStatus() {
        System.out.println("检查飞行状态...");
    }

    // Java 9+:私有方法(用于减少代码重复)
    private void log(String message) {
        System.out.println("[日志] " + message);
    }
}

实现接口用 implements 关键字:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class Eagle implements Flyable {
    private String name;

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

    @Override
    public void fly() {
        System.out.println(name + "展翅高飞,翱翔于天际!");
    }

    // 可以选择性地重写默认方法
    @Override
    public void land() {
        System.out.println(name + "优雅地降落在悬崖上");
    }
}

接口的"超能力"——多继承

这是接口最厉害的地方!在 Java 中,类只能单继承(一个类只能有一个父类),但一个类可以实现多个接口

 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
52
53
// 接口A
public interface Swimmable {
    void swim();
}

// 接口B
public interface Flyable {
    void fly();
}

// 接口C
public interface Runnable {
    void run();
}

// Duck 实现了三个接口!
public class Duck implements Swimmable, Flyable, Runnable {
    private String name;

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

    @Override
    public void swim() {
        System.out.println(name + "在水中优雅地游泳...");
    }

    @Override
    public void fly() {
        System.out.println(name + "扑腾翅膀起飞...");
    }

    @Override
    public void run() {
        System.out.println(name + "在岸上摇摇摆摆地跑...");
    }
}

public class Main {
    public static void main(String[] args) {
        Duck duck = new Duck("唐老鸭");

        // 同一个对象,可以被当作不同的类型来使用
        Swimmable swimmer = duck;
        Flyable flyer = duck;
        Runnable runner = duck;

        swimmer.swim();  // 唐老鸭在水中优雅地游泳...
        flyer.fly();      // 唐老鸭扑腾翅膀起飞...
        runner.run();     // 唐老鸭在岸上摇摇摆摆地跑...
    }
}

接口的"进化史"

版本新特性
Java 7 及以前只有抽象方法,常量
Java 8默认方法、静态方法
Java 9私有方法
Java 21密封接口(正式特性)

密封接口(Sealed Interface)

Java 17 引入了密封接口,可以精确控制谁可以实现接口:

 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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// sealed 修饰符:只允许 Person 的子类实现
public sealed interface Person permits Teacher, Student, Worker {
    String getName();
}

// 非密封类:允许任何类实现
public non-sealed class Worker implements Person {
    private String name;
    private String job;

    public Worker(String name, String job) {
        this.name = name;
        this.job = job;
    }

    @Override
    public String getName() {
        return name;
    }

    public String getJob() {
        return job;
    }
}

// final 类:不能被继承
public final class Teacher implements Person {
    private String name;
    private String subject;

    public Teacher(String name, String subject) {
        this.name = name;
        this.subject = subject;
    }

    @Override
    public String getName() {
        return name;
    }
}

// sealed 类:只能被指定的类继承
public sealed class Student implements Person {
    private String name;
    private int grade;

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

    @Override
    public String getName() {
        return name;
    }
}

// Student 的子类(如果要继续 sealed 链,必须声明 final/sealed/non-sealed)
// 例如:
public final class HighSchoolStudent extends Student {
    private String major;

    public HighSchoolStudent(String name, int grade, String major) {
        super(name, grade);
        this.major = major;
    }
}

16.3 抽象类 vs 接口——怎么选?

这是 Java 面试中最经典的问题之一!让我用一个表格 + 实战帮你彻底搞懂。

先看对比表

特性抽象类接口
关键字abstract classinterface
继承/实现单继承(一个类只能有一个父抽象类)多实现(一个类可以实现多个接口)
构造方法可以有不能有
成员变量可以是各种类型只能是 public static final(常量)
方法类型抽象方法 + 普通方法抽象方法 + 默认方法 + 静态方法(Java 8+)
访问修饰符任意方法默认 public
多继承❌ 不支持✅ 支持

什么时候用抽象类?

当你需要"是一个"(is-a)的关系时,且需要共享代码和状态:

 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
// 场景:动物家族有共同的行为和属性
public abstract class Animal {
    protected String name;
    protected int age;

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

    // 共同的实现
    public void eat() {
        System.out.println(name + "正在吃东西");
    }

    // 子类必须实现的方法
    public abstract void move();
}

// Dog is an Animal
public class Dog extends Animal {
    private String breed;

    public Dog(String name, int age, String breed) {
        super(name, age);
        this.breed = breed;
    }

    @Override
    public void move() {
        System.out.println(name + "用四条腿奔跑");
    }

    // Dog 特有的方法
    public void fetch() {
        System.out.println(name + "去捡球了!");
    }
}

什么时候用接口?

当你需要"能做什么"(can-do)的能力时

 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
// 能力接口
public interface Jumpable {
    void jump();
}

public interface Swimmable {
    void swim();
}

// 可以跳的鱼
public class JumpingFish implements Swimmable, Jumpable {
    private String name;

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

    @Override
    public void swim() {
        System.out.println(name + "在水中游泳");
    }

    @Override
    public void jump() {
        System.out.println(name + "跃出水面!");
    }
}

一句话总结

经典实战:Comparable vs Compartor

 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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import java.util.Arrays;
import java.util.Comparator;

// Student 继承自 Person(is-a 关系),Person 是抽象类
public abstract class Person {
    protected String name;
    protected int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

// Student is a Person(继承关系用抽象类)
public class Student extends Person {
    private int score;

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

    public int getScore() {
        return score;
    }

    @Override
    public String toString() {
        return "Student{name='" + name + "', age=" + age + ", score=" + score + "}";
    }
}

public class Main {
    public static void main(String[] args) {
        Student[] students = {
            new Student("张三", 18, 95),
            new Student("李四", 17, 88),
            new Student("王五", 19, 92)
        };

        // Comparable:让类自己具备比较能力("我怎样才能比较")
        // 这是一个接口,告诉 Student "你自己知道怎么被排序"
        class StudentComparable implements Comparable<StudentComparable> {
            private String name;
            private int score;

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

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

            @Override
            public String toString() {
                return "StudentComparable{name='" + name + "', score=" + score + "}";
            }
        }

        // Comparator:外部定义比较规则("你来告诉我怎么比较")
        // 这是一个接口的实例,代表一种"比较策略"
        Comparator<Student> byAge = (s1, s2) -> s1.getAge() - s2.getAge();
        Comparator<Student> byScore = Comparator.comparingInt(Student::getScore).reversed();

        System.out.println("按年龄排序:");
        Arrays.sort(students, byAge);
        Arrays.stream(students).forEach(System.out::println);

        System.out.println("\n按分数降序排序:");
        Arrays.sort(students, byScore);
        Arrays.stream(students).forEach(System.out::println);
    }
}

抽象类与接口的关系图

classDiagram
    class Animal {
        <<abstract>>
        +String name
        +eat()
        +move()$Abstract$
    }
    class Dog {
        +String breed
        +move()
        +fetch()
    }
    class Cat {
        +boolean indoor
        +move()
        +meow()
    }

    Animal <|-- Dog : 继承
    Animal <|-- Cat : 继承

    class Flyable {
        <<interface>>
        +fly()$Abstract$
        +land()$default$
    }
    class Swimmable {
        <<interface>>
        +swim()$Abstract$
    }
    class Eagle {
        +fly()
        +land()
    }
    class Fish {
        +swim()
    }

    Flyable <|.. Eagle : 实现
    Swimmable <|.. Fish : 实现

    class Duck {
        +swim()
        +fly()
    }

    Flyable <|.. Duck : 实现
    Swimmable <|.. Duck : 实现

    note for Animal "抽象类:is-a 关系\n共性代码 + 抽象方法"
    note for Flyable "接口:can-do 关系\n定义行为规范"

16.4 函数式接口

什么是函数式接口?

函数式接口(Functional Interface)是一个有且仅有一个抽象方法的接口。注意这里的"有且仅有"——但是可以有多个默认方法或静态方法。

@FunctionalInterface 注解标记后,编译器会帮你检查这个接口是否真的符合函数式接口的规范:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@FunctionalInterface
public interface Calculator {
    // 唯一的抽象方法
    int calculate(int a, int b);

    // 默认方法不算抽象方法,所以可以有多个
    default void description() {
        System.out.println("这是一个计算器接口");
    }

    // 静态方法也不算抽象方法
    static void info() {
        System.out.println("函数式接口用于 Lambda 表达式");
    }
}

如果你的接口有两个抽象方法,加了 @FunctionalInterface 注解后会编译报错:

1
2
3
4
5
@FunctionalInterface
public interface InvalidInterface {
    void method1();
    void method2();  // 编译错误!函数式接口只能有一个抽象方法
}

常用的函数式接口

Java 8 在 java.util.function 包里提供了很多常用的函数式接口:

接口抽象方法用途
Supplier<T>T get()生产一个值
Consumer<T>void accept(T t)消费一个值
Function<T,R>R apply(T t)转换值
Predicate<T>boolean test(T t)判断条件
UnaryOperator<T>T apply(T t)一元运算
BinaryOperator<T>T apply(T t, T t)二元运算

函数式接口实战

 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
import java.util.function.*;

public class FunctionalInterfaceDemo {
    public static void main(String[] args) {
        // 1. Supplier<T>:生产者,不接受参数,返回一个值
        Supplier<String> greeting = () -> "你好,Java!";
        System.out.println(greeting.get());  // 输出:你好,Java!

        // 2. Consumer<T>:消费者,接收一个参数,不返回
        Consumer<String> printer = message -> System.out.println("收到消息:" + message);
        printer.accept("Hello World");  // 输出:收到消息:Hello World

        // 3. Function<T,R>:转换器,接收T返回R
        Function<String, Integer> stringLength = s -> s.length();
        System.out.println("'Java'的长度是:" + stringLength.apply("Java"));  // 输出:4

        // 4. Predicate<T>:断言,接收T返回boolean
        Predicate<Integer> isEven = n -> n % 2 == 0;
        System.out.println("10是偶数吗?" + isEven.test(10));  // 输出:true

        // 5. UnaryOperator<T>:一元操作符,接收T返回T
        UnaryOperator<Integer> doubleIt = n -> n * 2;
        System.out.println("5的两倍是:" + doubleIt.apply(5));  // 输出:10

        // 6. BinaryOperator<T>:二元操作符,接收两个T返回T
        BinaryOperator<Integer> add = (a, b) -> a + b;
        System.out.println("3 + 7 = " + add.apply(3, 7));  // 输出:10

        // 7. BiFunction<T,U,R>:接收两个不同参数
        BiFunction<String, Integer, String> repeat = (s, n) -> s.repeat(n);
        System.out.println(repeat.apply("Java!", 3));  // 输出:Java!Java!Java!

        // 8. BiPredicate<T,U>:接收两个参数的断言
        BiPredicate<String, String> startsWith = (s, prefix) -> s.startsWith(prefix);
        System.out.println("Java 开头是 Ja 吗?" + startsWith.test("Java", "Ja"));  // 输出:true

        // 9. 方法引用:更简洁的 Lambda 写法
        Function<String, String> toUpperCase = String::toUpperCase;
        System.out.println(toUpperCase.apply("hello"));  // 输出:HELLO

        // 10. 组合使用
        Function<String, Integer> lengthAndDouble = stringLength.andThen(doubleIt);
        System.out.println("'Hi'长度翻倍:" + lengthAndDouble.apply("Hi"));  // 输出:4
    }
}

自定义函数式接口

 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
@FunctionalInterface
public interface MyFunction<T, R> {
    // 唯一的抽象方法
    R apply(T input);

    // 默认方法可以组合
    default <V> MyFunction<V, R> compose(MyFunction<V, T> before) {
        return v -> apply(before.apply(v));
    }

    default <V> MyFunction<T, V> andThen(MyFunction<R, V> after) {
        return t -> after.apply(apply(t));
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        // 字符串 -> 长度 -> 翻倍
        MyFunction<String, Integer> length = s -> s.length();
        MyFunction<Integer, Integer> doubleIt = n -> n * 2;
        MyFunction<String, Integer> combined = length.andThen(doubleIt);

        System.out.println(combined.apply("Java"));  // "Java"长度4,翻倍=8
    }
}

Lambda 表达式与方法引用

函数式接口的"最佳拍档"是 Lambda 表达式方法引用

 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
@FunctionalInterface
public interface Converter {
    int convert(String s);
}

public class Main {
    public static void main(String[] args) {
        // 方式1:Lambda 表达式
        Converter c1 = s -> Integer.parseInt(s);
        System.out.println(c1.convert("123"));  // 输出:123

        // 方式2:方法引用(更简洁)
        Converter c2 = Integer::parseInt;
        System.out.println(c2.convert("456"));  // 输出:456

        // 静态方法引用:ClassName::staticMethod
        Function<Double, Long> roundDown = Math::floor;
        System.out.println(roundDown.apply(3.7));  // 输出:3.0

        // 实例方法引用:instance::instanceMethod
        String str = "Hello";
        Function<Integer, String> substring = str::substring;
        System.out.println(substring.apply(1));  // 输出:ello

        // 构造方法引用:ClassName::new
        Function<String, StringBuilder> builderCreator = StringBuilder::new;
        StringBuilder sb = builderCreator.apply("你好");
        System.out.println(sb.reverse());  // 输出:好你
    }
}

内置函数式接口速查

 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
import java.util.function.*;

public class CheatSheet {
    public static void main(String[] args) {
        // ===== 生产者 =====
        Supplier<int[]> emptyArray = int[]::new;  // 无参构造
        int[] arr = emptyArray.get();  // 创建空数组

        // ===== 消费者 =====
        Consumer<String> print = System.out::println;
        print.accept("Hello");  // 输出:Hello

        // ===== 断言 =====
        Predicate<String> isEmpty = String::isEmpty;
        Predicate<String> notEmpty = isEmpty.negate();  // 取反
        System.out.println(notEmpty.test("Java"));  // 输出:true

        // ===== 基本类型专用(避免装箱拆箱) =====
        IntFunction<String> repeat = i -> "=".repeat(i);
        System.out.println(repeat.apply(10));  // 输出:==========

        // ===== 类型转换 =====
        ToIntFunction<String> toLength = String::length;
        System.out.println(toLength.applyAsInt("Java"));  // 输出:4
    }
}

本章小结

抽象类

  • abstract 关键字声明,不能直接实例化
  • 可以包含抽象方法(子类必须实现)和普通方法(可复用)
  • 单继承,一个类只能有一个父抽象类
  • 适合"is-a"关系,用于共性抽象 + 个性实现

接口

  • interface 关键字声明,实现用 implements
  • Java 8+ 支持默认方法静态方法,Java 9+ 支持私有方法
  • 多实现,一个类可以实现多个接口
  • 适合"can-do"关系,用于行为规范定义

抽象类 vs 接口的选择

  • 抽象类:有共性代码需要继承,有 protected/public 成员变量,需要构造方法
  • 接口:纯粹的行为能力定义,需要多继承,函数式编程场景

函数式接口

  • 有且仅有一个抽象方法的接口
  • @FunctionalInterface 注解标识
  • 配合 Lambda 表达式方法引用使用
  • java.util.function 包提供了丰富的内置函数式接口

最后修改 March 30, 2026: 新增 Java 教程 (4da1bd7)