Java 抽象类与接口
当父类只想定义"做什么",不想写"怎么做"时,就用抽象类或接口。它们是多态的"基石",也是 Java 工程化的核心工具——所有 Java 框架的设计都离不开接口。本章彻底讲清两者的区别与选择。
1. 抽象类(abstract class)
用 abstract 修饰的类叫抽象类,特点是:
- 不能直接 new(不能实例化),只能 new 它的具体子类。
- 可以有抽象方法(用
abstract修饰,没有方法体),子类必须实现。 - 可以有普通方法、字段、构造方法(给子类调用)。
- 用
extends继承(单继承)。
// 抽象类:用 abstract 修饰,不能 new(不能直接实例化)
abstract class Shape {
protected String name;
public Shape(String name) {
this.name = name;
}
// 普通方法:可以有方法体
public String describe() {
return name + " 面积 = " + area();
}
// 抽象方法:只有声明,没有方法体
// 子类必须实现(除非子类也是抽象的)
public abstract double area();
}
// 圆形:必须实现 area()
class Circle extends Shape {
private double r;
public Circle(double r) {
super("圆形");
this.r = r;
}
@Override
public double area() { return Math.PI * r * r; }
}
class Rectangle extends Shape {
private double w, h;
public Rectangle(double w, double h) {
super("矩形");
this.w = w;
this.h = h;
}
@Override
public double area() { return w * h; }
}
public class AbstractDemo {
public static void main(String[] args) {
// Shape s = new Shape("x"); // ❌ 抽象类不能 new
Shape s1 = new Circle(2);
Shape s2 = new Rectangle(3, 4);
System.out.println(s1.describe()); // 圆形 面积 = 12.566...
System.out.println(s2.describe()); // 矩形 面积 = 12.0
}
}抽象类的价值:作为"模板",强制子类实现某些方法(抽象方法),同时提供公共代码(普通方法/字段),避免重复。Shape 定义 area() 抽象方法,所有子类必须实现——这保证了"任何 Shape 都能算面积"。
2. 接口(interface)
接口是更彻底的抽象——Java 8 之前只能有抽象方法和常量。Java 8+ 放宽了,可以有默认方法和静态方法。
// 接口:用 interface 定义,方法默认 public abstract
interface Flyable {
void fly(); // 抽象方法(隐式 public abstract)
}
interface Swimmer {
void swim();
}
// 一个类可以实现多个接口(实现"多继承"效果)
// 用逗号分隔多个接口
class Duck implements Flyable, Swimmer {
@Override
public void fly() { System.out.println("鸭子会飞"); }
@Override
public void swim() { System.out.println("鸭子会游泳"); }
}
public class InterfaceDemo {
public static void main(String[] args) {
Duck d = new Duck();
// 接口引用也能指向实现类对象(类似向上转型)
Flyable f = d;
f.fly(); // 鸭子会飞
Swimmer s = d;
s.swim(); // 鸭子会游泳
}
}接口的关键特性:
- 一个类可以实现多个接口(
implements A, B, C),实现"多继承"效果。 - 接口中的方法默认 public abstract(写不写都行)。
- 接口中的字段默认 public static final(即常量)。
- 接口没有构造方法,没有实例字段。
- 用
implements实现,而非 extends。
3. Java 8+ 默认方法与静态方法
Java 8 给接口带来两个重要特性,解决了"接口升级"难题:
// Java 8+ 接口可以有"默认方法"和"静态方法"
interface Calculator {
// 抽象方法
int calculate(int a, int b);
// 默认方法:用 default 修饰,有方法体
// 解决"接口升级"问题——给老接口加方法,不用改所有实现类
default int doubleCalc(int a, int b) {
return calculate(a, b) * 2;
}
// 静态方法:属于接口本身,直接 接口名.方法() 调用
static Calculator getAdder() {
return (a, b) -> a + b; // lambda 表达式
}
}
// 实现类只需要实现抽象方法,默认方法自动继承
class Adder implements Calculator {
@Override
public int calculate(int a, int b) { return a + b; }
}
public class DefaultMethodDemo {
public static void main(String[] args) {
Calculator adder = new Adder();
System.out.println(adder.calculate(3, 5)); // 8
System.out.println(adder.doubleCalc(3, 5)); // 16(默认方法)
// 调用接口的静态方法
Calculator lambda = Calculator.getAdder();
System.out.println(lambda.calculate(10, 20)); // 30
}
}默认方法的价值:给老接口加方法,不用改所有实现类。Java 8 的 Collection.stream()、Iterable.forEach() 都是默认方法,这样所有 Collection 的实现类都自动获得了 stream 能力。静态方法用于放工具函数(如 Comparator.comparing())。
4. 函数式接口与 Lambda
函数式接口 = 有且只有一个抽象方法的接口。这种接口可以用 Lambda 表达式(Java 8+) 简洁地创建实例:
// 函数式接口:有且只有一个抽象方法的接口
// 可以用 @FunctionalInterface 让编译器帮你检查
@FunctionalInterface
interface Greeter {
void greet(String name);
// void another(); // ❌ 加这个就不再是函数式接口
// Object 重写的方法不算(toString/equals)
}
public class FunctionalDemo {
public static void main(String[] args) {
// 1. 传统匿名内部类(啰嗦)
Greeter g1 = new Greeter() {
@Override
public void greet(String name) {
System.out.println("Hi, " + name);
}
};
// 2. Lambda 表达式(Java 8+,简洁)
// 编译器看到 Greeter 只有一个抽象方法,自动匹配
Greeter g2 = (name) -> System.out.println("Hello, " + name);
Greeter g3 = name -> System.out.println("你好, " + name); // 单参数可省括号
g1.greet("小明"); // Hi, 小明
g2.greet("小明"); // Hello, 小明
g3.greet("小明"); // 你好, 小明
// Java 内置的函数式接口(在 java.util.function 包)
// Runnable、Comparator、Predicate、Function、Consumer、Supplier...
Runnable r = () -> System.out.println("running");
new Thread(r).start();
}
}函数式接口是 Java 函数式编程的基础。Java 内置了一批常用函数式接口(在 java.util.function 包),不必自己写:
Runnable:无参无返回。Predicate<T>:输入 T,返回 boolean(filter 用)。Function<T, R>:输入 T,返回 R(map 用)。Consumer<T>:输入 T,无返回(forEach 用)。Supplier<T>:无输入,返回 T(工厂用)。
5. 抽象类 vs 接口:怎么选?
这是新手最常问的问题。看下表对比:
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 继承数 | 单继承(一个 extends) | 多实现(多个 implements) |
| 字段 | 任意类型 | 只能 public static final 常量 |
| 方法 | 任意(抽象+普通) | 抽象+默认+静态(Java 8+) |
| 构造方法 | 有(给子类调) | 没有 |
| 访问修饰符 | 任意 | 默认 public |
选择原则:
- is-a 强关系用抽象类:如
Shape和Circle(圆是一种形状),共享代码 + 共享字段。 - can-do 弱能力用接口:如
Flyable、Comparable(可以飞/可以比较),不强调类型关系。 - 需要多继承用接口:Java 单继承,要"多继承"必须用接口。
- 接口是 Java 工程化的核心:Spring 的 Bean、DAO、Service 几乎都是接口先行。
6. 实战:组合使用
// 抽象类:适合"模板"——共享代码 + 共享字段
abstract class Vehicle {
protected String name;
public Vehicle(String name) { this.name = name; }
// 公共逻辑,所有子类复用
public void startEngine() {
System.out.println(name + " 启动引擎");
}
public abstract int getWheelCount(); // 子类各自实现
}
// 接口:适合"能力"——可以做什么
interface Electric {
void charge();
}
// 一个类:继承一个抽象类 + 实现多个接口
class ElectricCar extends Vehicle implements Electric {
public ElectricCar() { super("电车"); }
@Override public int getWheelCount() { return 4; }
@Override public void charge() { System.out.println("充电中..."); }
}实际工程中,抽象类和接口经常配合使用:抽象类提供基础实现,接口定义额外能力。Java 类库中 AbstractList(抽象类)+ List(接口)+ ArrayList(具体类)就是这种模式。
小结
这一章你掌握了 Java 抽象类与接口:abstract class 强制实现 + 共享代码,interface 定义能力 + 多实现,Java 8+ 默认方法解决接口升级,函数式接口拥抱 Lambda。下一篇我们看 Java 工程师的看家本领——集合框架。
← 上一篇 Java 多态
下一篇 Java 集合框架 →