一、简介
Java 8引入了函数式接口(Functional Interface)的概念,它是指只包含一个抽象方法的接口。函数式接口可以使用Lambda表达式来创建该接口的对象。这种接口设计使得Java可以更加轻松地支持函数式编程风格,引入了更简洁和灵活的语法。
函数式接口是只包含一个抽象方法的接口,通常用于 Lambda 表达式或方法引用。 这些接口被广泛用于 Stream API 和其他与函数式编程相关的特性。
Java 8中的函数式接口可以使用@FunctionalInterface
注解进行声明,这样做的好处是编译器会检查该接口是否符合函数式接口的定义(即是否只有一个抽象方法)。
二、示例
2.1定义一个函数式接口
假设我们要定义一个简单的函数式接口 Calculator
,该接口具有一个计算方法 int calculate(int a, int b)
,用于执行某种计算操作。
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
2.2使用Lambda表达式实现接口
现在,我们可以使用Lambda表达式来实现这个函数式接口的方法。
public class FunctionalInterfaceExample {
public static void main(String[] args) {
// Lambda表达式实现加法
Calculator addition = (a, b) -> a + b;
System.out.println("Addition: " + addition.calculate(10, 5)); // 输出 15
// Lambda表达式实现乘法
Calculator multiplication = (a, b) -> a * b;
System.out.println("Multiplication: " + multiplication.calculate(10, 5)); // 输出 50
// Lambda表达式实现除法
Calculator division = (a, b) -> a / b;
System.out.println("Division: " + division.calculate(10, 5)); // 输出 2
}
}
在上面的示例中,我们通过Lambda表达式分别实现了加法、乘法和除法。每个Lambda表达式都实现了 calculate
方法,并根据具体的计算需求编写了不同的逻辑。这些Lambda表达式可以直接赋值给 Calculator
接口类型的变量,而无需显式地编写实现类。
备注— @FunctionalInterface 注解
在定义函数式接口时,通常会使用 @FunctionalInterface
注解。这不是强制性的,但它可以帮助我们确保接口只有一个抽象方法,从而符合函数式接口的定义。如果尝试在带有多个抽象方法的接口上使用 @FunctionalInterface
注解,编译器会报错。
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
// 编译错误:Invalid '@FunctionalInterface' annotation; Calculator is not a functional interface
int subtract(int a, int b);
}
三、内置的函数式接口
JDK 1.8 API 包含了很多内置的函数式接口。其中就包括我们在老版本中经常见到的 Comparator 和 Runnable,Java 8 为他们都添加了 @FunctionalInterface 注解,以用来支持 Lambda 表达式。
值得一提的是,除了 Comparator 和 Runnable 外,还有一些新的函数式接口,它们很多都借鉴于知名的 Google Guava 库。
3.1Function
简介
Function 接口代表一个接受一个参数并且产生结果的操作。它在 Java 中被广泛用于函数式编程以及 Stream API 中的转换操作。
Function
函数式接口的作用是,我们可以为其提供一个原料,他给生产一个最终的产品。通过它提供的默认方法,组合,链行处理(compose
, andThen
):
接口概览
在 java.util.function
包中,Function 接口定义如下:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
T
表示输入参数的类型。R
表示返回结果的类型。apply(T t)
是 Function 接口中唯一的抽象方法,用于对输入参数t
进行操作,并返回结果。
Function 接口是一个泛型接口,它可以接受不同类型的参数和返回不同类型的结果。
示例 1:将字符串转换为大写
创建一个 Function 接口实例,将输入的字符串转换为大写形式。
Function<String, String> toUpperCaseFunc = str -> str.toUpperCase();
String input = "hello, world!";
String result = toUpperCaseFunc.apply(input);
System.out.println(result); // 输出 "HELLO, WORLD!"
在这个例子中,我们创建了一个 Function 对象 toUpperCaseFunc
,它将字符串转换为大写形式。我们调用 apply()
方法来执行函数,将输入的字符串 “hello, world!” 转换为大写形式并输出结果。
示例 2:计算字符串的长度
使用 Function 接口计算字符串的长度。
Function<String, Integer> lengthFunc = str -> str.length();
String input = "Java 1.8";
int length = lengthFunc.apply(input);
System.out.println("Length of '" + input + "' is: " + length); // 输出 "Length of 'Java 1.8' is: 7"
在这个例子中,我们创建了一个 Function 对象 lengthFunc
,它返回输入字符串的长度。我们调用 apply()
方法来执行函数,计算字符串 “Java 1.8” 的长度并输出结果。
示例 3:转换字符串为整数
使用 Function 接口将字符串转换为整数。
Function<String, Integer> parseIntFunc = str -> Integer.parseInt(str);
String input = "12345";
int number = parseIntFunc.apply(input);
System.out.println("Parsed number: " + number); // 输出 "Parsed number: 12345"
在这个例子中,我们创建了一个 Function 对象 parseIntFunc
,它将字符串转换为整数。我们调用 apply()
方法来执行函数,将字符串 “12345” 转换为整数并输出结果。
示例 4:函数的组合
Function 还提供了一些默认方法用于函数的组合:
andThen(Function after)
: 返回一个先执行当前 Function 的 apply 方法,再执行 after Function 的 apply 方法的新 Function。compose(Function before)
: 返回一个先执行 before Function 的 apply 方法,再执行当前 Function 的 apply 方法的新 Function。
下面是一个组合示例:
Function<String, Integer> parseToInt = str -> Integer.parseInt(str);
Function<Integer, Integer> square = num -> num * num;
Function<String, Integer> parseAndSquare = parseToInt.andThen(square);
String input = "5";
int result = parseAndSquare.apply(input);
System.out.println("Parsed and squared result: " + result); // 输出 "Parsed and squared result: 25"
在这个例子中,我们首先将字符串转换为整数,然后对整数进行平方运算,最终输出结果。
3.2BiFunction
BiFunction 接口代表一个接受两个参数并产生结果的操作。它在 Java 中被广泛用于对两个输入参数进行操作,然后返回一个结果。
接口概览
在 java.util.function
包中,BiFunction 接口定义如下:
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
T
表示第一个输入参数的类型。U
表示第二个输入参数的类型。R
表示返回结果的类型。apply(T t, U u)
是 BiFunction 接口中唯一的抽象方法,用于对输入参数t
和u
进行操作,并返回结果。
BiFunction 接口是一个泛型接口,它可以接受不同类型的参数和返回不同类型的结果。
示例 1:计算两个数字的和
创建一个 BiFunction 接口实例,用于计算两个数字的和。
BiFunction<Integer, Integer, Integer> add = (num1, num2) -> num1 + num2;
int result = add.apply(5, 10);
System.out.println("Sum: " + result); // 输出 "Sum: 15"
在这个例子中,我们创建了一个 BiFunction 对象 add
,它将两个整数相加并返回结果。我们调用 apply()
方法来执行 BiFunction,并计算 5 和 10 的和并输出结果。
示例 2:合并两个字符串
使用 BiFunction 接口合并两个字符串。
javaCopy codeBiFunction<String, String, String> mergeStrings = (str1, str2) -> str1 + " " + str2;
String result = mergeStrings.apply("Hello", "World");
System.out.println("Merged String: " + result); // 输出 "Merged String: Hello World"
在这个例子中,我们创建了一个 BiFunction 对象 mergeStrings
,它将两个字符串合并成一个新的字符串,并返回结果。我们调用 apply()
方法来执行 BiFunction,并合并 “Hello” 和 “World” 两个字符串并输出结果。
示例 3:两个数字的乘积
使用 BiFunction 接口计算两个数字的乘积。
BiFunction<Integer, Integer, Integer> multiply = (num1, num2) -> num1 * num2;
int result = multiply.apply(3, 4);
System.out.println("Product: " + result); // 输出 "Product: 12"
在这个例子中,我们创建了一个 BiFunction 对象 multiply
,它将两个整数相乘并返回结果。我们调用 apply()
方法来执行 BiFunction,并计算 3 和 4 的乘积并输出结果。
BiFunction 的组合
BiFunction 还提供了一些默认方法用于函数的组合:
andThen(Function after)
: 返回一个新的 Function,它表示当前 BiFunction 和另一个 Function 的顺序执行。
下面是一个组合示例:
BiFunction<Integer, Integer, Integer> add = (num1, num2) -> num1 + num2;
Function<Integer, Integer> square = num -> num * num;
BiFunction<Integer, Integer, Integer> addAndSquare = add.andThen(square);
int result = addAndSquare.apply(3, 4);
System.out.println("Result: " + result); // 输出 "Result: 49"
在这个例子中,我们首先创建了一个 BiFunction 对象 add
,它将两个整数相加。然后,我们创建了一个 Function 对象 square
,它将整数平方。最后,我们使用 andThen()
方法将这两个函数组合成一个新的 BiFunction addAndSquare
,它先执行相加操作,然后再执行平方操作。
3.3Predicate
简介
Predicate 是一个函数式接口,它代表了一个接受一个参数并返回布尔值的函数。它通常用于对集合中的元素进行筛选、过滤或判断。
接口概览
在 java.util.function
包中,Predicate
接口定义如下:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
可以看到,Predicate
是一个泛型接口,T
表示传入的参数类型。它只有一个抽象方法 test(T t)
,用于对传入的参数进行条件判断,并返回一个布尔值,表示传入的参数是否满足特定条件。
示例1—筛选集合中偶数
使用 Predicate
非常简单。首先,我们需要实现 Predicate
接口的抽象方法 test(T t)
,然后将该实现传递给需要进行条件判断的方法。
假设我们有一个存储整数的集合,我们想筛选出其中的偶数。在 Java 1.8 之前,我们可能会这样写:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = new ArrayList<>();
for (Integer num : numbers) {
if (num % 2 == 0) {
evenNumbers.add(num);
}
}
然而,使用 Predicate
,我们可以这样写:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = numbers.stream()
.filter(num -> num % 2 == 0)
.collect(Collectors.toList());
在上述代码中,我们使用了 filter()
方法来进行筛选。filter()
方法接受一个 Predicate
参数,表示我们希望保留满足条件的元素。在这里,我们使用 Lambda 表达式 num -> num % 2 == 0
来实现 Predicate
接口,并判断整数是否为偶数。
示例2—组合 Predicate
有时候,我们需要对多个条件进行组合,可以使用 Predicate
提供的默认方法来实现。
and(Predicate other)
: 返回一个新的Predicate
,它表示当前Predicate
和另一个Predicate
的逻辑与(AND
)操作。or(Predicate other)
: 返回一个新的Predicate
,它表示当前Predicate
和另一个Predicate
的逻辑或(OR
)操作。negate()
: 返回当前Predicate
的逻辑非(NOT
)操作。
下面是一个使用组合 Predicate
的例子:
Predicate<Integer> isEven = num -> num % 2 == 0;
Predicate<Integer> isGreaterThan5 = num -> num > 5;
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> filteredNumbers = numbers.stream()
.filter(isEven.and(isGreaterThan5))
.collect(Collectors.toList());
在上述例子中,我们定义了两个 Predicate
:isEven
用于判断是否为偶数,isGreaterThan5
用于判断是否大于5。然后,我们使用 filter()
方法进行组合判断,并筛选出同时满足这两个条件的整数。
3.4Supplier 生产者
Supplier 接口代表一个不接受参数但返回结果的操作。它在 Java 中被广泛用于懒加载或产生随机值等场景。
Supplier
与 Function
不同,它不接受入参,直接为我们生产一个指定的结果,有点像生产者模式:
class Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person
接口概览
在 java.util.function
包中,Supplier 接口定义如下:
@FunctionalInterface
public interface Supplier<T> {
T get();
}
T
表示返回结果的类型。get()
是 Supplier 接口中唯一的抽象方法,用于执行操作并返回结果。
Supplier 接口是一个泛型接口,它可以返回不同类型的结果。
示例 1:产生随机数
创建一个 Supplier 接口实例,用于产生一个随机整数。
Supplier<Integer> randomNumberSupplier = () -> new Random().nextInt(100);
int randomNumber = randomNumberSupplier.get();
System.out.println("Random number: " + randomNumber);
在这个例子中,我们创建了一个 Supplier 对象 randomNumberSupplier
,它将返回一个随机整数。我们调用 get()
方法来执行 Supplier,并产生一个随机整数并输出结果。
示例 2:懒加载
使用 Supplier 接口实现懒加载,只有在需要时才计算结果。
Supplier<String> lazyValueSupplier = () -> {
// 复杂的计算过程
System.out.println("Performing complex calculation...");
// 返回结果
return "Lazy value result";
};
// 只有在调用 get() 方法时,才会执行复杂计算
String lazyValue = lazyValueSupplier.get();
System.out.println(lazyValue);
在这个例子中,我们创建了一个 Supplier 对象 lazyValueSupplier
,它代表了一个复杂的计算过程。在调用 get()
方法之前,不会进行实际的计算。只有在调用 get()
方法时,才会执行复杂计算并返回结果。
示例 3:产生序列号
使用 Supplier 接口实现产生序列号的功能。
AtomicInteger counter = new AtomicInteger();
Supplier<Integer> sequentialNumberSupplier = () -> counter.getAndIncrement();
// 产生序列号
int number1 = sequentialNumberSupplier.get();
int number2 = sequentialNumberSupplier.get();
System.out.println("Number 1: " + number1); // 输出 "Number 1: 0"
System.out.println("Number 2: " + number2); // 输出 "Number 2: 1"
在这个例子中,我们创建了一个 Supplier 对象 sequentialNumberSupplier
,它使用 AtomicInteger 来产生序列号。每次调用 get()
方法,都会递增 counter 并返回递增后的值,从而实现序列号的生成。
3.5Consumer 消费者
Consumer 接口代表一个接受一个参数并且不返回结果的操作。它在 Java 中被广泛用于遍历集合或执行消费型操作。
对于 Consumer
,我们需要提供入参,用来被消费,如下面这段示例代码:
class Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
接口概览
在 java.util.function
包中,Consumer 接口定义如下:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
T
表示输入参数的类型。accept(T t)
是 Consumer 接口中唯一的抽象方法,用于对输入参数t
进行操作。
Consumer 接口是一个泛型接口,它可以接受不同类型的参数。
示例 1:打印元素
创建一个 Consumer 接口实例,用于打印集合中的元素。
Consumer<String> printString = str -> System.out.println(str);
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
names.forEach(printString);
在这个例子中,我们创建了一个 Consumer 对象 printString
,它用于打印字符串。然后,我们使用 forEach 方法遍历集合,并将每个元素传递给 Consumer 的 accept()
方法,实现打印操作。
示例 2:消费元素
使用 Consumer 接口对集合中的元素进行消费型操作。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Consumer<Integer> multiplyByTwo = num -> {
// 对入参进行乘 2 操作
int result = num * 2;
System.out.println("Result: " + result);
};
numbers.forEach(multiplyByTwo);
在这个例子中,我们创建了一个 Consumer 对象 multiplyByTwo
,它将传入的整数乘以2,并输出结果。然后,我们使用 forEach 方法遍历集合,并将每个元素传递给 Consumer 的 accept()
方法,实现对元素的消费型操作。
示例 3:集合元素求和
使用 Consumer 接口对集合中的元素进行求和操作。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用 AtomicInteger 来保存结果
AtomicInteger sum = new AtomicInteger();
Consumer<Integer> addToSum = num -> sum.addAndGet(num);
numbers.forEach(addToSum);
System.out.println("Sum: " + sum); // 输出 "Sum: 15"
在这个例子中,我们创建了一个 Consumer 对象 addToSum
,它将传入的整数添加到 AtomicInteger 中,并实现对元素的求和操作。然后,我们使用 forEach 方法遍历集合,并将每个元素传递给 Consumer 的 accept()
方法,实现对元素的求和。
Consumer 的组合
Consumer 还提供了一些默认方法用于函数的组合:
andThen(Consumer after)
: 返回一个新的 Consumer,它表示当前 Consumer 和另一个 Consumer 的顺序执行。
下面是一个组合示例:
Consumer<String> printUpperCase = str -> System.out.println(str.toUpperCase());
Consumer<String> printLowerCase = str -> System.out.println(str.toLowerCase());
Consumer<String> printAndThen = printUpperCase.andThen(printLowerCase);
printAndThen.accept("Hello, World!"); // 输出 "HELLO, WORLD!" 和 "hello, world!"
在这个例子中,我们首先创建了两个 Consumer 对象 printUpperCase
和 printLowerCase
,分别打印字符串的大写和小写形式。然后,我们使用 andThen()
方法将这两个 Consumer 组合成一个新的 Consumer printAndThen
,它先执行打印大写形式,然后再执行打印小写形式。
3.6Comparator
Comparator 接口常用于比较操作,它在集合排序、搜索、自定义排序等场景中提供了灵活的比较策略。
Comparator
在 Java 8 之前是使用比较普遍的。Java 8 中除了将其升级成了函数式接口,还为它拓展了一些默认方法:
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2); // > 0
comparator.reversed().compare(p1, p2); // < 0
接口概览
在 java.util
包中,Comparator 接口定义如下:
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}
T
表示待比较对象的类型。compare(T o1, T o2)
是 Comparator 接口中唯一的抽象方法,用于比较两个对象 o1 和 o2 的顺序。
Comparator 接口是一个泛型接口,它可以用于比较不同类型的对象。
示例 1:对整数列表排序
创建一个 Comparator 对象,用于对整数列表进行排序。
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 3);
Comparator<Integer> ascendingOrder = (a, b) -> a - b;
numbers.sort(ascendingOrder);
System.out.println("Ascending order: " + numbers); // 输出 "Ascending order: [1, 2, 3, 5, 8]"
在这个例子中,我们创建了一个 Comparator 对象 ascendingOrder
,它表示按照升序排序。我们使用 sort()
方法对整数列表进行排序,使用 compare()
方法来比较整数的顺序,并输出升序排序结果。
示例 2:对字符串列表排序
使用 Comparator 接口对字符串列表进行排序。
List<String> fruits = Arrays.asList("apple", "orange", "banana", "grape");
Comparator<String> descendingOrder = (a, b) -> b.compareTo(a);
fruits.sort(descendingOrder);
System.out.println("Descending order: " + fruits); // 输出 "Descending order: [orange, grape, banana, apple]"
在这个例子中,我们创建了一个 Comparator 对象 descendingOrder
,它表示按照降序排序。我们使用 sort()
方法对字符串列表进行排序,使用 compare()
方法来比较字符串的顺序,并输出降序排序结果。
示例 3:自定义对象排序
使用 Comparator 接口对自定义对象进行排序。
class Student {
private String name;
private int age;
// 构造函数和其他方法
// Getter 和 Setter 方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
List<Student> students = Arrays.asList(
new Student("Alice", 20),
new Student("Bob", 18),
new Student("Charlie", 22)
);
Comparator<Student> sortByAge = (s1, s2) -> s1.getAge() - s2.getAge();
students.sort(sortByAge);
System.out.println("Students sorted by age: " + students);
在这个例子中,我们创建了一个 Student 类,并在其中实现了 Comparable 接口。然后,我们创建了一个 Comparator 对象 sortByAge
,它表示按照年龄进行排序。我们使用 sort()
方法对学生列表进行排序,使用 compare()
方法来比较学生对象的顺序,并输出按年龄排序的结果。