IntStream.of(1, 2, 3, 4, 5, 6).forEach(System.out::println);
这个直接使用集合的forEach也是可以的,使用stream除了有原始类型版本减少拆包装包以外,还可以实现并行化:
IntStream.of(1, 2, 3, 4, 5, 6).parallel().forEach(System.out::println);
System.out.println();
IntStream.of(1, 2, 3, 4, 5, 6).parallel().forEach(System.out::println);
输出:
4
5
1
2
6
3
4
1
6
2
3
5
可见,使用了多线程并行处理stream,每个元素被调用的顺序是不确定的。stream还提供了一个顺序的forEachOrdered,对于串行stream来说,这个方法和forEach相同,而对于并行
stream则会强制按顺序执行,如:
IntStream.of(1, 2, 3, 4, 5, 6).parallel().forEachOrdered(System.out::println);
输出:
1
2
3
4
5
6
同样的道理,findFirst和findAny对于串行steam来说是一样的,而对于并行stream则不同:
System.out.println(Stream.of(1, 2, 3, 4, 5, 6).findFirst());
System.out.println(Stream.of(1, 2, 3, 4, 5, 6).findAny());
输出
Optional[1]
Optional[1]
再试下并行stream:
System.out.println(Stream.of(1, 2, 3, 4, 5, 6).parallel().findFirst());
System.out.println(Stream.of(1, 2, 3, 4, 5, 6).parallel().findAny());
System.out.println(Stream.of(1, 2, 3, 4, 5, 6).parallel().findAny());
System.out.println(Stream.of(1, 2, 3, 4, 5, 6).parallel().findAny());
输出
Optional[1]
Optional[4]
Optional[4]
Optional[2]
findFirst和findAny返回一个Optional<T>(对于原始类型stream,是返回OptionalInt,
OptionalLong,OptionalDouble), 因为对于空的stream,这两个方法需要返回一个找不到元素的语义(有别于null),得到Optional后可以用isPresent判断元素是否存 在,用get方法获得真正的值(如果元素不存在,调用get方法会抛出NoSuchElementException)。不过这个Optional有些坑 爹:
Stream.of(null,1,2,3).findFirst();
执行结果是抛出java.lang.NullPointerException,原因是Optional构造时不允许传入null,这里我没明白既然Optional用来区别null和present的语义,为什么又不能传入null。
findFirst和findAny和filter方法配合可以实现获得满足条件的第一个元素(或者任意一个元素),例如我们要找第一个负数,可以这么写:
stream.filter(x -> x < 0).findFirst();
接着测试Stream的惰性加载特性:
List<Integer> list = new AbstractList<Integer>()
{
@Override
public Integer get(int index)
{
System.out.println(index);
return index;
}
@Override
public int size()
{
return 3;
}
};
Stream<Integer> stream = list.stream();
System.out.println("get stream");
stream.forEach(System.out::println);
输出:
get stream
0
0
1
1
2
2
可见构造stream的时候 并不会访问List中的元素,等调用消费方法的时候,真正需要使用到某个元素再去访问。与iterator不同的是,构造iterator之后,如果 list被修改,再使用stream,并不会抛出ConcurrentModificationException,例如:
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
Stream<Integer> stream = list.stream();
list.add(3);
stream.forEach(System.out::println);
输出:
1
2
3
同理,测试映射方法的惰性加载特性:
List<Integer> list = new AbstractList<Integer>()
{
@Override
public Integer get(int index)
{
System.out.println(index);
return index;
}
@Override
public int size()
{
return 3;
}
};
Stream<Integer> stream = list.stream().map(x -> {
System.out.println(x);
return x * 2;
});
System.out.println("get stream");
stream.forEach(x -> {
System.out.println(x);
System.out.println();
});
输出:
get stream
0
0
0
1
1
2
2
2
4
对于不需要读到的元素,则连加载都不会加载:
List<Integer> list = new AbstractList<Integer>()
{
@Override
public Integer get(int index)
{
System.out.println(index);
return index;
}
@Override
public int size()
{
return 3;
}
};
Stream<Integer> stream = list.stream().limit(1);
System.out.println("get stream");
stream.forEach(System.out::println);
输出:
get stream
0
0
可见,只会去加载第一个元素。
也可以用peek方法来监控stream中元素的读取:
Stream.of(1, 2, 3, 4).peek(x -> System.out.print(x + " ")).map(x -> x * 2)
.forEach(System.out::println);
输出:
1 2
2 4
3 6
4 8
对于sorted方法,情况和其它映射方法略有不同:
Stream<Integer> stream = Stream.of(3, 1, 2, 4)
.peek(System.out::println).sorted();
System.out.println("get sorted stream");
stream.forEach(x -> System.out.println(" " + x));
输出:
get sorted stream
3
1
2
4
1
2
3
4
从输出结果可以看出,虽然是开始消费sorted stream的时候才会去访问原stream中的元素,不过在消费一开始的时候,所有元素就被遍历一遍了,这是因为只有把所有元素取出来才能排序,会消耗一个O(n)的额外空间。
测试Stream的一次性特性:
IntStream stream = IntStream.of(1, 2, 3, 4 - 1, 5);
System.out.println(stream.sum());
System.out.println(stream.anyMatch(x -> x < 0));
调用anyMatch的时候抛出异常:
java.lang.IllegalStateException: stream has already been operated upon or closed
调用映射方法后,原来的stream不再可用,如:
IntStream stream = IntStream.of(1, 2, 3, 4 - 1, 5);
stream.limit(1);
System.out.println(stream.sum());
抛出异常:
java.lang.IllegalStateException: stream has already been operated upon or closed
源stream关闭后,用映射方法构造出来的stream也不再可用。
IntStream stream = IntStream.of(1, 2, 3, 4 - 1, 5);
IntStream limit = stream.limit(1);
stream.close();
System.out.println(limit.sum());
抛出异常:java.lang.IllegalStateException: source already consumed or closed
当然,一般我们使用stream的时候都是链式调用,不会犯上面的错误。
一个stream被close后,相关的stream都会被close
Stream.of(1, 2, 3, 4, 5, 6).onClose(() -> System.out.println("close1"))
.limit(1).onClose(() -> System.out.println("close2")).close();
输出:
close1
close2
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
stream.onClose(() -> System.out.println("close1")).limit(1)
.onClose(() -> System.out.println("close2"));
stream.close();
输出:
close1
close2