函数式接口+default接口+springAi 中的ducumentReader去理解为什么存在default接口的形式

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 方法让接口可以安全地演化,不会破坏现有代码。

相关推荐
果汁华4 小时前
java学习连续打卡30天(1)
java
武子康4 小时前
Java-171 Neo4j 备份与恢复 + 预热与执行计划实战
java·开发语言·数据库·性能优化·系统架构·nosql·neo4j
m0_639817155 小时前
基于springboot火锅店管理系统【带源码和文档】
java·spring boot·后端
会编程的林俊杰5 小时前
SpringBoot项目启动时的依赖处理
java·spring boot·后端
一叶飘零_sweeeet6 小时前
深度拆解汽车制造系统设计:用 Java + 设计模式打造高扩展性品牌 - 车型动态生成架构
java·设计模式·工厂设计模式
王家羽翼-王羽6 小时前
nacos 3.1.0 运行主类报错 com.alibaba.cloud.nacos.logging.NacosLoggingAppRunListener
java
影子24017 小时前
oralce创建种子表,使用存储过程生成最大值sql,考虑并发,不考虑并发的脚本,plsql调试存储过程,java调用存储过程示例代码
java·数据库·sql
武子康7 小时前
Java-172 Neo4j 访问方式实战:嵌入式 vs 服务器(含 Java 示例与踩坑)
java·服务器·数据库·sql·spring·nosql·neo4j