【Java进阶】Java Lambda 表达式、Stream API完整梳理
一、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表达式只能用于函数式接口。
- 函数式接口: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方法。
- 上下文推断: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。
- 方法引用: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表达式中,双冒号(::)是一种特殊的语法,用于引用方法或构造函数。它可以用于以下几种情况:
- 方法引用:使用双冒号(::)来引用一个已经存在的方法。例如,假设有一个函数式接口
Function
,它有一个抽象方法apply
,可以使用方法引用来引用一个已经存在的方法作为apply
的实现。例如:
Function<String, Integer> function = Integer::parseInt;
这里,
Integer::parseInt
表示引用了Integer
类的静态方法parseInt
作为apply
方法的实现。
- 构造函数引用:使用双冒号(::)来引用一个构造函数。例如,假设有一个函数式接口
Supplier
,它有一个抽象方法get
,可以使用构造函数引用来引用一个构造函数作为get
方法的实现。例如:
Supplier<List<String>> supplier = ArrayList::new;
这里,
ArrayList::new
表示引用了ArrayList
类的构造函数作为get
方法的实现。
- 数组引用:使用双冒号(::)来引用一个数组。例如,可以使用
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表达式的一些最佳实践:
-
使用合适的函数式接口:Lambda表达式需要与函数式接口一起使用。函数式接口是只包含一个抽象方法的接口。在选择函数式接口时,确保它与Lambda表达式的参数和返回类型匹配。
-
保持Lambda表达式简洁:Lambda表达式的主要目的是使代码更加简洁和易读。因此,应尽量避免编写过于复杂的Lambda表达式。如果Lambda表达式变得过于冗长或复杂,可以考虑将其提取为一个独立的方法或使用方法引用。
-
使用方法引用:方法引用是Lambda表达式的一种简化形式,可以使代码更加简洁和易读。如果Lambda表达式只是简单地调用一个已经存在的方法,可以考虑使用方法引用来代替Lambda表达式。
-
避免副作用:Lambda表达式应该是无副作用的,即不会对外部状态产生影响。这有助于提高代码的可读性和可维护性。如果Lambda表达式需要修改外部状态,应该使用其他方式来处理,例如使用局部变量或实例变量。
-
使用类型推断:在Lambda表达式中,编译器可以根据上下文推断参数类型。因此,可以省略参数类型的显式声明,使代码更加简洁。但是,如果Lambda表达式的参数类型不明确或不容易理解,最好显式声明参数类型,以提高代码的可读性。
-
使用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 可以配合多种类使用,包括但不限于以下类:
- Collection 接口:Stream 可以通过 Collection 接口的
stream()
方法来创建。
List<String> names = Arrays.asList("Alice", "Bob", "Carol");
Stream<String> stream = names.stream();
- Arrays 类:Stream 可以通过 Arrays 类的
stream()
方法来创建。
int[] numbers = {1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(numbers);
- BufferedReader 类:Stream 可以通过 BufferedReader 类的
lines()
方法来创建。
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
Stream<String> lines = reader.lines();
- 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);
- Files 类:Stream 可以通过 Files 类的
lines()
方法来读取文件内容并创建。
Stream<String> lines = Files.lines(Paths.get("file.txt"));
- Pattern 类:Stream 可以通过 Pattern 类的
splitAsStream()
方法来将字符串拆分为 Stream。
Pattern pattern = Pattern.compile("\\s+");
Stream<String> words = pattern.splitAsStream("Hello World");
- Random 类:Stream 可以通过 Random 类的
ints()
、longs()
、doubles()
方法来生成随机数的 Stream。
Random random = new Random();
IntStream randomInts = random.ints(5, 1, 10);
- StreamSupport 类:Stream 可以通过 StreamSupport 类的
stream()
方法来创建自定义的 Stream。
Iterable<String> iterable = Arrays.asList("Alice", "Bob", "Carol");
Stream<String> stream = StreamSupport.stream(iterable.spliterator(), false);
这些类提供了不同的方式来创建和处理 Stream,可以根据具体的需求选择合适的类。配合不同的类使用 Stream 可以实现丰富的功能和灵活的操作。
参考
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!