跳到主要内容

Java 函数式接口

什么是函数式接口?

函数式接口(Functional Interface)是Java 8引入的一个重要概念,它是只包含一个抽象方法的接口。这些接口可以使用Lambda表达式来实现,是Java支持函数式编程的基础。

备注

虽然函数式接口只能有一个抽象方法,但它可以包含任意数量的默认方法和静态方法。

在Java中,函数式接口通常使用@FunctionalInterface注解进行标记。这个注解不是必须的,但它可以帮助编译器检查接口是否符合函数式接口的定义。

函数式接口的特点

  • 只包含一个抽象方法(Single Abstract Method,简称SAM)
  • 可以使用Lambda表达式实现
  • 可以包含默认方法和静态方法
  • 通常使用@FunctionalInterface注解标记

自定义函数式接口

让我们创建一个简单的函数式接口:

java
@FunctionalInterface
public interface Calculator {
int calculate(int a, int b);
}

这个接口只有一个抽象方法calculate,它接收两个整数并返回一个整数。现在,我们可以使用Lambda表达式来实现这个接口:

java
public class CalculatorDemo {
public static void main(String[] args) {
// 使用Lambda表达式实现加法
Calculator addition = (a, b) -> a + b;

// 使用Lambda表达式实现减法
Calculator subtraction = (a, b) -> a - b;

// 使用Lambda表达式实现乘法
Calculator multiplication = (a, b) -> a * b;

// 使用Lambda表达式实现除法
Calculator division = (a, b) -> a / b;

// 使用函数式接口
System.out.println("10 + 5 = " + addition.calculate(10, 5));
System.out.println("10 - 5 = " + subtraction.calculate(10, 5));
System.out.println("10 * 5 = " + multiplication.calculate(10, 5));
System.out.println("10 / 5 = " + division.calculate(10, 5));
}
}

输出结果:

10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 5 = 2

Java 内置函数式接口

Java 8在java.util.function包中提供了许多常用的函数式接口。以下是最常用的几个:

1. Consumer<T>

Consumer<T>接口表示一个接受单个输入参数但不返回结果的操作。

java
import java.util.function.Consumer;

public class ConsumerExample {
public static void main(String[] args) {
Consumer<String> printConsumer = message -> System.out.println(message);
printConsumer.accept("Hello, Consumer!");
}
}

输出:

Hello, Consumer!

2. Function<T, R>

Function<T, R>接口表示一个接受一个参数并返回结果的函数。

java
import java.util.function.Function;

public class FunctionExample {
public static void main(String[] args) {
Function<String, Integer> lengthFunction = str -> str.length();

String text = "Hello, Function!";
int length = lengthFunction.apply(text);

System.out.println("Length of '" + text + "' is " + length);
}
}

输出:

Length of 'Hello, Function!' is 16

3. Predicate<T>

Predicate<T>接口表示一个接受一个参数并返回布尔值的函数。

java
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class PredicateExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Jane", "Jack", "Joe");

// 创建一个Predicate,用于检查名字是否以'J'开头
Predicate<String> startsWithJ = name -> name.startsWith("J");

// 过滤并打印名字
System.out.println("Names starting with 'J':");
names.stream()
.filter(startsWithJ)
.forEach(System.out::println);
}
}

输出:

Names starting with 'J':
John
Jane
Jack
Joe

4. Supplier<T>

Supplier<T>接口表示一个不接受任何参数但返回结果的函数。

java
import java.util.function.Supplier;
import java.time.LocalDateTime;

public class SupplierExample {
public static void main(String[] args) {
Supplier<LocalDateTime> currentTime = () -> LocalDateTime.now();

System.out.println("Current time: " + currentTime.get());
}
}

输出:

Current time: 2023-07-14T15:30:45.123

函数式接口与方法引用

方法引用是Lambda表达式的一种简写形式。当Lambda表达式只是调用一个已经存在的方法时,可以使用方法引用。

