JAVA8 Stream流
简介
Stream
是一个 Collection
的增强工具,可以对集合进行各种操作,而且可以很方便的写出并发程序,学习之前需要了解一些函数,可以看 JAVA8 Lambda表达式。常见的获取方式就是 Collection.stream()
。
操作类型
操作类型分为两种
Intermediate
(中间操作):可以多次使用,因为返回一个Stream
。比如map(mapToInt, flatMap)
、filter
、sorted
、limit
、skip
。Terminal
(结束操作):使用后就会结束。比如forEach
、sort
、collect
、min
、count
、findFirst
、anyMatch
。
中间操作都是惰性的,也就是延迟的,所以会产生副作用,关于副作用会在之后的章节详细说明。
创建
Stream
的类型有三种 IntStream
,DoubleStream
和 LongStream
,当然也可以使用 Stream<T>
。
除了直接创建,还能通过 Collection
接口的 stream()
和 parallelStream()
创建,其中 parallelStream()
创建的流是并发、线程不安全且操作无序的,虽然它是并发的,但仍有可能在某些操作下变回串行的,例如 forEachOrdered
,此外还需要保证数据源是线程安全的。
下面的代码展示了流的创建,第二行代码是取流中前三个元素在控制台输出,如果开启最后一行注释将会出现 java.lang.IllegalStateException
的异常,详细错误信息为 stream has already been operated upon or closed
,所以创建的 Stream
仅能使用一次。
IntStream intStream = IntStream.of(1, 2, 3, 4);
intStream.limit(3).forEach(System.out::println);
//intStream.limit(1).forEach(System.out::println);
使用
JAVA8 中 Stream
接口中的操作有 filter
、map
、mapToInt
、mapToLong
、mapToDouble
、flatMap
、flatMapToInt
、flatMapToLong
、flatMapToDouble
、distinct
、sorted
、peek
、limit
、skip
、forEach
、forEachOrdered
、toArray
、reduce
、collect
、min
、max
、count
、anyMatch
、allMatch
、noneMatch
、findFirst
、findAny
,方法很多,没见过的看注释、参数和返回值就懂了。
示例使用 List
接口 的 stream
方法创建 Stream
,下面是示例所需要的数据。
//Get、Set、构造方法浪费空间,不粘贴了
public class Person {
//id
private Integer id;
//名字
private String name;
//年龄
private Integer age;
//组织
private String organization;
}
//数据初始化
List<Person> personList = new ArrayList<>();
personList.add(new Person(1, "灰原哀", 7, "帝丹小学"));
personList.add(new Person(2, "江户川柯南", 7, "帝丹小学"));
personList.add(new Person(3, "宫野明美", 24, "黑衣组织"));
personList.add(new Person(4, "赤井秀一", 27, "FBI"));
personList.add(new Person(5, "贝尔摩德", 29, "黑衣组织"));
下面是对 Stream
操作的两个简单示例。
第一个将 personList
中 前 4 个、年龄为 7 的人提取出来,以组织和姓名为 key
和 value
组装成一个新的 Map
,其中 Collectors.toMap
方法最后一个参数用于解决 key
冲突。
第二个获取 personList
中名字为安室透的第一个人。
Map<String, String> map = personList.stream().limit(4) //中间操作
.filter(person -> Objects.equals(person.getAge(), 7)) //中间操作
.collect(Collectors.toMap( // Collectors 类是随 Stream 一起引入的,即方便又好看,作用之一是收集元素到集合
Person::getOrganization, // map 的 key
Person::getName, // map 的 value
(old, now) -> old)); // 发生冲突的解决办法
Optional<Person> optional = personList.stream()
.filter(person->Objects.equals(person.getName(), "安室透")) //中间操作
.findFirst();
第二个方法返回值类型是 Optional<Person>
,Optional
是 JAVA8 中引入的一个容器,可以使用 get()
获取容器中的值,但 optional
中并没有值,所以会抛出 java.util.NoSuchElementException
,为了解决这个问题可以使用 orElse()
,当容器中值为空时返回设定的默认值,除了 orElse
还有 orElseGet
和 orElseThrow
。比如下面的这段代码返回了叫安室透的人。
// optional 在上一段代码中产生的对象
Person person = optional.orElse(new Person(7, "安室透", 29, "日本公安"));
副作用
对流的中间操作会产生副作用,结果是抛异常和数据的错误,它的来源有“干扰”和“有状态的 Lambda”。
- “干扰”就是在中间操作时修改了流的数据源。比如在
forEach(Consumer<? super T> action)
中应该是消费数据,却给数据源添加了一个数据,结果是抛出了java.util.ConcurrentModificationException
异常。 - “有状态的 Lambda”,当后面操作产生的结果会被前面的操作影响时,前面操作的
Lambda
就被称作是有状态的。比如 有状态的 Lambda 的例子,例子中使用parallelStream()
并发添加数据到parallelStorage
中,结果就是parallelStorage
中的数据顺序不可预测,因此称e -> { parallelStorage.add(e); return e; }
是有状态的Lambda
。
总结
本文没有深入介绍它的概念,只是简单介绍了 Stream
的使用和 Optional
容器,对于了解应该够了。