1. 首页
  2. 后端

Java学习十三—Java8特性之Functional函数式接口

  Java学习十三—Java8特性之Functional函数式接口

================================

一、简介

====

Java 8引入了函数式接口(Functional Interface)的概念,它是指只包含一个抽象方法的接口。函数式接口可以使用Lambda表达式来创建该接口的对象。这种接口设计使得Java可以更加轻松地支持函数式编程风格,引入了更简洁和灵活的语法。

函数式接口是只包含一个抽象方法的接口,通常用于 Lambda 表达式或方法引用。 这些接口被广泛用于 Stream API 和其他与函数式编程相关的特性。

PixPin_2024-07-05_23-47-12

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 库。

11111

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 接口是一个泛型接口,它可以接受不同类型的参数和返回不同类型的结果。

image

示例 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()​ 方法来比较学生对象的顺序,并输出按年龄排序的结果。

原文链接: https://juejin.cn/post/7388088513458372617

文章收集整理于网络,请勿商用,仅供个人学习使用,如有侵权,请联系作者删除,如若转载,请注明出处:http://www.cxyroad.com/17831.html

QR code