Stream是JDK8提供的一种新的资源管理方式.Stream
不存储值,而是通过管道的方式获取值,因此对流的操作不会修改底层的数据源.
一个完整的流由三部分组成
filter()
方法不会真的对该流进行过滤,而是会返回一个新流,该流在遍历时只会包含原流中通过过滤条件的值.forEach()
方法,会触发对流的遍历.流一旦执行了终止操作,可以认为该流被消耗了(consumed
),不能被再次使用.输出区间[3,8)
内的所有整数:
IntStream.of(3, 4, 5, 6, 7).forEach(System.out::println);
IntStream.range(3, 8).forEach(System.out::println);
IntStream.rangeClosed(3, 7).forEach(System.out::println);
对集合进行map-reduce
运算:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
list.stream().map(num -> num * 2).reduce(0, Integer::sum); // 42
描述性语言
而非命令式语言
.流操作描述的是结果,而不规定如何实现,由JDK做出优化.ParallelStream
,可以方便地执行多线程操作.使用of()
或stream()
方法从集合生成流
Stream<String> stream1 = Stream.of("hello", "world", "hello world");
String[] array = new String[]{"hello", "world", "hello world"};
Stream<String> stream2 = Stream.of(array);
Stream<String> stream3 = Arrays.stream(array);
List<String> list = Arrays.asList("hello", "world", "hello world");
Stream<String> stream4 = list.stream();
使用generate(Supplier supplier)
方法生成无限流:
generate()
方法通过调用supplier
对象的get()
方法生成一个无限流.
使用iterate(T seed, UnaryOperator
方法生成无限流:
iterate()
方法通过对种子seed
迭代调用function
对象的apply()
方法,生成一个无限流
Stream<Integer> series = Stream.iterate(1, num -> num * 2);
// series 为等比数列流{1, 2, 4, 8, ...}
使用concat(Stream stream1, Stream stream2)
拼接流:
Stream<String> stream1 = Stream.of("item1", "item2");
Stream<String> stream2 = Stream.of("item3", "item4", "item5");
Stream<String> sumStream = Stream.concat(stream1, stream2);
// sumStream 为流{"item1", "item2", "item3", "item4", "item5"}
Stream的所有中间操作都是惰性求值的,并不会立即进行运算,并返回一个新的Stream.
filter()
: 对流进行过滤filter(Predicate predicate)
方法调用predicate
对象的test()
方法对集合中元素进行过滤.
List<Integer> collect = IntStream.range(3, 8).filter(num -> num > 4).boxed().collect(Collectors.toList());
// 得到 {5, 6, 7}
map()
与flatMap()
:对流中的元素进行映射map(Function mapper)
是Stream最常用的中间操作,返回一个新的Stream,包含对原流中的每个元素应用mapper
中的apply()
方法得到的结果.
List<String> oldList = Arrays.asList("hello", "world", "hello world");
List<String> newList = oldList.stream().map(String::toUpperCase).collect(Collectors.toList());
// 得到 {"HELLO", "WORLD", "HELLO WORLD"}
flatMap(Function mapper)
可以将二维的Stream拍平成为一维的Stream,其中function
对象的apply()
方法返回的应是Stream
对象.
List<String> greetings = Arrays.asList("hello", "hi", "你好");
List<String> names = Arrays.asList("张三", "李四", "王五");
List<String> results = greetings.stream().flatMap(greeting -> names.stream().map(name -> greeting + " " + name)) // 将二维Stream拍平成一维Stream
.collect(Collectors.toList());
// 得到 {"hello 张三", "hello 李四", "hello 王五", "hi 张三", "hi 李四", "hi 王五", "你好 张三", "你好 李四", "你好 王五"}
skip()
,limit()
:对流进行分页skip(long n)
方法返回一个新Stream
对象,其中保存的是原Stream
对象中内容除去前n
个之后剩下的内容.若原Stream
对象中保存元素小于n
,则返回一个空的Stream
对象.
limit(long maxSize)
方法返回一个新Stream
对象,其中保存的是原Stream
对象中保存内容的前maxSize
个.
Stream<Integer> stream = Stream.iterate(1, num -> num * 2);
List<Integer> list = stream.skip(3).limit(5).collect(Collectors.toList());
// 得到 {8, 16, 32, 64, 128}
sorted()
:对流排序sorted()
和sorted(Comparator comparator)
方法返回一个新Stream
对象,其中保存的是原Stream
对象中内容排序后得到的结果.
List<String> unsortedList = Arrays.asList("element3", "element1", "element2");
List<String> sortedList = unsortedList.stream().sorted().collect(Collectors.toList());
// 得到 {"element1", "element2", "element3"}
distinct()
:对流中的元素进行去重distinct()
方法返回一个新Stream
对象,其保存的是原Stream
对象中的内容去重之后得到的结果.若原流有序,则去重过程是稳定的(重复元素保留第一次出现者);若原流是无序的,则去重操作不保证稳定性.
List<String> duplicateList = Arrays.asList("element1", "element2", "element3", "element1");
List<String> distinctList1 = duplicateList.stream().distinct().collect(Collectors.toList());
// 得到 {"element1", "element2", "element3"}
Stream所有的终止操作都是及早求值的,也就是说会立即进行运算.执行终止操作之后的流不能被复用.
reduce()
:将流中元素聚合为一个结果reduce()
方法将流中的所有元素聚合为一个结果,有以下三个重载的方法:
Optional
T reduce(T identity, BinaryOperator
该重载方法得到的结果与流中元素是同类型的,其两个参数意义如下:
identity
: 表示结果的初始值accumlator
: 其apply()
方法表示如何将流中的元素和结果相聚合,应满足结合律T reduce(T identity, BinaryOperator
方法的结果等价于以下代码块,但执行过程未必是顺序的.
T result = identity;
for (T element : thisStream)
result = accumulator.apply(result, element);
return result;
U reduce(U identity, BiFunction accumulator, BinaryOperator combiner)
该重载方法得到的结果与流中元素不是同类型的,同时又增加了一个combiner
参数.combiner
参数仅在并行流中起作用,表示如何将多个并行的流得到的结果汇总.
Stream<String> unparallelStream = Stream.of("item1", "item2", "item3", "item4");
Stream<String> parallelStream = Stream.of("item1", "item2", "item3", "item4").parallel();
System.out.println(unparallelStream.reduce("^",
String::concat,
// null)); // 非并行流,combiner参数不起作用,甚至可以设为null
(str1, str2) -> str1 + str2 + "$"));
// 得到 "^item1item2item3item4"
System.out.println(parallelStream.reduce("^",
String::concat,
(str1, str2) -> str1 + str2 + "$"));
// 得到 "^item1^item2$^item3^item4$$"
collect()
:将流中元素收集到容器中collect()
方法可以将流中的元素收集到容器中,是一种特化的reduce
操作.有两个重载方法:
其三个参数意义如下:
supplier
: 提供容器的生产者,在并行流中,该生产者的get()
方法会被反复调用,每次都生成一个全新的容器.accumulator
: 其accept()
方法表示如何将流中的元素存入容器.combiner
: 仅在并行流中有效,其accept()
方法表示如何将多个并行的流得到的结果汇总.下面两个例子演示collect()
方法的使用
// 将流中的元素收集到List中
List<String> List = stringStream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
// 将流中的元素收集到String中
String concat = stringStream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString();
Collector
类将supplier
,accumlator
,combiner
三个参数封装到一个对象中,其帮助类Collectors
提供很多方法获取常用的Collector
对象.
stream.collect(Collectors.toList()); // 将流收集为List
stream.collect(Collectors.toSet()); // 将流收集为Set
stream.collect(Collectors.toCollection(ArrayList::new)); // 将流收集为某种集合
stream.collect(Collectors.joining()); // 将字符串流拼接成字符串
max()
,min()
,average()
,count()
,findAny()
,findFirst()
:见名知义的终止操作上述终止操作返回的都是Optional
对象,其函数行为可以见名知义.
Collectors.groupingBy()
Collectors.groupingBy(Function classifier, Collector downstream)
可以对流进行分组查询.返回值为一个Map
,其key
为classifier
参数得到的结果,value
为对组内的元素使用downstream
收集得到的结果.两个参数的意义如下:
classfier
: 分类器,表示返回结果的key
的取值.downstream
: 收集器,对组内元素进行收集后作为返回参数的value
的取值.其默认取值为Collectors.toList()
,表示将分组结果收集到List
中.Student student1 = new Student("zhangsan", 100, 20);
Student student2 = new Student("lisi", 90, 20);
Student student3 = new Student("wangwu", 90, 30);
Student student4 = new Student("zhangsan", 80, 40);
List<Student> students = Arrays.asList(student1, student2, student3, student4);
Map<String, List<Student>> listMap = students.stream()
.collect(Collectors.groupingBy(Student::getName));
/* 得到
{ lisi=[Student{name='lisi', age=90, score=20}],
zhangsan=[Student{name='zhangsan', age=100, score=20}, Student{name='zhangsan', age=80, score=40}],
wangwu=[Student{name='wangwu', age=90, score=30}]} */
Map<String, Long> longMap = students.stream()
.collect(Collectors.groupingBy(Student::getName, Collectors.counting()));
// 得到 {lisi=1, zhangsan=2, wangwu=1}
Collectors.partitionBy()
分区查询是分组查询的一种,其分类条件应为一个bool值,只有true
和false
两种情况.
Student student1 = new Student("zhangsan", 100, 20);
Student student2 = new Student("lisi", 90, 20);
Student student3 = new Student("wangwu", 90, 30);
Student student4 = new Student("zhangsan", 80, 40);
List<Student> students = Arrays.asList(student1, student2, student3, student4);
Map<Boolean, List<Student>> listMap = students.stream()
.collect(Collectors.partitioningBy(student -> student.getAge() >= 90));
/* 得到
{ false=[Student{name='zhangsan', age=80, score=40}],
true=[Student{name='zhangsan', age=100, score=20}, Student{name='lisi', age=90, score=20}, Student{name='wangwu', age=90, score=30}]}
*/
Map<Boolean, Double> doubleMap = students.stream()
.collect(Collectors.partitioningBy(student -> student.getAge() >= 90, Collectors.averagingDouble(Student::getScore)));
// 得到 {false=40.0, true=23.333333333333332}
short-circuiting
)一些流操作是短路(short-circuiting
)的(例如limit()
,findFirst()
).对于这些操作,传入无限流,会返回有限流.这些短路操作使得对无限流进行操作和计算成为可能.
下面程序中,findFirst()
方法将流操作短路了:
OptionalInt firstLength = list.stream().mapToInt(str -> {
System.out.println("遍历到 " + str);
return str.length();
}).findFirst();
程序仅输出:
遍历到 hello
但短路操作不是总能发挥短路作用:
List<String> list = Arrays.asList("hello", "world", "helloworld");
OptionalInt firstLength = list.stream().mapToInt(str -> {
System.out.println("遍历到 " + str);
return str.length();
}).sorted().findFirst();
程序输出:
遍历到 hello
遍历到 world
遍历到 helloworld
parallelStream
)并行流(parallelStream
)可以使用Collection.parallelStream()
或BaseStream.parallel()
方法生成.
并行流的所有操作及其结果与顺序流是完全相同的,二者的唯一区别在于执行终止操作时,sequentialStream
顺序遍历所有元素,而parallelStream
开启多个线程访问每个元素,其底层是由fork-join
线程池实现的.
int sumOfWeights = widgets.parallelStream()
.filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();
可以使用BaseStream.isParallel()
方法判断当前流是否为并行流,使用BaseStream.sequential()
和BaseStream.parallel()
完成并行流和顺序流之间的转换.
Consumable
),关闭后不能再被使用与iterator
类似,Stream是消耗性的资源,只能被使用一次.对Stream进行任何终止操作都会导致流的关闭,使用关闭后的流会抛出IllegalStateException
异常.
IntStream intStream = IntStream.range(1, 11);
System.out.println(intStream.average()); // OptionalDouble[5.5]
System.out.println(intStream.sum()); // java.lang.IllegalStateException: stream has already been operated upon or closed
side-effect
)的操作对于绝大多数流的操作,都不应该执行有副作用(side-effect
)的操作:因为流操作不保证执行顺序;同时有可能带来线程安全问题.只有在少数流操作(如forEach()
和peek()
方法)中,才可以执行带有副作用的操作.
很多带有副作用的操作可以用归约(reduce
)操作来代替,例如下面代码在并行流中会带来线程安全问题:
ArrayList<String> results = new ArrayList<>();
stream.filter(s -> pattern.matcher(s).matches())
.forEach(s -> results.add(s)); // Unnecessary use of side-effects!
上述forEach()
操作会带来线程安全问题,可以用collect()
操作来代替
List<String>results = stream.filter(s -> pattern.matcher(s).matches())
.collect(Collectors.toList()); // No side-effects!
对无限流进行终止操作时要注意是否做出了有效的截断,否则会很危险:
// 没做出截断
IntStream.iterate(0, i -> (i + 1) % 2).distinct().forEach(System.out::println);
// 做出无效截断
IntStream.iterate(0, i -> (i + 1) % 2).distinct().limit(6).forEach(System.out::println);
// 做出有效截断
IntStream.iterate(0, i -> (i + 1) % 2).limit(6).distinct().forEach(System.out::println);