Java基础 - Java函数式编程 - 函数式接口

Introduction

函数式编程是一种编程范式,与其相对的是面向对象编程。

  • OOP:将数据和操作该数据的行为(方法)封装在对象中。程序的设计围绕着对象及其相互作用展开。
  • FP:它将计算视为数学函数的求值过程。程序的设计是通过组合一系列函数来完成的。

不像JavaScript,函数可以被当作参数传入函数中。在 Java 中,函数无法独立于类或对象存在。那么如何让Java代码可以传递函数作为参数呢?答案是函数式接口

回调函数

考虑以下场景:
文件下载是一个耗时的任务,我希望调用文件下载函数(downloadFile)之后立刻返回,继续处理别的数据,当文件下载结束之后,downloadFile函数可以自动调用一个onSuccess函数来对下载的文件作处理。

  1. 如果同步的对下载文件作处理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public File downloadFileSync(String url) {
    // ... 执行耗时的下载逻辑 ...
    File file = new File("...");
    return file;
    }

    // 调用代码
    System.out.println("开始下载...");
    File myFile = downloadFileSync("http://example.com/largefile.zip"); // <--- 程序会在这里卡住10秒钟
    System.out.println("下载完成!文件路径:" + myFile.getPath()); // 10秒后才会执行这句

    在 downloadFileSync 方法完成并返回结果之前,调用它的线程会被阻塞(Block),无法执行任何其他操作。

  2. 我们可以考虑使用两个线程来解决这个问题:一个是主线程,另一个线程负责执行下载以及之后的回调。思路:将函数作为参数传递到downloadFile的函数中。但是Java不支持函数作为参数,有什么解决方法呢?答案是:将函数包装在一个对象里。

1
2
3
4
public interface Callback<T> {
void onSuccess(T result); // 成功时调用,结果类型为 T
void onError(Exception e);
}
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
class FileDownloader {
// 使用线程池来管理异步任务
private final ExecutorService executor = Executors.newSingleThreadExecutor();

public void downloadFile(String url, Callback<File> callback) {
System.out.println("Downloader: 开始下载文件,URL: " + url);

// 提交一个新任务到线程池中执行,实现异步
executor.submit(() -> {
try {
// 模拟网络延迟和下载过程
System.out.println("正在下载中...");
Thread.sleep(2500);

File downloadedFile = File.createTempFile("download_", ".tmp");
System.out.println("文件下载成功");

// 任务成功,执行成功回调
if (callback != null) {
callback.onSuccess(downloadedFile);
}

} catch (InterruptedException | IOException e) {
// 模拟下载过程中发生错误
System.err.println("下载时发生错误");

if (callback != null) {
callback.onError(e);
}
}
});
System.out.println("Downloader: downloadFile 方法已立即返回,主线程不会被阻塞。");
}

// 提供一个关闭线程池的方法,以便在程序退出时释放资源
public void shutdown() {
executor.shutdown();
}
}
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
public class Main {
public static void main(String[] args) {
System.out.println("程序启动,准备调用下载方法。");

FileDownloader downloader = new FileDownloader();
// 创建了一个 Callback<File> 的匿名内部类实例,
// 这里相当于先创建一个class implements Callback<File>,再创建一个对象
Callback<File> myDownloadCallback = new Callback<File>() {
@Override
public void onSuccess(File result) {
System.out.println("回调函数被执行:任务成功!");
System.out.println("文件保存在: " + result.getAbsolutePath());
// 可以在这里做文件重命名、移动或通知用户等操作
}

@Override
public void onError(Exception e) {
System.err.println("回调函数被执行:任务失败!");
e.printStackTrace();
// 可以在这里记录日志、提示用户下载失败等
}
};

downloader.downloadFile(fileUrl, myDownloadCallback);

System.out.println("downloadFile 方法已调用完毕,主线程继续执行其他任务...");
// 主线程可以继续做其他事情,比如响应用户点击等,而不会被下载任务卡住
}
}

函数式接口

Java 8对上述模式中的“接口”进行了一次概念上的升级。如果一个接口只有一个抽象方法,它就被称为函数式接口。一个函数式接口是指有且仅有一个抽象方法的接口。Java 提供了 @FunctionalInterface 注解来在编译时强制校验这一点。一个经典的例子是 java.lang.Runnable:

1
2
3
4
@FunctionalInterface
public interface Runnable {
void run(); // 这是唯一的一个抽象方法
}

创建一个线程的本质可以理解为启动一个线程来执行某个函数,这个函数通过Runnable包装起来,使得这个函数可以被当作参数传递给其他函数。

直接实现 Runnable 接口

1
2
3
4
5
6
7
8
9
10
class MyTask implements Runnable {
@Override
public void run() {
System.out.println("任务正在执行...");
}
}

Runnable task = new MyTask();
Thread thread = new Thread(task);
thread.start(); // 启动线程,执行 run 方法

使用匿名类实现 Runnable

1
2
3
4
5
6
7
8
9
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("任务正在执行...");
}
};

Thread thread = new Thread(task);
thread.start(); // 启动线程,执行 run 方法

