# 1. 函数式编程
# 为什么需要再次修改Java?
多核 CPU 的兴起成为了不容回避的事实。涉及锁的编程算法不但容易出错,而且耗费时间。人们开发了 java.util.concurrent 包和很多第三方类库,试图将并发抽象化,帮助程序员写出在多核 CPU 上运行良好的程序。很可惜,到目前为止,我们的成果还远远不够。开发类库的程序员使用 Java 时,发现抽象级别还不够。处理大型数据集合就是个很好的例子,面对大型数据集合,Java 还欠缺高效的并行操作。开发者能够使用 Java 8 编写复杂的集合处理算法,只需要简单修改一个方法,就能让代码在多核 CPU 上高效运行。为了编写这类处理批量数据的并行类库,需要在语言层面上修改现有的 Java:增加 Lambda 表达式。
对于习惯了面向对象编程的开发者来说,抽象的概念并不陌生。面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象。
# 2. Lambda表达式
# 2.1第一个Lambda表达式
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.out.println("button clicked");
}
});
2
3
4
5
匿名内部类实现了该方法,但是代码还是不够简洁
使用 Lambda 表达式将行为和按钮单击进行关联
button.addActionListener(event -> System.out.println("button clicked"));
在 Lambda 表达式中无需指定类型,程序依然可以编译。这是因为 javac 根据程序的上下文(addActionListener 方法的签名)在后台推断出了参数 event 的类型。这意味着如果参数类型不言而明,则无需显式指定.在后续会介绍如何的推断出参数event的类型。
# 2.2 如何辨别Lambda表达式
Runnable noArguments = () -> System.out.println("Hello World"); 1
ActionListener oneArgument = event -> System.out.println("button clicked"); 2
Runnable multiStatement = () -> {
System.out.print("Hello");
System.out.println(" World");
}; 3
BinaryOperator<Long> add = (x, y) -> x + y; 4
BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y; 5
2
3
4
5
6
7
8
9
10
11
12
- 1中所示的 Lambda 表达式不包含参数,使用空括号 () 表示没有参数。该 Lambda 表达式实现了 Runnable 接口,该接口也只有一个 run 方法,没有参数,且返回类型为 void
- 中所示的 Lambda 表达式包含且只包含一个参数,可省略参数的括号
- Lambda 表达式的主体不仅可以是一个表达式,而且也可以是一段代码块,使用大括号({})将代码块括起来,如3所示。该代码块和普通方法遵循的规则别无二致,可以用返回或抛出异常来退出。只有一行代码的 Lambda 表达式也可使用大括号,用以明确 Lambda表达式从何处开始、到哪里结束
- Lambda 表达式也可以表示包含多个参数的方法,如4所示。
- 所有 Lambda 表达式中的参数类型都是由编译器推断得出的。这当然不错,但有时最好也可以显式声明参数类型,此时就需要使用小括号将参数括起来,多个参数的情况也是如此。如5所示
# 2.3 引用值,而不是变量
Lambda 表达式可以访问 final 的局部变量或实际上没有被重新赋值的变量(即有效 final 变量),这是因为 Java 的 Lambda 表达式使用的是变量的值,而不是变量的引用。当 Lambda 表达式捕获一个局部变量时,它实际上是捕获了该变量的值,并将这个值保存到内部类的一个私有 final 字段中。
Java 中的 Lambda 表达式是通过内部类实现的,当 Lambda 表达式需要访问外部局部变量时,这些变量必须是 final 或者实际上等同于 final(即在初始化后不再改变)。这是因为内部类和 Lambda 表达式的生命周期可能比创建它们的方法更长,如果允许修改这些变量,可能会导致不一致的状态,因为方法可能已经结束执行,而内部类或 Lambda 表达式仍然存在并且可能尝试访问这些变量。
所以,为了确保线程安全和避免潜在的并发问题,Java 语言规范要求 Lambda 表达式只能捕获 final 或有效 final 的局部变量。这意味着一旦变量被 Lambda 表达式捕获,它的值就不能再被改变了。这不仅保证了代码的安全性,也使得代码更加易于理解和维护。
# 2.4 函数接口
函数接口是只有一个抽象方法的接口,用作 Lambda 表达式的类型。
单个抽象方法:函数接口必须恰好有一个未实现的方法(即抽象方法)。这意味着任何实现了该接口的类都必须提供这个方法的具体实现。例如,
java.util.function包中的Predicate<T>接口只定义了一个名为test(T t)的抽象方法。@FunctionalInterface 注解:虽然不是必须的,但可以使用
@FunctionalInterface注解来显式地标注一个接口为函数接口。这样做有助于编译器检查,并确保接口符合函数接口的定义,即只能有一个抽象方法。如果尝试在被此注解的接口中添加第二个抽象方法,编译将失败。Lambda 表达式的类型:当使用 Lambda 表达式时,表达式本身没有具体的类型;它的类型由上下文决定,通常是由其赋值的目标或参数位置所指定的函数接口类型。例如,如果你有如下代码:
Predicate<String> p = s -> s.isEmpty();1这里的 Lambda 表达式
s -> s.isEmpty()类型是Predicate<String>,因为它是赋值给一个Predicate<String>类型的变量p。简化开发:通过使用函数接口和 Lambda 表达式,Java 程序员能够写出更加简洁、直观且易于理解的代码,尤其是处理集合操作时,如流(Stream)API。
默认方法和静态方法:值得注意的是,除了唯一的一个抽象方法外,函数接口还可以包含任意数量的默认方法和静态方法。这些不会影响到接口作为函数接口的身份,因为它们提供了具体的实现,不需要实现类或者 Lambda 表达式去提供实现。
# 2.5 类型推断
并不是所有的参数类型都可以根据上下文进行判断的,有时候也需要手动的指明类型。
根据方法签名来推断类型,
例如
public interface Predicate<T> {
boolean test(T t);
}
Predicate<Integer> atLeast5 = x -> x > 5;
因为Predicate定义了<T>,是一个泛型接口,可以为任何类型。但是Predicate<Integer> atLeast5,指定了<T>为Integer类型,所以参数类型就为Integer
Predicate atLeast5 = x -> x > 5;
如果没有指定泛型,编译就会报错,因为无法推断参数类型
2
3
4
5
6
7
8
9
根据 Lambda 表达式上下文信息就能推断出参数的正确类型。程序依然要经过类型检查来保证运行的安全性,但不用再显式声明类型罢了。这就是所谓的类型推断
# 3. 流
对核心类库的改进主要包括集合类的 API 和新引入的流(Stream)。流使程序员得以站在更高的抽象层次上对集合进行操作。
# 3.1 从外部迭代到内部迭代
计算来自伦敦的艺术家人数
# for 循环实现
int count = 0;
for (Artist artist : allArtists) {
if (artist.isFrom("London")) {
count++;
}
}
2
3
4
5
6
for 循环的样板代码模糊了代码的本意,for 循环其实是一个封装了迭代的语法糖调用的是迭代器。
# 迭代器实现
int count = 0;
Iterator<Artist> iterator = allArtists.iterator();
while(iterator.hasNext()) {
Artist artist = iterator.next();
if (artist.isFrom("London")) {
count++;
}
}
2
3
4
5
6
7
8
# Stream流实现
long count = allArtists.stream().filter(artist -> artist.isFrom("London")).count();
被分解为两步更简单的操作
- 找出所有来自伦敦的艺术家;
- 计算他们的人数
# 3.2 实现机制
虽然进行了两步,但并不是for循环了两遍。这里区分了惰性求值,
单看下面一行代码,其实并没有实际性的操作,filter 只刻画出了 Stream,但没有产生新的集合,像filter 这样只描述 Stream,最终不产生新集合的方法叫作惰性求值方法,而像 count 这样最终会从 Stream 产生值的方法叫作及早求值方法
allArtists.stream().filter(artist -> artist.isFrom("London"));
验证上面的正确性
allArtists.stream().filter(artist -> {
System.out.println(artist.getName());
return artist.isFrom("London");
});
2
3
4
我们发现控制台并没有打印出相应的结果,说明这行代码并没有执行。
long count = allArtists.stream()
.filter(artist -> {
System.out.println(artist.getName());
return artist.isFrom("London");
})
.count();
2
3
4
5
6
当我们添加了count发现控制台打印出了满足条件的结果。
那么如何判断是惰性求值还是及早求值方法?
判断一个操作是惰性求值还是及早求值很简单,只需看它的返回值。如果返回值是 Stream,那么是惰性求值;如果返回值是另一个值或为空,那么就是及早求值。
为什么要区分惰性求值和及早求值?
区分这两种求值方式的主要目的是为了优化性能和灵活性。通过将一些操作推迟到真正需要的时候再执行,可以减少不必要的工作量,同时也可以更好地利用现代计算机架构的特点,比如并行处理等。
总之,惰性求值有助于创建高效的、可组合的操作链,只有当终端操作触发整个流水线的执行时,才会实际执行这些操作,从而可能节省大量的处理时间和资源。
# 3.3 常用的流操作
# 3.3.1 collect 返回集合
collect() 方法是一个及早求值操作。
List<String> collected = Stream.of("a", "b", "c").collect(Collectors.toList());
先生成一个stream流,然后再使用 collect(toList()) 方法从 Stream 中生成一个列表。
collect方法原理
Stream.of("a", "b", "c")实现的是将同类型可变参数转换成流对象,然后通过collect方法
<R, A> R collect(Collector<? super T, A, R> collector);
collect方法需要实现一个Collector 接口:它为流(Stream)的归约操作提供了一种通用的实现方式,特别是用于 collect() 方法。Collector 接口定义了如何将流中的元素累积到一个汇总结果中,这个结果可以是任何类型
Collector 接口的主要方法
Supplier<A> supplier():提供一个创建新结果容器的函数。该函数没有参数,并返回一个新的累加器实例(A),这是收集过程中用来累积部分结果的对象。BiConsumer<A, T> accumulator():提供一个将单个流元素添加到结果容器的函数。该函数接受两个参数:一个是累加器实例(A),另一个是流中的元素(T)。它将流中的元素累积到累加器中,但不返回任何值。BinaryOperator<A> combiner():提供一个合并两个结果容器的函数。这对于并行流特别重要,因为它允许将不同线程的结果合并在一起。该函数接受两个累加器实例(A),并返回一个新的累加器实例,表示两者合并后的结果。Function<A, R> finisher():提供一个在所有元素都被处理后对结果容器进行最后转换的函数。该函数接受一个累加器实例(A),并返回最终结果(R)。对于某些收集器来说,这一步可能是恒等操作,但对于其他收集器来说,可能需要执行额外的逻辑来生成最终结果。Set<Characteristics> characteristics():返回描述收集器特性的集合。这些特性可以帮助优化收集过程。常见的特性包括:CONCURRENT:收集器是并发安全的,可以在多个线程之间共享而无需外部同步。UNORDERED:收集器的结果不受输入源是否有序的影响。IDENTITY_FINISH:收集器的finisher函数是一个恒等函数,即累加器本身就是最终结果。
虽然可以直接实现 Collector 接口,但通常我们会使用 Collectors 类提供的静态工厂方法来创建常用类型的收集器,比如:
# 1.
toList()- 功能:将流中的元素收集到一个新的
List中。 - 特点:不保证保留流中元素的顺序,除非流本身是有序的(如由有序集合生成的流)。
- 使用示例:
List<String> list = Stream.of("a", "b", "c").collect(Collectors.toList());1# 2.
toSet()- 功能:将流中的元素收集到一个新的
Set中,去除重复项。 - 特点:结果集不保证任何特定的顺序。
- 使用示例:
Set<String> set = Stream.of("a", "b", "c", "a").collect(Collectors.toSet());1# 3.
toMap(Function<K>, Function<V>)- 功能:将流中的元素收集到一个新的
Map中。第一个参数定义了键的映射函数,第二个参数定义了值的映射函数。 - 特点:如果遇到键冲突,默认会抛出
IllegalStateException;可以通过重载版本提供合并函数来处理键冲突。 - 使用示例:
Map<Integer, String> map = Stream.of("a", "b", "c") .collect(Collectors.toMap(String::hashCode, s -> s));1
2# 4.
groupingBy(Classifier)- 功能:根据分类函数对流中的元素进行分组,并将相同类别的元素收集到一个
Map中,其中键是类别,值是一个包含该类别所有元素的List。 - 特点:支持进一步定制子收集器和处理键冲突。
- 使用示例:
Map<Integer, List<String>> grouped = Stream.of("apple", "banana", "carrot") .collect(Collectors.groupingBy(String::length));1
2# 5.
partitioningBy(Predicate)- 功能:根据给定的谓词条件将流中的元素分区为两个
Map.Entry<Boolean, List<T>>,分别表示满足条件和不满足条件的元素列表。 - 特点:结果是一个
Map<Boolean, List<T>>,其中键为true和false。 - 使用示例:
Map<Boolean, List<Integer>> partitioned = Stream.of(1, 2, 3, 4) .collect(Collectors.partitioningBy(n -> n % 2 == 0));1
2# 6.
summarizingInt/Long/Double()- 功能:计算流中整数、长整数或双精度浮点数的统计摘要,包括计数、最小值、最大值、总和和平均值。
- 特点:返回对应的
IntSummaryStatistics、LongSummaryStatistics或DoubleSummaryStatistics对象。 - 使用示例:
IntSummaryStatistics stats = Stream.of(1, 2, 3, 4).collect(Collectors.summarizingInt(Integer::intValue));1# 7.
joining()- 功能:将流中的字符串元素连接成一个单一的字符串。
- 特点:可以指定分隔符、前缀和后缀。
- 使用示例:
String joined = Stream.of("a", "b", "c").collect(Collectors.joining(", "));1# 8.
averagingInt/Long/Double()- 功能:计算流中整数、长整数或双精度浮点数的平均值。
- 特点:返回
double类型的结果。 - 使用示例:
double average = Stream.of(1, 2, 3, 4).collect(Collectors.averagingInt(Integer::intValue));1# 9.
maxBy(Comparator)和minBy(Comparator)- 功能:找到流中按给定比较器的最大或最小元素。
- 特点:如果流为空,则返回
Optional.empty()。 - 使用示例:
Optional<Integer> max = Stream.of(1, 2, 3, 4).collect(Collectors.maxBy(Integer::compare)); Optional<Integer> min = Stream.of(1, 2, 3, 4).collect(Collectors.minBy(Integer::compare));1
2# 10.
reducing(BinaryOperator)和reducing(T identity, BinaryOperator)- 功能:通过提供的二元运算符将流中的元素累积为一个值。前者需要流非空,后者允许提供一个初始值。
- 特点:适用于任意类型的归约操作。
- 使用示例:
// 没有初始值的情况 Optional<Integer> sum = Stream.of(1, 2, 3, 4).collect(Collectors.reducing((a, b) -> a + b)); // 提供初始值的情况 Integer product = Stream.of(1, 2, 3, 4).collect(Collectors.reducing(1, (a, b) -> a * b));1
2
3
4
5- 功能:将流中的元素收集到一个新的
这些方法简化了复杂收集逻辑的创建,提供了现成的解决方案。
# 3.3.2 map返回特定的值
map方法类型的值转换成另外一种类型
使用 for 循环将字符串转换为大写
List<String> collected = new ArrayList<>();
for (String string : asList("a", "b", "hello")) {
String uppercaseString = string.toUpperCase();
collected.add(uppercaseString);
}
2
3
4
5
使用 map 操作将字符串转换为大写形式
List<String> collected = Stream.of("a", "b", "hello")
.map(string -> string.toUpperCase()) // <1>
.collect(toList());
2
3
传给 map的 Lambda 表达式只接受一个 String 类型的参数,返回一个新的 String,
map方法原理
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
从上面的方法可以看出参数和返回值不必属于同一种类型,但是 Lambda 表达式必须是 Function 接口的一个实例
Function 接口介绍
Function<T, R> 是一个函数式接口(位于 java.util.function 包中),它代表了一个从类型 T 的输入参数到类型 R 的结果的函数。
这个接口包含了一个抽象方法 apply(T t),该函数应用于给定的参数并返回新的结果。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
compose(Function<? super V, ? extends T> before)
- 功能:这个方法用于创建一个新的
Function实例,该实例首先应用传入的before执行before中的apply方法,然后将结果传递给当前Function实例的apply方法。
使用示例
// 将字符串转换为整数
Function<String, Integer> stringToInt = Integer::parseInt;
// 将整数转换为字符串
Function<Integer, String> intToString = i -> Integer.toString(i); // 显式指定
//无法处理直接处理String转String,可以利用compose实现,
// 创建一个新的 Function,它首先将字符串转换为整数,然后增加1,最后再转换回字符串
Function<String, String> process = intToString.compose(stringToInt);
// 应用组合后的函数
String result = process.apply("123"); // 结果应该是 "124"
2
3
4
5
6
7
8
9
10
11
12
andThen(Function<? super R, ? extends V> after)
- 功能:这个方法用于创建一个新的
Function实例,该实例首先应用当前Function实例的apply方法,然后将结果传递给传入的after的apply方法。
使用示例
Function<String, Integer> stringToInt = Integer::parseInt;
Function<Integer, Double> intToDouble = Math::sqrt;
// 创建一个新的 Function,它首先将字符串转换为整数,然后再计算其平方根
Function<String, Double> andThen = stringToInt.andThen(intToDouble);
Double result = andThen.apply("16"); // 结果是 4.0
2
3
4
5
6
7
static <T> Function<T, T> identity()
- 功能:这是一个静态方法,用于创建一个恒等函数,即输入什么就返回什么,不做任何修改。
使用示例
1. 用于分组操作
在分组操作中,如果需要将元素本身作为键,可以使用 Function.identity()。
java复制代码import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class FunctionIdentityExample {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Alice", "Tom");
// 按名字分组
Map<String, List<String>> groupedByName = names.stream()
.collect(Collectors.groupingBy(Function.identity()));
System.out.println(groupedByName);
// 输出: {Alice=[Alice, Alice], Bob=[Bob], Tom=[Tom]}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2. 用于去重
如果需要去重且保留每个元素本身,可以用 Collectors.toMap 结合 Function.identity()。
java复制代码import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class FunctionIdentityExample {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Alice", "Tom");
// 去重并保留原始值
Map<String, String> uniqueNames = names.stream()
.collect(Collectors.toMap(
Function.identity(), // 使用元素本身作为键
Function.identity(), // 使用元素本身作为值
(existing, replacement) -> existing // 解决键冲突,保留已有值
));
System.out.println(uniqueNames);
// 输出: {Alice=Alice, Bob=Bob, Tom=Tom}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
3. 用于默认值的处理
当使用映射操作(如 Map.computeIfAbsent)时,Function.identity() 可以作为简单的函数逻辑。
java复制代码import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class FunctionIdentityExample {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("key1", "value1");
// 如果 key2 不存在,则返回 key2 本身作为值
String value = map.computeIfAbsent("key2", Function.identity());
System.out.println(map);
// 输出: {key1=value1, key2=key2}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
4. 简化流映射操作
当流中元素无需进行额外处理时,可以直接用 Function.identity() 作为映射函数。
java复制代码import java.util.List;
import java.util.stream.Collectors;
public class FunctionIdentityExample {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Tom");
// 映射后直接输出原始值
List<String> result = names.stream()
.map(Function.identity())
.collect(Collectors.toList());
System.out.println(result);
// 输出: [Alice, Bob, Tom]
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
总结
这些默认方法和静态方法使得 Function 接口更加灵活且易于使用。通过 compose() 和 andThen(),我们可以在不改变原始函数的情况下轻松地将多个函数链接在一起,形成复杂的数据处理流水线。identity() 则提供了一种方便的方式来创建一个不做任何操作的函数,这在某些情况下是非常有用的,例如作为默认值或占位符。
同类型方法
map 方法 <R> Stream<R> map(Function<? super T,? extends R> mapper)
mapToInt 方法 IntStream mapToInt(ToIntFunction<? super T> mapper)
mapToLong 方法 LongStream mapToLong(ToLongFunction<? super T> mapper)
mapToDouble 方法 DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
2
3
4
# 3.3.3 filter过滤
遍历数据并检查其中的元素时,是否满足过滤条件形成新的流数据。filter 核心思想是保留 Stream中的一些元素,而过滤掉其他的
使用循环遍历列表,使用条件语句做判断
for (String value : asList("a", "1abc", "abc1")) {
if (isDigit(value.charAt(0))) {
beginningWithNumbers.add(value);
}
}
2
3
4
5
函数式风格
List<String> beginningWithNumbers
= Stream.of("a", "1abc", "abc1")
.filter(value -> isDigit(value.charAt(0)))
.collect(toList());
2
3
4
filter方法原理解析
filter方法,需要传入一个Predicate接口实现类对象
Stream<T> filter(Predicate<? super T> predicate);
Predicate
Predicate<T> 是一个函数式接口(位于 java.util.function 包中),它表示一个接受单个输入参数并返回布尔值的谓词(predicate)。这个接口可以用于实现各种逻辑条件判断,通常与流(Stream)API结合使用,以便于过滤集合或数组中的元素。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);//主要用执行接口实现,来返回布尔值
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
当中还包含几个默认方法,如下是接口中的默认方法的使用
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class PredicateExample {
public static void main(String[] args) {
// 创建一个数字列表用于演示
List<Integer> numbers = Arrays.asList(1, 5, 10, 15, 20, 25);
// 定义几个简单的谓词
Predicate<Integer> isGreaterThanTen = n -> n > 10;
Predicate<Integer> isLessThanTwenty = n -> n < 20;
Predicate<Integer> isEven = n -> n % 2 == 0;
// 使用 and() 组合两个谓词,只保留大于10且小于20的元素
List<Integer> betweenTenAndTwenty = numbers.stream()
.filter(isGreaterThanTen.and(isLessThanTwenty))
.collect(Collectors.toList());
System.out.println("Between 10 and 20: " + betweenTenAndTwenty); // 输出: [15]
// 使用 or() 组合两个谓词,保留大于10或为偶数的元素
List<Integer> greaterThanTenOrEven = numbers.stream()
.filter(isGreaterThanTen.or(isEven))
.collect(Collectors.toList());
System.out.println("Greater than 10 or even: " + greaterThanTenOrEven); // 输出: [10, 15, 20, 25]
// 使用 negate() 反转谓词,保留不大于10的元素
List<Integer> notGreaterThanTen = numbers.stream()
.filter(isGreaterThanTen.negate())
.collect(Collectors.toList());
System.out.println("Not greater than 10: " + notGreaterThanTen); // 输出: [1, 5, 10]
// 使用 isEqual() 检查相等性,这里创建一个与特定值相等的谓词
Predicate<Integer> isEqualToFifteen = Predicate.isEqual(15);
List<Integer> equalToFifteen = numbers.stream()
.filter(isEqualToFifteen)
.collect(Collectors.toList());
System.out.println("Equal to 15: " + equalToFifteen); // 输出: [15]
// 还可以直接用静态方法 isEqual 来比较对象是否相等
String targetString = "Hello";
Predicate<String> stringPredicate = Predicate.isEqual(targetString);
boolean isEqual = stringPredicate.test("Hello");
System.out.println("Is equal to 'Hello': " + isEqual); // 输出: true
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 3.3.4 flatMap 合并流
用 Stream 替换值,然后将多个 Stream 连接成一个 Stream,合并流,将多个流合并平坦化为新流的效果
包含多个列表的 Stream
List<Integer> together = Stream.of(asList(1, 2), asList(3, 4))
.flatMap(numbers -> numbers.stream())
.collect(toList());
2
3
调用 stream 方法,将每个列表转换成 Stream 对象,其余部分由 flatMap 方法处理。flatMap 方法的相关函数接口和 map 方法的一样,都是 Function 接口,只是方法的返回值限定为 Stream 类型。
同类型方法
flatMap 方法 <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
flatMapToInt 方法 IntStream flatMapToInt(Function<? super T,? extends IntStream> mapper)
flatMapToLong 方法 LongStream flatMapToLong(Function<? super T,? extends LongStream> mapper)
flatMapToDouble 方法 DoubleStream flatMapToDouble(Function<? super T,? extends DoubleStream> mapper)
2
3
4
# 3.3.5 max和min比较值
Stream 上常用的操作之一是求最大值和最小值
List<Track> tracks = asList(new Track("Bakai", 524),
new Track("Violets for Your Furs", 378),
new Track("Time Was", 451));
Track shortestTrack = tracks.stream()
.min(comparing(track -> track.getLength()))
.get();
Track shortestTrack2 = tracks.stream()
.max(comparing(track -> track.getLength()))
.get();
2
3
4
5
6
7
8
9
10
11
为了让 Stream 对象按照曲目长度进行排序,需要传给它一个 Comparator 对象,Java 8 提供了一个新的静态方法 comparing,使用它可以方便地实现一个比较器。这个方法接受一个函数并返回另一个函数。
实现原理分析
Comparator接口
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
default <U> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
return thenComparing(comparing(keyExtractor, keyComparator));
}
default <U extends Comparable<? super U>> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor)
{
return thenComparing(comparing(keyExtractor));
}
default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
return thenComparing(comparingInt(keyExtractor));
}
default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {
return thenComparing(comparingLong(keyExtractor));
}
default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) {
return thenComparing(comparingDouble(keyExtractor));
}
public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
return Collections.reverseOrder();
}
@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
}
public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(true, comparator);
}
public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(false, comparator);
}
public static <T, U> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(keyComparator);
return (Comparator<T> & Serializable)
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
keyExtractor.apply(c2));
}
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
}
public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));
}
public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
是 Java 中用于定义对象排序规则的接口,位于 java.util 包中。它允许你为类定义一个或多个排序顺序,而不需要修改类本身。Comparator 接口在需要对集合中的元素进行排序时非常有用,特别是在处理没有自然排序(即没有实现 Comparable 接口)的对象,或者想要以不同于自然顺序的方式排序时。
# 1.
int compare(T o1, T o2)- 功能:这是
Comparator接口中唯一的抽象方法,用于比较两个对象。
# 示例代码:
import java.util.*; public class ComparatorExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9); // 使用自定义Comparator按降序排序 Comparator<Integer> descendingOrder = (a, b) -> b - a; numbers.sort(descendingOrder); System.out.println(numbers); // 输出: [9, 5, 4, 3, 1, 1] } }1
2
3
4
5
6
7
8
9
10
11
12
13# 2. 默认方法
#
default Comparator<T> reversed()- 功能:返回当前比较器的反转版本,即将升序变为降序,反之亦然。
Comparator<Integer> ascendingOrder = Integer::compare; Comparator<Integer> descendingOrder = ascendingOrder.reversed();1
2#
default <U> Comparator<T> thenComparing(Function<? super T, ? extends U> keyExtractor)- 功能:当两个对象在当前比较器下比较结果相同时,使用给定的比较器进一步比较。
// 假设有一个Person类,包含name和age属性 Comparator<Person> byName = Comparator.comparing(Person::getName); Comparator<Person> byAgeThenName = byName.thenComparing(Person::getAge);1
2
3#
default Comparator<T> thenComparing(Comparator<? super T> other)- 功能:与上面的方法类似,但接受另一个
Comparator对象作为参数。
Comparator<Person> byAge = Comparator.comparingInt(Person::getAge); Comparator<Person> byAgeThenName = byAge.thenComparing(byName);1
2#
default <U extends Comparable<? super U>> Comparator<T> thenComparing(Function<? super T, ? extends U> keyExtractor)- 功能:提供了一个简化版本的
thenComparing方法,直接接受一个提取键的函数,并假设该键是可比较的。
Comparator<Person> byAgeThenName = Comparator.comparing(Person::getAge).thenComparing(Person::getName);1#
default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor)- 功能:提供了一个专门用于双精度浮点数键的
thenComparing方法。
Comparator<Person> byHeight = Comparator.comparingDouble(Person::getHeight);1#
default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor)- 功能:提供了一个专门用于整型键的
thenComparing方法。
Comparator<Person> byAge = Comparator.comparingInt(Person::getAge);1#
default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor)- 功能:提供了一个专门用于长整型键的
thenComparing方法。
Comparator<Person> bySalary = Comparator.comparingLong(Person::getSalary);1# 3. 静态方法
#
static <T, U> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor)- 功能:接受一个从类型
T映射到可比较类型U的函数,并基于此映射创建一个比较器。
Comparator<Person> byName = Comparator.comparing(Person::getName);1#
static <T, U> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator)- 功能:接受一个映射函数和一个用于比较映射结果的比较器。
Comparator<Person> caseInsensitiveByName = Comparator.comparing(Person::getName, String.CASE_INSENSITIVE_ORDER);1#
static <T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor)- 功能:简化版的
comparing方法,直接接受一个返回Comparable类型的函数。
Comparator<Person> byName = Comparator.comparing(Person::getName);1#
static <T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor)- 功能:提供了一个专门用于双精度浮点数键的
comparing方法。
Comparator<Person> byHeight = Comparator.comparingDouble(Person::getHeight);1#
static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor)- 功能:提供了一个专门用于整型键的
comparing方法。
Comparator<Person> byAge = Comparator.comparingInt(Person::getAge);1#
static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor)- 功能:提供了一个专门用于长整型键的
comparing方法。
Comparator<Person> bySalary = Comparator.comparingLong(Person::getSalary);1#
static <T extends Comparable<? super T>> Comparator<T> naturalOrder()- 功能:返回一个按照自然顺序比较元素的比较器。
Comparator<String> stringNaturalOrder = Comparator.naturalOrder();1#
static <T extends Comparable<? super T>> Comparator<T> reverseOrder()- 功能:返回一个按照自然顺序相反方向比较元素的比较器。
Comparator<String> stringReverseOrder = Comparator.reverseOrder();1- 功能:这是
# 3.3.7 reduce一组值生成一个值
reduce 操作可以实现从一组值中生成一个值。在上述例子中用到的 count、min 和 max 方法,因为常用而被纳入标准库中。事实上,这些方法都是 reduce 操作
使用 reduce 求和
int count = Stream.of(1, 2, 3).reduce(0, (acc, element) -> acc + element);
同类型方法
T reduce(T identity,BinaryOperator<T> accumulator) //指定初始值和一个二元操作符
原理解析
BiFunction接口
功能:这是 BiFunction 接口中唯一的抽象方法,用于将两个输入参数应用到某个操作上,并返回结果。
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}
2
3
4
5
6
7
8
9
10
11
- 默认方法
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after)
- 功能:允许你将当前
BiFunction的结果传递给另一个函数after进行进一步处理。这可以用来构建函数链,使得可以在一个步骤之后立即执行另一个操作。
import java.util.function.BiFunction;
import java.util.function.Function;
public class AndThenExample {
public static void main(String[] args) {
// 定义一个BiFunction来接收两个整数并返回它们的和
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
// 定义一个Function来将整数转换为字符串
Function<Integer, String> toString = Object::toString;
// 创建一个新的BiFunction,它首先加法运算,然后将结果转换为字符串
BiFunction<Integer, Integer, String> addAndConvertToString = add.andThen(toString);
// 应用组合后的函数
String stringResult = addAndConvertToString.apply(3, 7);
System.out.println("The sum as string is: " + stringResult); // 输出: The sum as string is: 10
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
使用场景
BiFunction 接口适用于需要对两个同类型或不同类型的数据进行操作的情况,例如数学运算(如加法、乘法等),也可以用于更复杂的逻辑,比如数据转换或属性组合。
实际应用中的例子
假设我们有一个简单的场景,需要创建一个 BiFunction 来接收两个字符串,并返回它们连接后的结果:
// 定义一个BiFunction来接收两个字符串并返回它们连接后的结果
BiFunction<String, String, String> concatenate = (s1, s2) -> s1.concat(s2);
// 应用该函数
String result = concatenate.apply("Hello", " World");
System.out.println(result); // 输出: Hello World
}
2
3
4
5
6
7
BinaryOperator接口
功能:实际上是一个特殊的 BiFunction<T, T, T>,意味着它接受两个相同类型的输入参数,并返回相同类型的结果。这个接口主要用于表示对两个同类型对象的操作,例如数学运算(加法、乘法等),并且结果与输入参数类型一致。
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}
public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BinaryOperator 接口的主要方法
T apply(T t, T u)
- 功能:这是
BinaryOperator接口中唯一的抽象方法,用于将两个相同类型的输入参数应用到某个操作上,并返回相同类型的结果。
import java.util.function.BinaryOperator;
public class BinaryOperatorExample {
public static void main(String[] args) {
// 定义一个BinaryOperator来接收两个整数并返回它们的和
BinaryOperator<Integer> add = (a, b) -> a + b;
// 应用该函数
int result = add.apply(5, 10);
System.out.println("The sum is: " + result); // 输出: The sum is: 15
}
}
2
3
4
5
6
7
8
9
10
11
12
默认方法
default <V> BiFunction<T,T,V> andThen(Function<? super T,? extends V> after)
- 功能:允许你将当前
BinaryOperator的结果传递给另一个函数after进行进一步处理。这可以用来构建函数链,使得可以在一个步骤之后立即执行另一个操作。
import java.util.function.BinaryOperator;
import java.util.function.Function;
public class AndThenExample {
public static void main(String[] args) {
// 定义一个BinaryOperator来接收两个整数并返回它们的和
BinaryOperator<Integer> add = (a, b) -> a + b;
// 定义一个Function来将整数转换为字符串
Function<Integer, String> toString = Object::toString;
// 创建一个新的BiFunction,它首先加法运算,然后将结果转换为字符串
BiFunction<Integer, Integer, String> addAndConvertToString = add.andThen(toString);
// 应用组合后的函数
String stringResult = addAndConvertToString.apply(3, 7);
System.out.println("The sum as string is: " + stringResult); // 输出: The sum as string is: 10
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
静态方法
BinaryOperator 接口还提供了两个静态方法,用于创建基于比较器的最大值和最小值操作:
static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator)
- 功能:根据提供的比较器,创建一个
BinaryOperator,该操作返回两个元素中的较小者。
static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator)
- 功能:根据提供的比较器,创建一个
BinaryOperator,该操作返回两个元素中的较大者。
import java.util.Comparator;
import java.util.function.BinaryOperator;
public class MinMaxByExample {
public static void main(String[] args) {
// 使用minBy创建一个BinaryOperator来找到两个整数中的较小者
BinaryOperator<Integer> minOp = BinaryOperator.minBy(Integer::compare);
int minValue = minOp.apply(10, 20);
System.out.println("Min value: " + minValue); // 输出: Min value: 10
// 使用maxBy创建一个BinaryOperator来找到两个整数中的较大者
BinaryOperator<Integer> maxOp = BinaryOperator.maxBy(Integer::compare);
int maxValue = maxOp.apply(10, 20);
System.out.println("Max value: " + maxValue); // 输出: Max value: 20
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3.3.8 distinct 去重流
Stream<T> distinct();
distinct() 是 Java Stream API 中的一个中间操作,用于去除流中的重复元素。它通过 Object.equals(Object) 方法来判断两个元素是否相等,并确保结果流中每个元素都是唯一的。这个方法对于集合去重非常有用,尤其是在处理大量数据时。
@Test
public void distinct() {
List<Integer> list = Stream.of(1, 2, 3, 4, 4, 5).distinct().collect(toList());
assertEquals(asList(1, 2, 3, 4, 5), list);
}
2
3
4
5
自定义对象的去重 当流中的元素不是基本类型或简单对象(如 String),而是自定义对象时,distinct() 默认仍然会根据对象的 equals() 方法来判断是否相等。因此,如果你希望基于特定属性进行去重,你需要确保你的类正确地重写了 equals() 和 hashCode() 方法
@Test
public void distinct2() {
//自定义数据类型去重
Author author = new Author(1L, "蒙多", 33, "一个从菜刀中明悟哲理的祖安人");
Author author2 = new Author(2L, "亚拉索", 15, "狂风也追逐不上他的思考速度");
Author author3 = new Author(3L, "易", 14, "是这个世界在限制他的思维");
Author author4 = new Author(3L, "易", 14, "是这个世界在限制他的思维");
List<Author> collect = Stream.of(author, author2, author3, author4).distinct().collect(toList());
collect.forEach(e -> System.out.println(e.toString()));
}
2
3
4
5
6
7
8
9
10
11
# 3.3.9 sorted 排序
sorted() 是 Java Stream API 中的一个中间操作,用于对流中的元素进行排序。它返回一个新的流,其中元素按照自然顺序(如果元素实现了 Comparable 接口)或根据提供的比较器进行排序。sorted() 操作不会修改原始流或集合,而是创建一个有序的流视图。
当流中的元素实现了 Comparable 接口时,可以直接使用 sorted() 方法进行排序。
Set<Integer> numbers = new HashSet<>(asList(4, 3, 2, 1));
List<Integer> sameOrder = numbers.stream()
.sorted()
.collect(toList()); //默认升序
assertEquals(asList(1, 2, 3, 4), sameOrder);
assertEquals(asList(4, 3, 2, 1), numbers.stream()
.sorted(Comparator.reverseOrder())
.collect(toList())); //反转顺序
2
3
4
5
6
7
8
9
自定义数据类型排序
@Test
public void hashSetToStreamSorted() {
Set<Integer> numbers = new HashSet<>(asList(4, 3, 2, 1));
//自定义数据类型去重
Author author = new Author(1L, "蒙多", 33, "一个从菜刀中明悟哲理的祖安人");
Author author2 = new Author(2L, "亚拉索", 15, "狂风也追逐不上他的思考速度");
Author author3 = new Author(3L, "易", 14, "是这个世界在限制他的思维");
Author author4 = new Author(3L, "易", 14, "是这个世界在限制他的思维");
//升序
List<Author> collect = Stream.of(author, author2, author3, author4).sorted(comparing(Author::getAge)).collect(toList());
collect.forEach(e -> System.out.println(e.toString()));
//倒序
List<Author> collect2 = Stream.of(author, author2, author3, author4).sorted(comparing(Author::getAge).reversed()).collect(toList());
collect2.forEach(e -> System.out.println(e.toString()));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3.3.10 peek 观察流
peek() 是 Java Stream API 中的一个中间操作,它允许你在流的处理过程中对每个元素执行一个操作(如调试输出、修改状态等),但不会改变流的内容。换句话说,peek() 可以用来观察流中的元素,而不会影响后续的操作。
peek() 方法接受一个 Consumer<? super T> 接口作为参数,该接口定义了一个单一抽象方法 void accept(T t),用于消费传入的元素。这意味着你可以通过传递一个 Consumer 实例来指定在流中每个元素上执行的动作。
注意事项
- 惰性求值:由于流是惰性求值的,除非你调用了终端操作(如
collect()、forEach()等),否则流中的任何中间操作(包括peek())都不会被执行。 - 副作用:虽然
peek()不应该被用来改变流中的元素,但在某些情况下,它可以用于具有副作用的操作,比如更新外部状态或记录日志。然而,这种做法应当谨慎使用,因为它可能会导致代码难以理解和维护。
@Test
public void peekTest() {
List<String> collect = Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(toList());
System.out.println(collect);
}
2
3
4
5
6
7
8
9
10
11
Consumer 接口的作用
Consumer<T> 是 Java 8 引入的一个函数式接口,位于 java.util.function 包中。它表示一个接受单个输入参数并返回无结果的操作。Consumer 接口主要用于定义消费型操作,即那些只对数据进行某种形式的处理而不产生新数据的操作。
主要方法
void accept(T t):这是Consumer接口中唯一的抽象方法,用于定义如何消费给定类型的参数。
默认方法
default Consumer<T> andThen(Consumer<? super T> after):这个方法允许你将当前Consumer和另一个Consumer组合起来,形成一个新的Consumer,它会先应用当前Consumer,然后再应用传入的Consumer。
import java.util.function.Consumer;
public class ConsumerExample {
public static void main(String[] args) {
Consumer<String> printUpperCase = s -> System.out.println(s.toUpperCase());
Consumer<String> printLowerCase = s -> System.out.println(s.toLowerCase());
// 创建一个新的Consumer,它首先将字符串转换为大写,然后转换为小写
Consumer<String> combined = printUpperCase.andThen(printLowerCase);
combined.accept("Hello");
// 输出:
// HELLO
// hello
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3.3.11 llimit 取前n个元素
# 3.3.12 forEach() 并行循坏处理
@Test
public void limitTest() {
//1.准备一个List集合
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("ddddd");
list.add("eee");
list.add("ffffff");
list.add("bbb");
//2.只取前5个元素
list.stream().limit(5).forEach(System.out::println);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.3.13 skip 跳过前n个元素
//3.跳过前3个元素 eee ffffff bbb
list.stream().skip(3).forEach(System.out::println);
2
# 3.3.14 forEachOrder 顺序遍历
forEachOrdered() 是 Java Stream API 中的一个终端操作,它保证按照流的遇到顺序来遍历元素并对其执行给定的操作。对于有序流(如 List 创建的流),这将确保元素按照原始顺序被处理;而对于无序流(如 Set 或并行流),则不保证任何特定顺序。
主要特点
- 保持遇到顺序:
forEachOrdered()会按照流中元素的遇到顺序来应用提供的动作,这对于需要保持元素顺序的操作非常重要。 - 适用于并行流:即使在并行流中,
forEachOrdered()也会保持元素的遇到顺序,而普通的forEach()在并行流中不保证这一点。
使用场景
当你需要确保对流中的元素进行某种操作时保持它们的原始顺序,尤其是在处理并行流时,forEachOrdered() 就显得尤为重要。
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class ForEachOrderedExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用forEachOrdered()打印流中的每个元素,保持遇到顺序
numbers.stream()
.parallel() // 创建一个并行流
.forEachOrdered(System.out::println);
// 输出:
// 1
// 2
// 3
// 4
// 5
// 注意:尽管是并行流,输出仍然保持了原始顺序
// 对比使用forEach()在并行流上的行为
System.out.println("Using forEach in parallel stream:");
numbers.stream()
.parallel()
.forEach(System.out::println);
// 输出:
// 可能会乱序,例如:
// 1
// 3
// 2
// 5
// 4
// 因为forEach()在并行流中不保证遇到顺序
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
在这个例子中,forEachOrdered() 确保了即使在并行流中,元素也按照原始顺序被打印出来。而 forEach() 则可能不会保持这种顺序,特别是在并行流中。
# 3.3.15 parallel()和parallelStream()
parallel() 和 parallelStream() 是 Java 并行处理中的两个不同概念,它们分别用于不同的上下文中。让我们逐一解释这两个方法,并说明它们之间的区别和使用场景。
parallel()
定义
parallel() 是 java.util.stream.BaseStream 接口中的一个中间操作,它可以将一个串行流转换为并行流。一旦调用了 parallel(),后续的流操作将会尽可能地并行执行,即利用多个线程来处理流中的元素,从而可能提高性能,特别是在处理大量数据时。
使用场景
当你有一个已经存在的流对象,并且你希望在这个流的基础上开启并行处理时,可以使用 parallel() 方法
import java.util.Arrays;
import java.util.List;
public class ParallelExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 创建一个串行流,然后转换为并行流
numbers.stream()
.parallel() // 转换为并行流
.forEach(System.out::println);
// 输出顺序不保证,因为是并行流
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
parallelStream()
定义
parallelStream() 是集合类(如 List, Set, Map 等)中直接提供的方法,用于立即创建一个并行流。它提供了一种更简洁的方式来开始并行处理,而不需要先创建一个串行流再调用 parallel() 方法。
import java.util.Arrays;
import java.util.List;
public class ParallelStreamExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 直接创建一个并行流
numbers.parallelStream()
.forEach(System.out::println);
// 输出顺序不保证,因为是并行流
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
关键区别
- 调用位置:
parallel()是一个可以在任何现有的流上调用的方法,用来将其转换为并行流;而parallelStream()是集合类的一个方法,用于直接创建一个并行流。 - 初始状态:
parallelStream()创建的流默认就是并行的,而parallel()需要在一个已有的串行流上调用才能改变其行为。 - 灵活性:如果你需要在同一个流的不同阶段之间切换串行和并行模式,那么
parallel()提供了更大的灵活性。你可以随时通过sequential()方法恢复到串行模式。
注意事项
- 并行不一定更快:并行流并不总是比串行流快。对于小型数据集或简单操作,启动并行任务的成本可能会超过并行化带来的性能提升。此外,并行流还可能导致额外的复杂性,例如非确定性的迭代顺序。
- 线程安全问题:当使用并行流时,确保共享资源的访问是线程安全的,以避免并发修改异常等问题。
总结
parallel() 和 parallelStream() 都是用于启用流的并行处理的功能,但它们的应用场景略有不同。parallel() 更适合那些已经拥有一个流对象并且想要动态地转换为并行流的情况;而 parallelStream() 则提供了更直接的方式,在一开始就创建一个并行流。选择哪种方式取决于你的具体需求和代码结构。
3.3.16 toArray转数组
同样方法
<A> A[] toArray(IntFunction<A[]> generator)
Author author = new Author(1L, "蒙多", 33, "一个从菜刀中明悟哲理的祖安人");
Author author2 = new Author(2L, "亚拉索", 15, "狂风也追逐不上他的思考速度");
Author author3 = new Author(3L, "易", 14, "是这个世界在限制他的思维");
Author author4 = new Author(3L, "易", 14, "是这个世界在限制他的思维");
Object[] array1 = Stream.of(author, author2, author3, author4).map(Author::getName).toArray();
System.out.println(Arrays.toString(array1));
Author[] array = Stream.of(author, author2, author3, author4).toArray(Author[]::new);
System.out.println(Arrays.toString(array));
2
3
4
5
6
7
8
# 3.3.16 count 计算流留个数
@Test
public void countTest(){
List<String> list = Arrays.asList("apple", "banana", "orange");
long count = list.stream().count();
System.out.println("List size: " + count);
}
2
3
4
5
6
# 3.3.17 anyMatch检查集合中是否存在满足条件的元素
@Test
public void anyMatchTest(){
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean anyMatch = numbers.stream().anyMatch(n -> n > 3);
System.out.println("Any element greater than 3: " + anyMatch);
}
2
3
4
5
6
# 3.3.18 检查集合中的所有元素是否都满足给定的条件
@Test
public void allMatchTest(){
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean allMatch = numbers.stream().allMatch(n -> n > 0);
System.out.println("All elements greater than 0: " + allMatch);
}
2
3
4
5
6
7
# 3.3.19 noneMatch 检查集合中是否没有元素满足给定的条件
@Test
public void noneMatchTest(){
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean noneMatch = numbers.stream().noneMatch(n -> n < 0);
System.out.println("No element is less than 0: " + noneMatch);
}
2
3
4
5
6
# 3.3.20 findFirst 返回流中的第一个元素
# 3.3.21 findAny 返回流中的任意一个元素
@Test
public void findTest(){
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstMatch = numbers.stream().findFirst();
System.out.println("First element: " + firstMatch.orElse(null));
Optional<Integer> anyMatch = numbers.stream().findAny();
System.out.println("Any element: " + anyMatch.orElse(null));
}
2
3
4
5
6
7
8
9
10
← 第05章:接口 第07章:异常、断言 →
