Java 将流转换为集合,流处理完成之后,用户希望将流转换为 List
、Set
或其他线性集合。使用 Collectors
工具类定义的 toList
、toSet
或 toCollection
方法。Java 8 一般通过称为流水线(pipeline)的中间操作(intermediate operation)来传递流元素,并在达到终止操作(terminal operation)后结束。
Java 将流转换为集合 问题描述
流处理完成之后,用户希望将流转换为 List
、Set
或其他线性集合。
Java 将流转换为集合 解决方案
使用 Collectors
工具类定义的 toList
、toSet
或 toCollection
方法。
Java 将流转换为集合 具体实例
Java 8 一般通过称为流水线(pipeline)的中间操作(intermediate operation)来传递流元素,并在达到终止操作(terminal operation)后结束。Stream
接口定义的 collect
方法就是一种终止操作,用于将流转换为集合。
collect
方法有两种重载形式,如例 4-7 所示。
例 4-7
Stream.collect
方法
<R,A> R collect(Collector<? super T,A,R> collector)
<R> R collect(Supplier<R> supplier,
BiConsumer<R,? super T> accumulator,
BiConsumer<R,R> combiner)
本范例采用第一种形式,它传入 Collector
作为参数。收集器执行“可变归约操作”(mutable reduction operation),将元素累加至结果容器(result container),此时结果是一个集合。
由于 java.util.stream.Collector
属于接口,无法被实例化。Collector
接口包含一个称为 of
的静态方法,它用于生成 Collector
,但通常有更好(或至少更简单)的方式可以实现这一点。
Java 8 API 经常使用静态方法
of
作为工厂方法。
Collectors
类定义的静态方法将用于生成 Collector
实例,它作为 Stream.collect
方法的参数来填充集合。
例 4-82 显示了一个创建 List
的简单示例。
2本范例中出现的人名来自《神秘兵团》,这是 20 世纪 90 年代最为人所忽视的电影之一。(Furious 说:“Lance Hunt 就是神奇队长。”Shoveler 答道:“Lance Hunt 戴眼镜,但神奇队长不戴。”Furious 说:“他变身时会把眼镜摘下来。”Shoveler 答道:“怎么可能!他根本看不见!”)
例 4-8 创建
List
List<String> superHeroes =
Stream.of("Mr. Furious", "The Blue Raja", "The Shoveler",
"The Bowler", "Invisible Boy", "The Spleen", "The Sphinx")
.collect(Collectors.toList());
上述方法创建了一个 ArrayList
类,并采用给定的流元素进行填充。创建 Set
也很简单,如例 4-9 所示。
例 4-9 创建
Set
Set<String> villains =
Stream.of("Casanova Frankenstein", "The Disco Boys",
"The Not-So-Goodie Mob", "The Suits", "The Suzies",
"The Furriers", "The Furriers") ➊
.collect(Collectors.toSet());
}
➊ 重复的人名(将在转换为 Set
时删除)
上述方法创建了一个 HashSet
类的实例并加以填充,并忽略任何重复的人名。
例 4-8 和例 4-9 均使用默认的数据结构:对于 List
是 ArrayList
,对于 Set
是 HashSet
。如果希望指定某种特定的数据结构,则应使用 Collectors.toCollection
方法,它传入 Supplier
作为参数。示例代码如例 4-10 所示。
例 4-10 创建关联列表(linked list)
List<String> actors =
Stream.of("Hank Azaria", "Janeane Garofalo", "William H. Macy",
"Paul Reubens", "Ben Stiller", "Kel Mitchell", "Wes Studi")
.collect(Collectors.toCollection(LinkedList::new));
}
由于 toCollection
方法的参数是集合 Supplier
,本例提供 LinkedList
类的构造函数引用。collect
方法将 LinkedList
实例化,并采用给定的姓名加以填充。
Stream
接口还定义了一个用于创建对象数组的方法 toArray
,它有两种重载形式:
Object[] toArray();
<A> A[] toArray(IntFunction<A[]> generator);
第一种形式返回一个包含流元素的数组,但未指定类型。第二种形式传入一个函数并生成所需类型的新数组,数组的长度与流相同,很容易与数组构造函数引用(array constructor reference)一起使用,如例 4-11 所示。
例 4-11 创建
Array
String[] wannabes =
Stream.of("The Waffler", "Reverse Psychologist", "PMS Avenger")
.toArray(String[]::new); ➊
}
➊ 数组构造函数引用作为 Supplier
返回数组具有指定的类型,其长度与流中的元素数量匹配。
为了将流转换为 Map
,Collectors.toMap
方法需要传入两个 Function
实例,分别用于键和值。
我们以一个包装 name
和 role
的 Actor
POJO 为例进行讨论。假设一部给定电影中存在 Actor
实例的 Set
,例 4-12 根据这些实例创建了一个 Map
。
例 4-12 创建
Map
Set<Actor> actors = mysteryMen.getActors();
Map<String, String> actorMap = actors.stream()
.collect(Collectors.toMap(Actor::getName, Actor::getRole)); ➊
actorMap.forEach((key,value) ->
System.out.printf("%s played %s%n", key, value));
➊ 生成键和值所用的函数
输出如下:
Janeane Garofalo played The Bowler
Greg Kinnear played Captain Amazing
William H. Macy played The Shoveler
Paul Reubens played The Spleen
Wes Studi played The Sphinx
Kel Mitchell played Invisible Boy
Geoffrey Rush played Casanova Frankenstein
Ben Stiller played Mr. Furious
Hank Azaria played The Blue Raja
利用 Collectors.toConcurrentMap
方法对本例稍作修改,就能创建 ConcurrentMap
。