简介
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 容器,对于了解应该够了。