【Java进阶】Java Lambda 表达式、Stream API完整梳理

发布时间:2023年12月25日

一、Lambda表达式

Lambda 表达式是一种匿名函数,它可以用来定义函数式接口的实现。Lambda 表达式可以用来简化代码,提高代码的可读性和可维护性。

1、Lambda 表达式

1.1、语法介绍

Lambda 表达式的语法如下:

(parameters) -> expression or statement

或者

(parameters) -> { statements; }

Lambda表达式由以下几个部分组成:

  • 参数列表(parameters):Lambda表达式可以有零个或多个参数。如果有多个参数,使用逗号将它们分隔开。参数的类型可以显式指定,也可以通过上下文推断得出。

  • 箭头符号(->):箭头符号用于分隔参数列表和Lambda表达式的主体。

  • 表达式(expression)或语句块(statements):Lambda表达式的主体可以是一个表达式或一个代码块。如果主体是一个表达式,则可以直接返回该表达式的结果。如果主体是一个代码块,则需要使用大括号将代码块括起来,并且可能需要使用return语句来返回结果。

Lambda 表达式是一种匿名函数,它可以用来定义函数式接口的实现。Lambda 表达式可以用来简化代码,提高代码的可读性和可维护性。

1.2、Lambda表达式特性

除了Java版本的要求外,Lambda表达式只能用于函数式接口。

  1. 函数式接口:Lambda表达式只能用于函数式接口,即只有一个抽象方法的接口。函数式接口可以使用@FunctionalInterface注解进行标记,以确保只有一个抽象方法。
@FunctionalInterface
interface MyInterface {
    void myMethod();
}

public class Main {
    public static void main(String[] args) {
        MyInterface myInterface = () -> System.out.println("Hello, Lambda!");
        myInterface.myMethod();
    }
}

在上面的示例中,我们定义了一个函数式接口MyInterface,它只有一个抽象方法myMethod。然后,我们使用Lambda表达式来实现这个接口,并在myMethod方法中打印一条消息。最后,我们创建了一个接口实例并调用myMethod方法。

  1. 上下文推断:Lambda表达式的参数类型可以通过上下文推断得出,无需显式指定。这是Java 8引入的类型推断机制的一部分。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));