匿名类是 Java 中的一种特殊类,用来创建一个没有名字的类并立即实例化它。本质上,以上代码创建了一个匿名类,它实现了 Runnable 接口,并提供了 run() 方法的具体实现

使用 Lambda 表达式

1
2
3
Runnable task = () -> System.out.println("任务正在执行..."); // Runnable是一个函数式接口
Thread thread = new Thread(task);
thread.start(); // 启动线程,执行 run 方法

Lambda函数是前两个实现方法的语法糖。它允许你直接提供那个唯一抽象方法的实现,编译器会自动为你完成“创建对象”这个包装过程。

java.util.function

java.util.function 包提供一套通用、标准化的函数式接口。

1
2
3
4
5
6
7
8
9
10
// 自定义接口
interface StringOperation {
String apply(String s);
}
StringOperation func = (s) -> s.toUpperCase();

public void processString(String input, StringOperation op) {
System.out.println(op.apply(input));
}
processString("hello", func);

有了 java.util.function,你不再需要定义 StringOperation,可以直接使用 Function<String, String>StringOperation这个函数式接口本质上定义了一个“接受一个 String,返回一个 String”的函数载体Function<String, String> 精确地描述了“接受一个 String,返回一个 String”这个函数签名。

Function<String, String>是预先定义好的接口,它就是一个函数式接口,只不过提供了统一的规范:

1
2
3
4
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}

Function<T, R>

  • 接口方法: R apply(T t)
  • 接受一个类型为 T 的参数,经过转换后,返回一个类型为 R 的结果。这是最典型的“输入 -> 输出”模型。
1
2
3
// 将字符串转换为其长度 (Integer)
Function<String, Integer> lengthFunction = s -> s.length();
Integer len = lengthFunction.apply("Hello, World!"); // len is 13

Predicate

  • 接口方法: boolean test(T t)
  • 接受一个类型为 T 的参数,返回一个布尔值。用于判断输入是否满足某个条件。
1
2
3
4
Predicate<Integer> isPositive = i -> i > 0;

boolean result = isPositive.test(10); // true
boolean result2 = isPositive.test(-5); // false

Consumer

  • 接口方法: void accept(T t)
  • 接受一个类型为 T 的参数,执行某个操作,但没有返回值。
1
2
3
// 打印字符串到控制台
Consumer<String> printer = s -> System.out.println(s);
printer.accept("Processing data...");

Supplier

  • 接口方法: T get()
  • 不接受任何参数,但返回一个类型为 T 的结果。
1
2
3
// 生成一个新的 UUID
Supplier<UUID> uuidGenerator = () -> UUID.randomUUID();
UUID newId = uuidGenerator.get();

方法引用

方法引用可以看作是特定场景下 Lambda 表达式的一种语法糖。当一个 Lambda 表达式的全部内容只是在调用一个已经存在的方法时,提供一种更简洁的写法来直接引用那个方法

1
2
3
4
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 使用 Lambda 表达式
names.forEach(s -> System.out.println(s));

这个lambda函数本质上就是接收一个参数 s,然后将这个参数 s 原封不动地传递给了 System.out.println() 方法。

1
2
// 使用方法引用
names.forEach(System.out::println);
  1. 静态方法引用: ClassName::staticMethodName
    str -> Integer.parseInt(str) 可以写成 Integer::parseInt

    1
    2
    3
    4
    5
    List<String> stringNumbers = Arrays.asList("1", "2", "3");
    List<Integer> numbers = stringNumbers.stream()
    .map(Integer::parseInt) // 代替 s -> Integer.parseInt(s)
    .collect(Collectors.toList());
    // numbers is [1, 2, 3]
  2. 指向特定对象的实例方法引用: instance::instanceMethodName
    x -> System.out.println(x) 可以写成 System.out::println

    1
    2
    List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
    names.forEach(System.out::println); // 代替 name -> System.out.println(name)
  3. 指向任意类型实例方法引用: ClassName::instanceMethodName
    语法:(arg0, rest_of_args) -> arg0.instanceMethodName(rest_of_args)
    (s1, s2) -> s1.compareTo(s2) 可以写成 String::compareTo

    1
    2
    3
    4
    5
    6
    List<String> list = Arrays.asList("a", "", "c");
    Predicate<String> isEmptyPredicate = String::isEmpty; // 目标类型是 Predicate<String>
    // 其抽象方法是 boolean test(String s)
    // 映射为 s -> s.isEmpty()

    boolean hasEmptyString = list.stream().anyMatch(isEmptyPredicate); // true
  4. 构造函数引用: ClassName::new
    () -> new ArrayList<>() 可以写成 ArrayList::new

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Person {
    String name;
    public Person(String name) { this.name = name; }
    // ...
    }

    List<String> names = Arrays.asList("Alice", "Bob");
    List<Person> people = names.stream()
    .map(Person::new) // 代替 name -> new Person(name)
    .collect(Collectors.toList());

Java基础 - Java函数式编程 - 函数式接口
https://thiefcat.github.io/2025/07/28/JAVA/java-fp-basic/
Author
小贼猫
Posted on
July 28, 2025
Licensed under