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

一、简介

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

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

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

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(105)); // 输出 15

        // Lambda表达式实现乘法
        Calculator multiplication = (a, b) -> a * b;
        System.out.println("Multiplication: " + multiplication.calculate(105)); // 输出 50

        // Lambda表达式实现除法
        Calculator division = (a, b) -> a / b;
        System.out.println("Division: " + division.calculate(105)); // 输出 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 库。

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


3.1Function

简介

Function 接口代表一个接受一个参数并且产生结果的操作。它在 Java 中被广泛用于函数式编程以及 Stream API 中的转换操作

Function 函数式接口的作用是,我们可以为其提供一个原料,他给生产一个最终的产品。通过它提供的默认方法,组合,链行处理(compose, andThen):

接口概览

java.util.function 包中,Function 接口定义如下:

@FunctionalInterface
public interface Function<TR{
    apply(T t);
}
  • T 表示输入参数的类型。

  • R 表示返回结果的类型。

  • apply(T t) 是 Function 接口中唯一的抽象方法,用于对输入参数 t 进行操作,并返回结果。

Function 接口是一个泛型接口,它可以接受不同类型的参数和返回不同类型的结果。

Java学习十三—Java8特性之Functional函数式接口
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<TUR{
    apply(T t, U u);
}
  • T 表示第一个输入参数的类型。

  • U 表示第二个输入参数的类型。

  • R 表示返回结果的类型。

  • apply(T t, U u) 是 BiFunction 接口中唯一的抽象方法,用于对输入参数 tu 进行操作,并返回结果。

BiFunction 接口是一个泛型接口,它可以接受不同类型的参数和返回不同类型的结果。

示例 1:计算两个数字的和

创建一个 BiFunction 接口实例,用于计算两个数字的和。

BiFunction<Integer, Integer, Integer> add = (num1, num2) -> num1 + num2;
int result = add.apply(510);
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(34);
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(34);
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(12345678910);
List<Integer> evenNumbers = new ArrayList<>();

for (Integer num : numbers) {
    if (num % 2 == 0) {
        evenNumbers.add(num);
    }
}

然而,使用 Predicate,我们可以这样写:

List<Integer> numbers = Arrays.asList(12345678910);
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(12345678910);

List<Integer> filteredNumbers = numbers.stream()
                                       .filter(isEven.and(isGreaterThan5))
                                       .collect(Collectors.toList());

在上述例子中,我们定义了两个 PredicateisEven 用于判断是否为偶数,isGreaterThan5 用于判断是否大于5。然后,我们使用 filter() 方法进行组合判断,并筛选出同时满足这两个条件的整数。

3.4Supplier 生产者

Supplier 接口代表一个不接受参数但返回结果的操作。它在 Java 中被广泛用于懒加载或产生随机值等场景。

SupplierFunction 不同,它不接受入参,直接为我们生产一个指定的结果,有点像生产者模式:

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{
    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(12345);

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(12345);

// 使用 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 对象 printUpperCaseprintLowerCase,分别打印字符串的大写和小写形式。然后,我们使用 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(52813);

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

原文始发于微信公众号(技海拾贝):Java学习十三—Java8特性之Functional函数式接口

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/295222.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!