在这个例子中,Lambda表达式name -> System.out.println(name)被传递给forEach方法。编译器会根据forEach方法的参数类型推断出Lambda表达式的参数类型为String。

  1. 方法引用:Lambda表达式可以使用方法引用来简化代码,双冒号(:😃。方法引用是指直接引用已有方法的方式,可以通过类名、对象实例或超类来引用方法。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

names.stream()
     .map(String::toUpperCase)
     .forEach(System.out::println);

在这个例子中,String::toUpperCase是一个方法引用,它引用了String类的toUpperCase方法。在map方法中,每个元素都会被转换为大写。然后,使用forEach方法将转换后的结果打印出来。方法引用使代码更加简洁,不需要编写Lambda表达式来调用toUpperCase方法。

1.3、方法引用,双冒号(::)

在Lambda表达式中,双冒号(::)是一种特殊的语法,用于引用方法或构造函数。它可以用于以下几种情况:

  1. 方法引用:使用双冒号(::)来引用一个已经存在的方法。例如,假设有一个函数式接口Function,它有一个抽象方法apply,可以使用方法引用来引用一个已经存在的方法作为apply的实现。例如:
Function<String, Integer> function = Integer::parseInt;

这里,Integer::parseInt表示引用了Integer类的静态方法parseInt作为apply方法的实现。

  1. 构造函数引用:使用双冒号(::)来引用一个构造函数。例如,假设有一个函数式接口Supplier,它有一个抽象方法get,可以使用构造函数引用来引用一个构造函数作为get方法的实现。例如:
Supplier<List<String>> supplier = ArrayList::new;

这里,ArrayList::new表示引用了ArrayList类的构造函数作为get方法的实现。

  1. 数组引用:使用双冒号(::)来引用一个数组。例如,可以使用int[]::new来引用一个整型数组的构造函数。

2、Lambda 表达式最佳实践

2.1、简单使用示例

以下是一些使用 Lambda 表达式的示例:

// 作为方法的参数
List<String> names = Arrays.asList("Alice", "Bob", "Carol");
names.sort((a, b) -> b.compareTo(a));

// 作为构造函数的参数
Button button = new Button(() -> System.out.println("Click"));

// 作为成员变量的初始值
Map<String, String> map = new HashMap<>();
map.put("key", "value");

// 作为局部变量的初始值
int sum = names.stream().mapToInt(String::length).sum();

Lambda 表达式可以提高代码的可读性和可维护性,因此在编写 Java 代码时,我们应该尽可能使用 Lambda 表达式。

2.2、Lambda表达式最佳实践

以下是使用Lambda表达式的一些最佳实践:

  1. 使用合适的函数式接口:Lambda表达式需要与函数式接口一起使用。函数式接口是只包含一个抽象方法的接口。在选择函数式接口时,确保它与Lambda表达式的参数和返回类型匹配。

  2. 保持Lambda表达式简洁:Lambda表达式的主要目的是使代码更加简洁和易读。因此,应尽量避免编写过于复杂的Lambda表达式。如果Lambda表达式变得过于冗长或复杂,可以考虑将其提取为一个独立的方法或使用方法引用。

  3. 使用方法引用:方法引用是Lambda表达式的一种简化形式,可以使代码更加简洁和易读。如果Lambda表达式只是简单地调用一个已经存在的方法,可以考虑使用方法引用来代替Lambda表达式。

  4. 避免副作用:Lambda表达式应该是无副作用的,即不会对外部状态产生影响。这有助于提高代码的可读性和可维护性。如果Lambda表达式需要修改外部状态,应该使用其他方式来处理,例如使用局部变量或实例变量。

  5. 使用类型推断:在Lambda表达式中,编译器可以根据上下文推断参数类型。因此,可以省略参数类型的显式声明,使代码更加简洁。但是,如果Lambda表达式的参数类型不明确或不容易理解,最好显式声明参数类型,以提高代码的可读性。

  6. 使用Lambda表达式的流式操作:Lambda表达式与流式操作(Stream API)结合使用可以实现更加简洁和功能强大的代码。流式操作提供了一种流畅的方式来处理集合数据,可以使用Lambda表达式来定义各种操作,如过滤、映射、排序等。

总的来说,使用Lambda表达式应该注重代码的简洁性、可读性和可维护性。合理选择函数式接口、使用方法引用、避免副作用以及充分利用类型推断和流式操作等技巧,可以使Lambda表达式的使用更加高效和优雅。

二、Steam API

Java 8 引入了新的 Stream API,它提供了一种更简单、更高效的方式来处理集合。Stream API 是一个函数式编程 API,它允许我们对集合中的元素进行各种操作,而无需使用传统的 for 循环。

Stream API 的核心是 Stream 对象,它表示一个元素序列。Stream 可以由集合创建,也可以由其他 Stream 创建。Stream 可以通过各种方式进行操作,包括过滤、映射、聚合和收集。

1、Stream API 常见使用

Stream API 的使用非常简单。要创建一个 Stream,我们可以使用集合的 stream() 方法。例如,以下代码创建了一个包含数字 1 到 10 的 Stream:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> stream = numbers.stream();

Stream 可以通过各种方式进行操作。最常见的操作是过滤和映射。过滤操作用于从 Stream 中选择满足某些条件的元素。例如,以下代码使用 filter() 方法从 Stream 中选择所有偶数:

Stream<Integer> evenNumbers = stream.filter(n -> n % 2 == 0);

映射操作用于将 Stream 中的元素转换为另一种类型的元素。例如,以下代码使用 map() 方法将 Stream 中的数字转换为字符串:

Stream<String> strings = stream.map(n -> String.valueOf(n));

Stream 还可以通过聚合操作来计算结果。聚合操作用于将 Stream 中的元素汇总到一个单一的值。例如,以下代码使用 sum() 方法计算 Stream 中所有元素的总和:

int sum = stream.sum();

Stream 还可以通过收集操作将结果保存到集合中。例如,以下代码使用 collect() 方法将 Stream 中的元素收集到一个列表中:

List<Integer> list = stream.collect(Collectors.toList());

Stream API 提供了一种非常简单、高效的方式来处理集合。它可以帮助我们编写更简洁、更可读的代码。

2、Stream API 使用示例

以下是一些使用 Stream API 的示例:

  • 计算集合中所有元素的总和:
int sum = numbers.stream().mapToInt(n -> n).sum();
  • 计算集合中所有元素的平均值:
double average = numbers.stream().mapToInt(n -> n).average().getAsDouble();
  • 查找集合中最大的元素:
Optional<Integer> max = numbers.stream().max(Comparator.comparing(n -> n));
  • 查找集合中所有的偶数:
Stream<Integer> evenNumbers = numbers.stream().filter(n -> n % 2 == 0);
  • 将集合中的元素转换为字符串:
Stream<String> strings = numbers.stream().map(n -> String.valueOf(n));
  • 将集合中的元素收集到一个列表中:
List<Integer> list = numbers.stream().collect(Collectors.toList());
  • 将集合中的元素保存到文件中:
numbers.stream().forEach(n -> System.out.println(n));

3、Stream API 核心方法

下面常用的Stream API方法:

方法描述类型使用示例
filter(Predicate<T> predicate)过滤流中的元素,只保留满足条件的元素中间操作stream.filter(x -> x > 5)
map(Function<T, R> mapper)对流中的每个元素应用一个函数,并将结果映射为一个新的流中间操作stream.map(x -> x * 2)
flatMap(Function<T, Stream<R>> mapper)对流中的每个元素应用一个函数,并将结果扁平化为一个新的流中间操作stream.flatMap(x -> Stream.of(x, x + 1))
distinct()去除流中的重复元素中间操作stream.distinct()
sorted()对流中的元素进行排序中间操作stream.sorted()
limit(long maxSize)限制流中元素的数量不超过指定的最大值中间操作stream.limit(10)
skip(long n)跳过流中的前n个元素中间操作stream.skip(5)
forEach(Consumer<T> action)对流中的每个元素执行指定的操作终止操作stream.forEach(System.out::println)
collect(Collector<T, A, R> collector)将流中的元素收集到一个可变容器中,如List、Set或Map终止操作stream.collect(Collectors.toList())
reduce(BinaryOperator<T> accumulator)将流中的元素按照指定的操作进行归约,得到一个结果终止操作stream.reduce(0, (a, b) -> a + b)
anyMatch(Predicate<T> predicate)检查流中是否存在满足条件的元素终止操作stream.anyMatch(x -> x > 5)
allMatch(Predicate<T> predicate)检查流中的所有元素是否都满足条件终止操作stream.allMatch(x -> x > 0)
noneMatch(Predicate<T> predicate)检查流中是否没有任何元素满足条件终止操作stream.noneMatch(x -> x < 0)
findFirst()返回流中的第一个元素终止操作stream.findFirst()
findAny()返回流中的任意一个元素终止操作stream.findAny()
count()返回流中的元素数量终止操作stream.count()
min(Comparator<T> comparator)返回流中的最小元素终止操作stream.min(Comparator.naturalOrder())
max(Comparator<T> comparator)返回流中的最大元素终止操作stream.max(Comparator.naturalOrder())

这些方法根据其功能和使用方式可以分为两种类型:中间操作和终止操作。中间操作返回一个新的流,可以被链式调用,而终止操作会产生一个最终结果或副作用。根据具体的需求,可以根据需要组合使用这些方法来处理和操作流中的元素。

4、Stream 可以配合哪些类使用

Stream 可以配合多种类使用,包括但不限于以下类:

  1. Collection 接口:Stream 可以通过 Collection 接口的 stream() 方法来创建。
List<String> names = Arrays.asList("Alice", "Bob", "Carol");
Stream<String> stream = names.stream();
  1. Arrays 类:Stream 可以通过 Arrays 类的 stream() 方法来创建。
int[] numbers = {1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(numbers);
  1. BufferedReader 类:Stream 可以通过 BufferedReader 类的 lines() 方法来创建。
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
Stream<String> lines = reader.lines();
  1. IntStream、LongStream、DoubleStream 类:这些类提供了特定类型的 Stream,可以用于处理基本类型的元素。
IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
LongStream longStream = LongStream.range(1, 10);
DoubleStream doubleStream = DoubleStream.generate(Math::random);
  1. Files 类:Stream 可以通过 Files 类的 lines() 方法来读取文件内容并创建。
Stream<String> lines = Files.lines(Paths.get("file.txt"));
  1. Pattern 类:Stream 可以通过 Pattern 类的 splitAsStream() 方法来将字符串拆分为 Stream。
Pattern pattern = Pattern.compile("\\s+");
Stream<String> words = pattern.splitAsStream("Hello World");
  1. Random 类:Stream 可以通过 Random 类的 ints()longs()doubles() 方法来生成随机数的 Stream。
Random random = new Random();
IntStream randomInts = random.ints(5, 1, 10);
  1. StreamSupport 类:Stream 可以通过 StreamSupport 类的 stream() 方法来创建自定义的 Stream。
Iterable<String> iterable = Arrays.asList("Alice", "Bob", "Carol");
Stream<String> stream = StreamSupport.stream(iterable.spliterator(), false);

这些类提供了不同的方式来创建和处理 Stream,可以根据具体的需求选择合适的类。配合不同的类使用 Stream 可以实现丰富的功能和灵活的操作。

参考

文章来源:https://blog.csdn.net/wlddhj/article/details/135196577
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。