1. 前言
Lambda 是JAVA8中最为重要的改变之一, 而Stream API(java.util.stream.*)则是另外一个重要的改变.Stream是JAVA8中处理集合的一个抽象的概念,通过Steam来处理集合中的数据,被处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等,就像使用Sql执行数据库数据处理一样简单,Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
2. 什么是Stream.
Stream(流)是一个来自数据源(Set,List等)的元素队列并支持聚合操作. 集合主要是为了存储其中的数据, 而Steam流 则是为了去计算.
需要注意:
- Stream并不会存储对象.
- Stream也不会改变数据源,Srream的中间操作(filter,map等操作)都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。
- Stream 是通过内部迭代的方式,无需程序员像在操作Collection的时候使用for循环或者迭代器手动去遍历.
3. Stream的操作步骤 以及对应API
Stream操作步骤分为三步,
- 创建Stream
对一个数据源获取其流. - 中间操作
对数据源数据进行相应的处理(filter,map,sorted等). - 终止操作
执行中间操作,并产生结果.
3.1 Stream 创建操作
Collection,Arrays 在JDK1.8 中均存在Stream()方法. (PS: JDK1.8后接口中default修饰的方法可以有方法体)
- stream() − 为集合创建串行流。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream() //创建串行流
.forEach(System.out::println); // 遍历结果
//result: 1,2,3,4,5
- parallelStream() − 为集合创建并行流。
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6);
integers.parallelStream() //parallelStream提供了流的并行处理,它是Stream的另一重要特性,其底层使用Fork/Join框架实现。
.forEach(num ->System.out.println(Thread.currentThread().getName()+">>>>>"+num));
//result :
// main>>>>>4
// main>>>>>6
// main>>>>>5
// ForkJoinPool.commonPool-worker-9>>>>>1
// ForkJoinPool.commonPool-worker-9>>>>>3
// main>>>>>2
3.2 中间操作
中间操作分为这三类:
-
映射
-
筛选与切片
-
排序
3.2.1 映射
map——接收 Lambda , 将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
//要求map(Function)中的Function 是传入T 返回R型的
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
flatMap——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流.
//要求flatMap(Function) 中的Function 是传入T 返回Stream<R>的
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
接下来进行具体的操作
//map 操作
List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
strList.stream()
.map(String::toUpperCase) // 使用map 将每个小写字母变为大写
.forEach(System.out::println);;
// result:: AAA,BBB,CCC,DDD,EEE
下面我们新建一个 filterCharacter 函数,将一个字符串中的每个字符加入到List中.
public static Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for (Character ch : str.toCharArray()) {
list.add(ch);
}
return list.stream(); //注意返回的是 Stream<Stream<Character>>
}
Stream<Stream<Character>> stream2 = strList.stream()
.map(TestStreamAPI1::filterCharacter); // map 使用该函数返回Stream<Stream<Character>> ! ,外面多包了一层Stream
//因此foreach 遍历 也需要两层
stream2.forEach((sm) -> {
sm.forEach(System.out::println);
});
然后使用flatMap 来操作
Stream<Character> stream3 = strList.stream()
.flatMap(TestStreamAPI1::filterCharacter); //返回的就是我们Function 中的返回值
stream3.forEach(System.out::println);
由上面我们可以得出map会将所有的返回值再重新放入一个Stream 中,所以当我的函数返回值是Stream<R>时,由map得到的返回值是Stream<Stream<R>>.
但是flatMap 不同,由于他的function要求返回的是Stream<R>,他会将所有的R 全部拿出来,在放入到一个Steam 之中.
3.2.2 筛选与切片
filter——接受Predicate 断言型 lambda,从流中排除某些元素
// 断言true 则保留, false 则剔除
Stream<T> filter(Predicate<? super T> predicate);
distinct——通过流所生成元素的hashCOde()和equals()去除重复元素
//一定要判断为同一对象才能去除
Stream<T> distinct();
limit —— 截断流,使元素不超过给定的数量
skip —— 返回一个扔掉了前n个元素的流, 若流中元素不足n个,则返回空流,与limit 互补.
@Test
public void test1(){
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9,1,1,1,1);
Stream<Integer> limit = integers.stream()
.filter(x -> x < 5) //筛选出小于5的
.distinct() //去重,会根据hashcode去重
.limit(3); //只返回前三条
limit.forEach(System.out::println);
}
//result: 1,2,3
@Test
public void test2(){
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9,1,1,1,1);
Stream<Integer> limit = integers.stream()
.filter(x -> x < 5)
.distinct()
.skip(3); // 跳过前三条
limit.forEach(System.out::println);
}
//result : 4
3.2.3 排序
sorted —— 产生一个新流,按照自然排序.
sorted(Comparator comp) —— 产生一个新流,按照我们自定义的比较器排序
@Test
public void test3(){
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9,1,1,1,1);
Stream<Integer> sorted = integers.stream()
.sorted();
sorted.forEach(x -> System.out.print(x+ ","));
}
//reult : 1,1,1,1,1,2,3,4,5,6,7,8,9,
//定制排序,倒叙排数字
@Test
public void test4(){
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9,1,1,1,1);
Stream<Integer> sorted = integers.stream()
.sorted((x,y) -> -Integer.compare(x,y)); //注意 Integer.compare前面的 - 号
sorted.forEach(x -> System.out.print(x+ ","));
}
//reult : 9,8,7,6,5,4,3,2,1,1,1,1,1,
3.3 终止操作
Stream的终止操作会从流中生成结果,其结果不会再是任何流,而是List,Integer,甚至是void 等.主要分为一下三类:
-
查找与匹配
-
归约
-
收集
3.3.1 查找与匹配操作
其中流使用了终止操作之后就不能再操作流了.
@Test
public void test8() {
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6);
boolean bl0 = integers.stream()
.allMatch(x -> x > 5);
System.out.println("allMatch——检查是否匹配所有元素: " + bl0);
boolean bl1 = integers.stream()
.anyMatch(x -> x > 5);
System.out.println("anyMatch——检查是否至少匹配一个元素: " + bl1);
boolean bl2 = integers.stream()
.noneMatch(x -> x > 5);
System.out.println("noneMatch——检查是否没有匹配的元素: " + bl2);
Optional<Integer> first = integers.stream()
.filter(x -> x > 5)
.findFirst(); //findFirst 返回Optional 对象, 因为值有可能为空
System.out.println("findFirst——返回第一个元素:" + first.get());
long count = integers.stream()
.filter(x -> x > 5)
.count();
System.out.println("count——返回流中元素的总个数:" + count);
Optional<Integer> max = integers.stream()
.max(Integer::compareTo); //制定compare方式 返回option
System.out.println("max——返回流中最大值:" + max);
Optional<Integer> min = integers.stream()
.min(Integer::compareTo);
System.out.println("min——返回流中最小值: " + min);
}
// allMatch——检查是否匹配所有元素: false
// anyMatch——检查是否至少匹配一个元素: true
// noneMatch——检查是否没有匹配的元素: false
// findFirst——返回第一个元素:6
// count——返回流中元素的总个数:1
// max——返回流中最大值:Optional[6]
// min——返回流中最小值: Optional[1]
3.3.2 归约操作
归约
reduce(T identity, BinaryOperator) / reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。
@Test
public void test1(){
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream()
.reduce(0, (x, y) -> x + y); // 这里是将初始值设置为0 , x+y的得到的值赋值给x , y是每次从List中遍历出来的值.
System.out.println(sum);
}
//result: 55
//reduce -map 相结合实现 计数功能
@Test
public void test_ReduceMap(){
//查询大于数组中大于6的值得个数
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Optional<Integer> sum = list.stream()
.map((i) -> {
if(i > 6)
return 1;
else
return 0;
}).reduce(Integer::sum);
System.out.println(sum.get());
}
3.3.3 收集
collect——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法,通常用于转化为List Set等,也可以用来做分组等操作.
//在Test 中创建一个苹果list, 有name, weight,price 三个属性
List<Apple> apples = Arrays.asList(
new Apple("红苹果", 3, 33.33),
new Apple("黄苹果", 4, 44.44),
new Apple("绿苹果", 5, 55.55),
new Apple("毒苹果", 6, 66.66)
);
/**
* 转化为集合类操作
*/
@Test
public void test5(){
List<String> list = apples.stream()
.map(Apple::getName)
.collect(Collectors.toList());
list.forEach(System.out::println);
System.out.println("----------------------------------");
Set<String> set = apples.stream()
.map(Apple::getName)
.collect(Collectors.toSet());
set.forEach(System.out::println);
System.out.println("----------------------------------");
HashSet<String> hs = apples.stream()
.map(Apple::getName)
.collect(Collectors.toCollection(HashSet::new)); //可以指定为任意Collection 类
hs.forEach(System.out::println);
}
//result:
// 红苹果
// 黄苹果
// 绿苹果
// 毒苹果
// ----------------------------------
// 毒苹果
// 黄苹果
// 红苹果
// 绿苹果
// ----------------------------------
// 毒苹果
// 黄苹果
// 红苹果
// 绿苹果
gourpingBy方法对苹果使用苹果名字进行分组
@Test
public void testGroupBy(){
Map<String, List<Apple>> map = apples.stream()
.collect(Collectors.groupingBy(Apple::getName));
System.out.println(map);
}
//result: {毒苹果=[Apple(name=毒苹果, weight=6, price=66.66)], 黄苹果=[Apple(name=黄苹果, weight=4, price=44.44)], 绿苹果=[Apple(name=绿苹果, weight=5, price=55.55)], 红苹果=[Apple(name=红苹果, weight=3, price=33.33)]}
甚至可以多级分组,因为gourpingBy 方法中可以在传递一个Collector对象,只要你愿意可以无限分组
Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream)
//多级分组
@Test
public void test6(){
Map<String, Map<String, List<Apple>>> map = apples.stream()
.collect(Collectors.groupingBy(Apple::getName, Collectors.groupingBy((e) -> {
if(e.getPrice() >= 60)
return "高档苹果";
else if(e.getPrice() >= 35)
return "中档苹果";
else
return "低档苹果";
})));
System.out.println(map);
}
//result: {毒苹果={高档苹果=[Apple(name=毒苹果, weight=6, price=66.66)]}, 黄苹果={中档苹果=[Apple(name=黄苹果, weight=4, price=44.44)]}, 绿苹果={中档苹果=[Apple(name=绿苹果, weight=5, price=55.55)]}, 红苹果={低档苹果=[Apple(name=红苹果, weight=3, price=33.33)]}}
还可以使用Collectors.partitioningBy 方法来进行分区
@Test
public void test7(){
Map<Boolean, List<Apple>> map = apples.stream()
.collect(Collectors.partitioningBy((apple) -> apple.getPrice() >= 50)); //根据苹果价格是否大于50 来分区
System.out.println(map);
}
//result : {false=[Apple(name=红苹果, weight=3, price=33.33), Apple(name=黄苹果, weight=4, price=44.44)], true=[Apple(name=绿苹果, weight=5, price=55.55), Apple(name=毒苹果, weight=6, price=66.66)]}
4 总结
java8 中的Stream 搭配四大内置核心函数式接口使用起来,基本上取代了很多我们以前需要自己手动实现的方法,使得代码逻辑清楚,语言表达能力更强,通过Stream 也体会到了lambda 表达式这种函数的方便之处, 更重要的是他可以帮助我们处理以前很多在sql中完成的操作,大大减小了数据库压力,数据库所需要做的只是拿出数据,我们可以将大量的逻辑放在语言中实现,其中更是有parallelStream()这种底层采用fork/join的并行流,大大增加了数据的处理速度.通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序.