一、什么是Stream?
- Java Stream函数式编程接口最初是在Java 8中引入的,并且与lambda一起成为Java开发的里程碑式的功能特性,它极大的方便了开放人员处理集合类数据的效率。从笔者之前看过的调查文章显示,绝大部分的开发者使用的JDK版本是java 8,其中Java Stream和lambda功不可没。
- Java Stream就是一个数据流经的管道,并且在管道中对数据进行操作,然后流入下一个管道。
- 管道的功能包括:Filter(过滤)、Map(映射)、sort(排序)等,集合数据通过Java Stream管道处理之后,转化为另一组集合或数据输出。
- Stream流可以分两种
- 顺序流 : 按照顺序对集合中的元素进行处理
- 并行流 : 使用多线程同时对集合中多个元素进行处理 在使用并行流的时候就要注意线程安全的问题
- 元素流在管道中经过中间操作(intermediate operation)的处理,最后由终端操作 (terminal operation) 得到前面处理的结果。
- 中间操作(intermediate operation): 中间操作会产生另一个流 ,( 流是一种惰性操作,所有对源数据的计算只在终止操作被初始化的时候才会执行), 而且中间操作还分无状态操作和有状态操作两种
- 无状态操作 : 在处理流中的元素时,会对当前的元素进行单独处理。 (例如:过滤操作)
- 有状态操作 : 某个元素的处理可能依赖于其他元素.( 例如:查找最小值,最大值,和排序 )
- 无状态操作 : 在处理流中的元素时,会对当前的元素进行单独处理。 (例如:过滤操作)
- 终止操作 (terminal operation):消费 Stream 流,并且会产生一个结果 . 如果一个 Stream 流被消费过了,那它就不能被重用的。
- 中间操作(intermediate operation): 中间操作会产生另一个流 ,( 流是一种惰性操作,所有对源数据的计算只在终止操作被初始化的时候才会执行), 而且中间操作还分无状态操作和有状态操作两种
中间操作(intermediate operation)
包括 map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered等.
终止操作 (terminal operation)
包括 forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator等
二、转换方式
数组
String[] array = {"Monkey", "Lion", "Giraffe", "Lemur"};
Stream nameStrs2 = Stream.of(array);
Stream nameStrs3 = Stream.of("Monkey", "Lion", "Giraffe", "Lemur");
集合
List list = Arrays.asList("Monkey", "Lion", "Giraffe", "Lemur");
Stream streamFromList = list.stream();
Set set = new HashSet<>(list);
Stream streamFromSet = set.stream();
文本文件
通过Files.lines方法将文本文件转换为管道流,下图中的Paths.get()方法作用就是获取文件,是Java NIO的API
也就是说:我们可以很方便的使用Java Stream加载文本文件,然后逐行的对文件内容进行处理
Stream lines = Files.lines(Paths.get("file.txt"));
三、简单使用
List names = Arrays.asList("Monkey", "Lion", "Giraffe","Lemur","Lion","Liu","LiLi");
List list = names.stream()
.filter(s -> s.startsWith("L"))
.map(String::toUpperCase)
.sorted(Comparator.reverseOrder())
.distinct()
.skip(1)
.limit(2)
.collect(toList());
System.out.println(list);
结果:[LION, LILI]
- 首先,我们使用Stream()函数,将一个List转换为管道流
- 调用filter函数过滤数组元素,过滤方法使用lambda表达式,以L开头的元素返回true被保留,其他的List元素被过滤掉
- 然后调用Map函数对管道流中每个元素进行处理,字母全部转换为大写
- 然后调用sort函数,对管道流中数据进行排序(默认升序)
- 然后调用distinct函数,对管道流中数据进行去重
- 然后调用skip函数,跳过第一个元素
- 然后调用limit函数,取前两个元素
- 最后调用collect函数toList,将管道流转换为List返回
匹配
- anyMatch(),只要有一个元素匹配传入的条件,就返回 true。
- allMatch(),只有有一个元素不匹配传入的条件,就返回 false;如果全部匹配,则返回 true。
- noneMatch(),只要有一个元素匹配传入的条件,就返回 false;如果全部匹配,则返回 true。
List list = new ArrayList<>();
list.add("周杰伦");
list.add("王力宏");
list.add("陶喆");
list.add("林俊杰");
boolean anyMatchFlag = list.stream().anyMatch(element -> element.contains("王"));//true
boolean allMatchFlag = list.stream().allMatch(element -> element.length() > 2);//false
boolean noneMatchFlag = list.stream().noneMatch(element -> element.endsWith("沉"));//true
组合
reduce()
方法的主要作用是把 Stream 中的元素组合起来,它有两种用法:
Optional reduce(BinaryOperator accumulator)
没有起始值,只有一个参数,就是运算规则,此时返回 Optional。
T reduce(T identity, BinaryOperator accumulator)
有起始值,有运算规则,两个参数,此时返回的类型和起始值类型一致。
Integer[] ints = {0, 1, 2, 3};
List list = Arrays.asList(ints);
Optional optional = list.stream().reduce((a, b) -> a + b);
Optional optional1 = list.stream().reduce(Integer::sum);
System.out.println(optional.orElse(0));//6
System.out.println(optional1.orElse(0));//6
int reduce = list.stream().reduce(6, (a, b) -> a + b);//12
int reduce1 = list.stream().reduce(6, Integer::sum);//12
Collect
Collect是一个非常有用的终端操作,以流的元素转变成一种不同的结果,例如一个List,Set或Map。Collect接受Collector包含四种不同操作的操作:供应商,累加器,组合器和修整器。这听起来非常复杂,但是Java 8通过Collectors类支持各种内置收集器。因此,对于最常见的操作,您不必自己实现收集器。
将所有人连接成一个字符串
List persons =
Arrays.asList(
new Person("Max", 18),
new Person("Peter", 23),
new Person("Pamela", 23),
new Person("David", 12));
String phrase = persons
.stream()
.filter(p -> p.age >= 18)
.map(p -> p.name)
.collect(Collectors.joining(" and ", "In Germany ", " are of legal age."));
System.out.println(phrase);//In Germany Max and Peter and Pamela are of legal age.
Collectors 还包括
summarizingInt 汇总收集器将返回一个特殊的内置摘要统计信息对象
averagingInt 平均值
groupingBy 分组 等
映射
ToMap 指定如何映射键和值。请记住,映射的键必须是唯一的,否则抛出一个IllegalStateException。您可以选择将合并函数作为附加参数传递以绕过异常:
Map map = persons
.stream()
.collect(Collectors.toMap(
p -> p.age,
p -> p.name,
(name1, name2) -> name1 + ";" + name2));
System.out.println(map);//{18=Max, 23=Peter;Pamela, 12=David}
构建Collect
希望将流的所有人转换为单个字符串,该字符串由|管道字符分隔的大写字母组成。为了实现这一目标,我们创建了一个新的Collector.of()
。
Collector personNameCollector =
Collector.of(
() -> new StringJoiner(" | "), // supplier
(j, p) -> j.add(p.name.toUpperCase()), // accumulator
StringJoiner::merge, // combiner
StringJoiner::toString); // finisher
String names = persons
.stream()
.collect(personNameCollector);
System.out.println(names);// MAX | PETER | PAMELA | DAVID
供应商最初使用适当的分隔符构造这样的StringJoiner。累加器用于将每个人的大写名称添加到StringJoiner。组合器知道如何将两个StringJoiners合并为一个。在最后一步中,整理器从StringJoiner构造所需的String。
FlatMap
Map有点受限,因为每个对象只能映射到另一个对象。但是如果我们想要将一个对象转换为多个其他对象或者根本不转换它们呢?这是flatMap救援的地方。
FlatMap将流的每个元素转换为其他对象的流。因此,每个对象将被转换为由流支持的零个,一个或多个其他对象。然后将这些流的内容放入返回flatMap操作流中。
class Foo {
String name;
List bars = new ArrayList<>();
Foo(String name) {
this.name = name;
}
}
class Bar {
String name;
Bar(String name) {
this.name = name;
}
}
利用有关流的知识来实例化几个对象
List foos = new ArrayList<>();
// create foos
IntStream
.range(1, 4)
.forEach(i -> foos.add(new Foo("Foo" + i)));
// create bars
foos.forEach(f ->
IntStream
.range(1, 4)
.forEach(i -> f.bars.add(new Bar("Bar" + i + " <- " + f.name))));
现在我们列出了三个foos,每个foos由三个数据组成。
FlatMap接受一个必须返回对象流的函数。所以为了解决每个foo的bar对象,我们只传递相应的函数:
foos.stream()
.flatMap(f -> f.bars.stream())
.forEach(b -> System.out.println(b.name));
输出
Bar1 <- Foo1
Bar2 <- Foo1
Bar3 <- Foo1
Bar1 <- Foo2
Bar2 <- Foo2
Bar3 <- Foo2
Bar1 <- Foo3
Bar2 <- Foo3
Bar3 <- Foo3
成功将三个foo对象的流转换为九个bar对象的流。
上面的代码示例可以简化为流操作的单个管道:
IntStream.range(1, 4)
.mapToObj(i -> new Foo("Foo" + i))
.peek(f -> IntStream.range(1, 4)
.mapToObj(i -> new Bar("Bar" + i + " <- " f.name))
.forEach(f.bars::add))
.flatMap(f -> f.bars.stream())
.forEach(b -> System.out.println(b.name));
FlatMap也可用于Java 8中引入的Optional类。Optionals flatMap操作返回另一种类型的可选对象。因此,它可以用来防止令人讨厌的null检查。
这样一个高度分层的结构:
class Outer {
Nested nested;
}
class Nested {
Inner inner;
}
class Inner {
String foo;
}
为了解析foo外部实例的内部字符串,您必须添加多个空值检查以防止可能的NullPointerExceptions:
Outer outer = new Outer();
if (outer != null && outer.nested != null && outer.nested.inner != null) {
System.out.println(outer.nested.inner.foo);
}
利用选项flatMap操作可以获得相同的行为:
Optional.of(new Outer())
.flatMap(o -> Optional.ofNullable(o.nested))
.flatMap(n -> Optional.ofNullable(n.inner))
.flatMap(i -> Optional.ofNullable(i.foo))
.ifPresent(System.out::println);