方法引用有四种类型:

  1. 静态方法引用ClassName::staticMethodName
  2. 实例方法引用instance::methodName
  3. 特定类型的实例方法引用ClassName::methodName
  4. 构造函数引用ClassName::new

例如:

java
import java.util.Arrays;
import java.util.List;

public class MethodReferenceExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Jane", "Jack", "Joe");

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

// 使用方法引用(等价于上面的Lambda表达式)
names.forEach(System.out::println);
}
}

实际应用场景

1. 事件处理

在GUI编程中,函数式接口常用于处理用户事件:

java
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class ButtonClickExample {
public static void main(String[] args) {
JFrame frame = new JFrame("Button Click Example");
JButton button = new JButton("Click Me");

// 传统方式
/*
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked!");
}
});
*/

// 使用Lambda表达式
button.addActionListener(e -> System.out.println("Button clicked!"));

frame.add(button);
frame.setSize(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}

2. 流处理

函数式接口在Java Stream API中得到广泛应用:

java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Jane", "Jack", "Joe", "Jill");

// 使用多个函数式接口进行流处理
List<String> filteredNames = names.stream()
.filter(name -> name.length() > 3) // Predicate
.map(name -> name.toUpperCase()) // Function
.sorted() // Comparator(也是函数式接口)
.collect(Collectors.toList());

System.out.println(filteredNames);
}
}

输出:

[JACK, JANE, JILL, JOHN]

3. 自定义排序

java
import java.util.Arrays;
import java.util.Comparator;

public class CustomSortExample {
public static void main(String[] args) {
String[] names = {"John", "Jane", "Jack", "Joe", "Jill"};

// 使用Lambda表达式定义自定义排序规则(按字符串长度排序)
Arrays.sort(names, (s1, s2) -> s1.length() - s2.length());

System.out.println("Sorted by length: " + Arrays.toString(names));

// 使用Lambda表达式定义自定义排序规则(按字母顺序排序)
Arrays.sort(names, (s1, s2) -> s1.compareTo(s2));

System.out.println("Sorted alphabetically: " + Arrays.toString(names));
}
}

输出:

Sorted by length: [Joe, Jack, Jane, Jill, John]
Sorted alphabetically: [Jack, Jane, Jill, Joe, John]

函数式接口的优势

  1. 代码简洁:使用Lambda表达式可以大大减少模板代码
  2. 提高可读性:代码更加直观,意图更加明确
  3. 便于并行处理:结合Stream API,可以轻松实现并行处理
  4. 延迟执行:可以延迟执行某些操作,提高性能
  5. 便于单元测试:可以轻松模拟函数式接口的实现

注意事项

  1. 函数式接口只能有一个抽象方法,否则编译会出错(特别是使用了@FunctionalInterface注解的情况下)
  2. Lambda表达式中引用的外部变量必须是final或effectively final的
  3. 在复杂的业务逻辑中,过度使用Lambda可能降低代码的可读性
  4. Lambda表达式不能访问默认方法
提示

当你发现自己频繁地编写类似的匿名内部类时,考虑使用函数式接口和Lambda表达式来简化代码。

总结

函数式接口是Java 8引入的重要特性,它为Java语言引入了函数式编程的能力。通过函数式接口,我们可以:

  • 使用Lambda表达式简化代码
  • 将行为像数据一样传递
  • 利用Stream API进行强大的集合操作
  • 编写更具表达力和更简洁的代码

掌握函数式接口是学习Java现代编程的关键一步,它将帮助你编写更加简洁、可维护和高效的代码。

练习题

  1. 创建一个名为StringProcessor的函数式接口,它有一个方法process,接收一个字符串并返回一个处理后的字符串。
  2. 使用Lambda表达式和你创建的接口实现以下功能:
    • 将字符串转换为大写
    • 将字符串转换为小写
    • 移除字符串中的所有空格
  3. 使用Java内置的函数式接口Function<String, String>替换你的自定义接口,实现相同的功能。

进一步学习资源

学习函数式接口是掌握Java Lambda表达式和函数式编程的第一步。通过实践和应用,你会发现函数式编程如何改变你思考和解决问题的方式!