Java 接口学习笔记
函数式接口
简单来说,函数式接口就是只有一个抽象方法的接口。Java 8 引入这个概念主要是为了支持 Lambda 表达式。
java
@FunctionalInterface
public interface MyFunction {
void doSomething(); // 只有一个抽象方法
}
以前我们要这样写:
java
MyFunction func1 = new MyFunction() {
@Override
public void doSomething() {
System.out.println("传统方式");
}
};
现在可以直接用 Lambda:
java
MyFunction func2 = () -> System.out.println("Lambda 方式");
常用的几个函数式接口
Java 提供了几个常用的:
Supplier<T>-T get()- 提供数据,无参数有返回值Consumer<T>-void accept(T t)- 消费数据,有参数无返回值Function<T,R>-R apply(T t)- 转换数据Predicate<T>-boolean test(T t)- 判断/过滤
Supplier 接口
这个接口很简单:
java
@FunctionalInterface
public interface Supplier<T> {
T get();
}
就是个数据提供者,不需要输入参数,返回一个值。
常见用法:
java
// 延迟加载
Supplier<List<String>> lazyData = () -> {
System.out.println("现在才开始加载数据");
return Arrays.asList("A", "B", "C");
};
List<String> data = lazyData.get(); // 调用时才执行
// 在 Optional 中使用
Optional<String> opt = Optional.empty();
String result = opt.orElseGet(() -> "默认值");
// Stream 中使用
Stream.generate(() -> Math.random()).limit(5).forEach(System.out::println);
default 方法
Java 8 还有个重要特性,就是接口里可以有具体实现的方法了,用 default 关键字标记。
java
public interface MyInterface {
void mustImplement(); // 抽象方法
default void optional() { // 有实现的方法
System.out.println("有默认实现");
}
}
为什么需要这个?
主要是为了向后兼容。想象一个场景:
java
// 已经有 1000 个类实现了这个接口
public interface Animal {
void eat();
}
// 如果直接加新方法,所有类都编译不过了
public interface Animal {
void eat();
void sleep(); // 这会导致所有实现类报错
}
// 用 default 就没问题
public interface Animal {
void eat();
default void sleep() {
System.out.println("睡觉中...");
}
}
常见用法
1. 组合已有的抽象方法
这是最常见的用法,default 方法调用接口中的其他方法:
java
public interface Logger {
void log(String level, String msg);
default void info(String msg) {
log("INFO", msg);
}
default void error(String msg) {
log("ERROR", msg);
}
}
2. 提供可选功能的默认实现
java
public interface Payment {
boolean pay(double amount);
default boolean refund(double amount) {
System.out.println("不支持退款");
return false;
}
}
3. 提供工具方法
java
public interface Validator<T> {
boolean isValid(T value);
default void validateOrThrow(T value) {
if (!isValid(value)) {
throw new IllegalArgumentException("验证失败");
}
}
}
实际案例:DocumentReader
这是 Spring AI 里的一个接口设计,挺有意思的:
java
public interface DocumentReader extends Supplier<List<Document>> {
default List<Document> read() {
return get();
}
}
看起来很简单,但设计得很巧妙。
为什么这样设计?
1. 语义更清晰
java
DocumentReader reader = new FileDocumentReader();
List<Document> docs = reader.read(); // read() 一看就知道是读取文档
2. 可以当 Supplier 用
因为继承了 Supplier,所以可以用在函数式编程的地方:
java
Supplier<List<Document>> supplier = reader;
List<Document> docs = supplier.get();
// 可以传给需要 Supplier 的方法
Optional.empty().orElseGet(reader);
Stream.generate(reader).limit(3);
3. 实现类很简单
实现类只需要写一个 get() 方法就行了,read() 方法自动就有了:
java
class FileDocumentReader implements DocumentReader {
@Override
public List<Document> get() {
return loadDocuments(); // 只需要实现这个
}
// read() 自动继承
}
完整代码示例
java
class Document {
String id;
String content;
public Document(String id, String content) {
this.id = id;
this.content = content;
}
}
// 接口
public interface DocumentReader extends Supplier<List<Document>> {
default List<Document> read() {
return get();
}
}
// 从文件读取
class FileDocumentReader implements DocumentReader {
private String path;
public FileDocumentReader(String path) {
this.path = path;
}
@Override
public List<Document> get() {
System.out.println("从文件读取: " + path);
return Arrays.asList(
new Document("1", "文档内容1"),
new Document("2", "文档内容2")
);
}
}
// 从数据库读取
class DbDocumentReader implements DocumentReader {
@Override
public List<Document> get() {
System.out.println("从数据库读取");
return Arrays.asList(new Document("db1", "数据库文档"));
}
}
// 使用
public class Demo {
public static void main(String[] args) {
DocumentReader reader = new FileDocumentReader("data.txt");
// 方式1:业务 API
List<Document> docs1 = reader.read();
// 方式2:函数式 API
List<Document> docs2 = reader.get();
// 方式3:作为 Supplier 传递
processDocuments(reader);
}
static void processDocuments(Supplier<List<Document>> supplier) {
List<Document> docs = supplier.get();
System.out.println("处理 " + docs.size() + " 个文档");
}
}
总结
函数式接口
- 只有一个抽象方法的接口
- 用来支持 Lambda 表达式
- 常见的有 Supplier、Consumer、Function、Predicate
default 方法
- 接口中可以有具体实现的方法
- 主要是为了向后兼容,不破坏已有代码
- 常用来组合其他抽象方法,提供便利的 API
DocumentReader 的设计
这个设计把两者结合起来:
- 继承 Supplier 获得函数式能力
- 用 default 方法提供更好的业务语义
- 实现类只需要实现一个 get() 方法
- 既可以
reader.read()也可以reader.get()
总的来说,这两个特性让 Java 的接口设计变得更灵活了。函数式接口支持 Lambda,让代码更简洁;default 方法让接口可以安全地演化,不会破坏现有代码。