序:
我们在做后端开发的时候,绝大多数情况下都是在操作集合,对集合的增删改,然后返回到前端渲染。
一般情况,我们将集合视为一个可伸缩的数组,通过add、set、remove 等操作对集合进行增改删等。很多时候,我们需要对集合中的每一个数据对象进行操作,我们便会用for循环,有没有其他的快捷简单的优雅的方式呢?stream流就是解决集合的遍历的。
eg:
- 从员工集合中筛选出salary大于8000的员工,并放置到新的集合里。
- 统计员工的最高薪资、平均薪资、薪资之和。
- 将员工按薪资从高到低排序,同样薪资者年龄小者在前。
- 将员工按性别分类,将员工按性别和地区分类,将员工按薪资是否高于8000分为两部分。
这些功能,用传统的迭代处理也不是很难,但代码就显得冗余了,跟Stream相比高下立判。
stream流的api相关的脑图如图:
1 Stream概述
Java 8 是一个非常成功的版本,这个版本新增的Stream,配合同版本出现的 Lambda ,给我们操作集合(Collection)提供了极大的便利。
那么什么是Stream?Stream将要处理的元素集合看作一种流,在流的过程中,借助Stream API对流中的元素进行操作,比如:筛选、排序、聚合等。
Stream可以由数组或集合创建,对流的操作分为两种:
- 中间操作:每次返回一个新的流,例如我们对它进行过滤之后,会返回一个过滤之后的流,然后可以继续对过滤之后的这个流进行排序之类的操作。
- 终端操作:每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值。一般就是对流进行收集,也就是将多次加工之后的数据放入一直新的集合对象中,或者直接返回成其他的形式的集合或者对象,最后结束操作,我们就获得我们想要的集合对象。
另外,Stream有几个特性: - stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。说简单点,一个stream流是一个集合的加工机器,仅仅用于对数据进行加工处理。
- stream不会改变数据源,通常情况下会产生一个新的集合或一个值。就像刚才说的,最后处理完之后,原来的集合中的数据是不会变的,只是生成一个新的集合返回。
- stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。只有你调用手收集的相关操作,所有的流才会操作。类似于懒加载。
2.Stream的创建
创建stream流的方式有三种:
- 第一种是集合数组的方式创建。
- 第二种是Collection/Map的方式创建。
- 第三种是使用Stream的静态方法。
2.1 集合的方式创建流
Stream可以通过集合或者数组创建。
1、通过 java.util.Collection.stream() 方法用集合创建流
List<String> list = Arrays.asList("a", "b", "c");
// 创建一个顺序流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream = list.parallelStream();
2.2 使用数组的方式创建
使用java.util.Arrays.stream(T[] array)方法用数组创建流
int[] array={1,3,5,6,8};
IntStream stream = Arrays.stream(array);
2.3 使用Stream静态方法的方式创建
使用Stream的静态方法:of()、iterate()、generate()
//这里请自行的去了解lambda表达式。
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4);
stream2.forEach(System.out::println);
Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println);
2.4 顺序流和并行流(stream、parallelStream)
stream和parallelStream的简单区分: stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。例如筛选集合中的奇数,两者的处理不同之处如下图:
如果流中的数据量足够大,并行流可以加快处速度。
除了直接创建并行流,还可以通过parallel()把顺序流转换成并行流:
Optional<Integer> findFirst = list.stream().parallel().filter(x->x>6).findFirst();
3.Stream的使用
下面直接用案例的方式讲解stream流中的api:
先给一个员工的案例:
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, "male", "New York"));
personList.add(new Person("Jack", 7000, "male", "Washington"));
personList.add(new Person("Lily", 7800, "female", "Washington"));
personList.add(new Person("Anni", 8200, "female", "New York"));
personList.add(new Person("Owen", 9500, "male", "New York"));
personList.add(new Person("Alisa", 7900, "female", "New York"));
class Person {
private String name; // 姓名
private int salary; // 薪资
private int age; // 年龄
private String sex; //性别
private String area; // 地区
// 构造方法
public Person(String name, int salary, int age,String sex,String area) {
this.name = name;
this.salary = salary;
this.age = age;
this.sex = sex;
this.area = area;
}
// 省略了get和set,请自行添加
}
3.1 Stream (foreach、find、match)
这里为了新手能够更好的理解Stream的使用,这里不做lambda表达式的简化。
List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
// 遍历输出符合条件的元素
list.stream().forEach(new Consumer<Integer>() {
@Override
public void accept(Integer inputRes) {
System.out.print(inputRes); //这里会打印出 7693821
}
});
//没学过Optional的可以看我的文章:
// https://blog.youkuaiyun.com/weixin_46269045/article/details/142681907
// 匹配第一个(这里是有序的,所以不能用并行流。但是他不会返回一个数据对象,而是返回一个Optional,
Optional<Integer> findFirst = list.stream().findFirst();
Integer findFirstInt = findFirst.get();//结果是7
// 匹配任意,这里返回(适用于并行流)
Optional<Integer> findAny = list.parallelStream().findAny();
Integer findAnyInt = findAny.get(); // 结果是任何一个数字
// 是否包含符合特定条件的元素
//判断这个集合是否包含一个及其以上的满足该条的结果
//下面的结果返回true,因为7 9 8 都大于6
boolean anyMatch = list.stream().anyMatch(new Predicate<Integer>() {
@Override
public boolean test(Integer inputRes) {
return inputRes > 6;
}
});
3.2 筛选(filter)
筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作。
案例一:筛选出Integer集合中大于7的元素,并打印出来:
list.stream().filter(new Predicate<Integer>() { //过滤
@Override
public boolean test(Integer inputRes) {
return inputRes > 7; //凡是满足这个的结果,都会被收纳
}
}).forEach(new Consumer<Integer>() { //遍历操作
@Override
public void accept(Integer inputRes) {
System.out.println(inputRes); //输出8和9
}
});
//案例2----------------------------------------------------------------------------------------------
// 筛选员工中工资高于8000的人,并形成新的集合。 形成新集合依赖collect(收集),后文有详细介绍。
List<Person> personList = new ArrayList<>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.stream().filter(new Predicate<Person>() {
@Override
public boolean test(Person person) {
return person.getSalary() > 8000; //筛选薪资是否大于8000
}
}).forEach(new Consumer<Person>() {
@Override
public void accept(Person person) {
System.out.println(person.getName()); //打印 Tom
}
});
3.3 聚合(max/min/count)
max、min、count这些字眼你一定不陌生,没错,在mysql中我们常用它们进行数据统计。Java stream中也引入了这些概念和用法,极大地方便了我们对集合、数组的数据统计工作。它的作用就是统计集合的值。整体理解起来还是比较简单,这里上直接上案例:
1.max(求最大值):
//获取最长的字符串
List<String> list = Arrays.asList("wang", "dany", "danyyy", "danywang", "danywangwang");
Optional<String> optionalMax = list.stream().max(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
});
String result = optionalMax.get(); //danywangwang
2.min(和max正好相反,这里不做过多介绍)
3.count (统计个数)
一般count是统计个数的,我们可以知道符合筛选条件的count的个数为多少
List<Integer> list = Arrays.asList(7, 6, 4, 8, 2, 11, 9);
long count = list.stream().filter(x -> x > 6).count(); //这里是统计有多少个大于6的数字
System.out.println("list中大于6的元素个数:" + count); //结果是4
3.4 map映射(map/flatMap)
映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。比如我们可以将Person流的对象映射成为Person的name的对象,从而获取到Person姓名的集合,其中:
- map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
- flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
这里不理解这两个流的实际意义的区别没关系,其实我也不太理解,咱们后面举几个例子可能就能够理解了,如果实在理解不了,以后遇到需要映射的操作的时候,用map即可。
map :
String[] lowerCase = { "aaa", "bbb", "ccc", "ddd" };
List<String> uppeerCaseList = Arrays.stream(lowerCase).map(new Function<String, String>() {
@Override
public String apply(String s) { //这里s就是原始集合的一个个的值
return s.toUpperCase();//大写操作 这里返回的就是新的大写的值
}
}).collect(Collectors.toList());
/**
* 最终的效果就像下面的映射一样:
* aaa->AAA
* bbb->BBB
* ccc->CCC
* ddd->DDD
**/
//将其最终大写化(最后uppeerCaseList 的数据是 AAA BBB CCC DDD )
flatMap:
//案例:两个字符数组合并成一个新的字符数组。
List<String> list = Arrays.asList("m,k,l,a", "1,3,5,7");
List<String> listNew = list.stream().flatMap(s -> {
// 将每个元素转换成一个stream
String[] split = s.split(",");
Stream<String> s2 = Arrays.stream(split);
return s2;
}).collect(Collectors.toList());
System.out.println("处理前的集合:" + list);
System.out.println("处理后的集合:" + listNew);
//处理前的集合:[m-k-l-a, 1-3-5]
//处理后的集合:[m, k, l, a, 1, 3, 5]
看到这里就有疑问了,这个flatMap好像也并没有什么特殊的用处,有点多此一举?其实不然。有的集合想要映射成一个个数组或者集合相关的对象(就像上面案例一样),然后将一个个集合进一步处理,为了对一个个集合也进行流的操作,这里就可以直接用到flatMap了。
mapToInt、mapToLong、mapToDouble
显而易见,这三个就是将对象映射为int long double等对象的,这里就不过多举例子:看下下面的两个例子即可:
// 输出字符串集合中每个字符串的长度
List<String> stringList = Arrays.asList("mu", "优快云", "hello",
"world", "quickly");
stringList.stream().mapToInt(String::length).forEach(System.out::println);
//---------------------------------------------------------------------------
// 将int集合的每个元素增加1000
List<Integer> integerList = Arrays.asList(4, 5, 2, 1, 6, 3);
integerList.stream().mapToInt(x -> x + 1000).forEach(System.out::println);
除此之外,mapToXXX 之类的方法还有很对聚集的功能,比如求最大最小值、求和、求平均值等,这里就不需要我们去实现一些函数式接口来定义了:
List<Double> doubleList = Arrays.asList(1.0, 2.0, 3.0, 4.0, 2.0);
double average = doubleList.stream().mapToDouble(new ToDoubleFunction<Double>() {
@Override
public double applyAsDouble(Double value) {
//这里可以做一些额外的处理操作,具体就不详细说明了。
return value;
}
})
.average()//直接求平均值,简单方便 还有其他的.sum()以及.max()等来求出总和以及最大值
.getAsDouble();//获得到最后的值
3.5 归约
什么是规约,规约也叫缩减,就是将一个集合的值进行一步步的“合并”,比如,将集合:[1,2,3,4]一步步相加,就是1+2 = 3 ------> 3(这个是1+2得到的3)+ 3 = 7
----> 7 + 4 = 11 。类似这种累计操作的功能,就是归约。也就是【1】和【2】进行操作,得到的结果在以同样的规则和【3】进行操作,以此类推。
stream给我们提供了规约的api,这里给一个简单的例子:
List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4);
// 求和方式1
Optional<Integer> sum = list.stream().reduce(new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer int1, Integer int2) {
//对两个元素做操作,得到的结果作为第三个元素的int1
return int1 + int2;
}
});//这里返回的不是一个规约后的结果,是一个Optional Optional不会的可以去看下我写的Optional讲解,最后得到的结果是这个集合的和
//-----------------------------------------------------------------------------------------------------
//咱们还可以通过它求一个集合中集合的最大值
List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4);
// 求和方式1
Optional<Integer> sum = list.stream().reduce(new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer int1, Integer int2) {
//最后求出集合的最大值
return Math.max(int1,int2);
}
});//这里返回的不是一个规约后的结果,是一个Optional Optional不会的可以去看下我写的Optional讲解
}
3.6 Stream收集
我们对集合的一个个元素进行处理之后,就需要将处理好的元素收集起来,这里,收集成什么样的数据结构,List,Map,还是Set等,就取决于你指定哪个最终的集合。
因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toList、toSet和toMap比较常用,另外还有toCollection、toConcurrentMap等复杂一些的用法
collect的收集可以说是stream中内容最繁多、功能最丰富的部分了。
其中:
收集的collect方法主要依赖java.util.stream.Collectors类内置的静态方法。
这点比较重要!
3.6.1 (toList、toSet、toMap)
这三个是我们平时开发中最常用的三个收集器,toList就是将处理好的数据流收集成为一个List集合。其他与此相似。toSet可以给最终的数据流做去重处理(注意这里去重对象的话记得要重写equals、haCode)。toMap的入参给我们提供了两个Function接口,入参是每个实体对象,然后你可以选择这个对象的哪个属性(也可以直接把对象作为key,一般不常用)作为key,什么作为value。这样就能将一个单列集合转成一个Map。下面给几个具体的简单案例来说明一下:
List<Integer> list = Arrays.asList(1, 6, 3, 4,4,4,4 6, 7, 9, 6, 20);
//先对集合做过滤,取2的倍数,然后最后收集为List集合(注意这里的listNew集合是个新的集合,不会影响list里面的元素)
List<Integer> listNew = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toList());
//与上同理,但是4会被去重
Set<Integer> set = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toSet());
//Map收集
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
personList.add(new Person("Anni", 8200, 24, "female", "New York"));
List<Person> list = new ArrayList<>();
Map<?, Person> map = list.stream()
.collect(Collectors.toMap(new Function<Person, String>() {
//第一个函数式接口是返回key的
@Override
public String apply(Person person) {
return person.getName();
}
}, new Function<Person, Person>() {
//第二个是返回value的
@Override
public Person apply(Person person) {
//这里可以对每个元素进行二次处理,然后,返回作为value
return person;
}
})); //最终我们通过toMap 将person集合变成以用户名为Key,person为value的map集合
System.out.println("toList:" + listNew);
System.out.println("toSet:" + set);
System.out.println("toMap:" + map);
3.6.2 统计(count/averaging/max/min)
平时我们对集合进行聚合统计的时候(求最大值,最小值,平均值等),可以通过for循环等进行统计。有没有更加简洁优雅的办法呢?当然,Collectors提供了一系列用于数据统计的静态方法:
1.计数:count
2.平均值:averagingInt、averagingLong、averagingDouble
3.最值:maxBy、minBy
4.求和:summingInt、summingLong、summingDouble
5.统计以上所有:summarizingInt、summarizingLong、summarizingDouble
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
// 求总数
Long count = personList.stream().collect(Collectors.counting());//求元素的总和
//--------------------------------------------------------------------------------------------------------
Double averageSalary = personList.stream().collect(Collectors.averagingDouble(new ToDoubleFunction<Person>() {
@Override
public double applyAsDouble(Person value) {
return value.getSalary();//将需要统计的属性进行二次处理,转换为double,再进行统计
}
}));//求薪资的平均值(averagingLong,averagingInteger同理,只是最后的值的类型不同而已)
//--------------------------------------------------------------------------------------------------------
//求工资最多的那个person
Person maxPerson = personList.stream().collect(Collectors.maxBy(new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
return p1.getSalary() - p2.getSalary();//正数表示p1大,负数表示p2大,为零则相等
}
})).get();//求薪资的最大值的那个person(Collectors.maxBy的泛型为Optional,这里用get获取它的真实的值)
//--------------------------------------------------------------------------------------------------------
// 求工资之和
Integer sumSalary = personList.stream().collect(Collectors.summingInt(new ToIntFunction<Person>() {
@Override
public int applyAsInt(Person person) {
return person.getSalary();
}
}));//(summingXXX 其他类型同理)
//--------------------------------------------------------------------------------------------------------
//一次性统计所有信息
DoubleSummaryStatistics statistics = personList.stream().collect(Collectors.summarizingDouble(new ToDoubleFunction<Person>() {
@Override
public double applyAsDouble(Person person) {
return person.getSalary();//提供需要统计的那个属性字段
}
}));//结果会返回一个DoubleSummaryStatistics对象,里面会有平均值,最大最小等值的信息。
System.out.println(statistics.getAverage());//平均值
System.out.println(statistics.getMax());//最大值
System.out.println(statistics.getCount());//最小值
// --------------------------------------------------------------------------------------------------------
3.6.3 分组(partitioningBy/groupingBy)
分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。
分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 45, "male", "New York"));
personList.add(new Person("Jack", 7000, 86, "male", "Washington"));
personList.add(new Person("Lily", 7800, 23, "female", "Washington"));
personList.add(new Person("Anni", 8200, 18, "female", "New York"));
personList.add(new Person("Owen", 9500, 69, "male", "New York"));
personList.add(new Person("Alisa", 7900, 75, "female", "New York"));
// 将员工按薪资是否高于8000分组
Map<Boolean, List<Person>> part = personList.stream().collect(Collectors.partitioningBy(new Predicate<Person>() {
@Override
public boolean test(Person person) {
return person.getSalary() > 8000;
}
}));
// 将员工按性别分组
Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(new Function<Person, String>() {
@Override
public String apply(Person person) {
return person.getSex();
}
}));
// 将员工先按性别分组,再按地区分组
Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(new Function<Person, String>() {
@Override
public String apply(Person person) {
return person.getSex();
}
}, Collectors.groupingBy(new Function<Person, String>() {
@Override
public String apply(Person person) {
return person.getCity();
}
})));
System.out.println("员工按薪资是否大于8000分组情况:" + part);
System.out.println("员工按性别分组情况:" + group);
System.out.println("员工按性别、地区:" + group2);
结果:
员工按薪资是否大于8000分组情况:{false=[Test.Person(name=Jack, salary=7000, age=86, sex=male, city=Washington), Test.Person(name=Lily, salary=7800, age=23, sex=female, city=Washington), Test.Person(name=Alisa, salary=7900, age=75, sex=female, city=New York)], true=[Test.Person(name=Tom, salary=8900, age=45, sex=male, city=New York), Test.Person(name=Anni, salary=8200, age=18, sex=female, city=New York), Test.Person(name=Owen, salary=9500, age=69, sex=male, city=New York)]}
员工按性别分组情况:{female=[Test.Person(name=Lily, salary=7800, age=23, sex=female, city=Washington), Test.Person(name=Anni, salary=8200, age=18, sex=female, city=New York), Test.Person(name=Alisa, salary=7900, age=75, sex=female, city=New York)], male=[Test.Person(name=Tom, salary=8900, age=45, sex=male, city=New York), Test.Person(name=Jack, salary=7000, age=86, sex=male, city=Washington), Test.Person(name=Owen, salary=9500, age=69, sex=male, city=New York)]}
员工按性别、地区:{female={New York=[Test.Person(name=Anni, salary=8200, age=18, sex=female, city=New York), Test.Person(name=Alisa, salary=7900, age=75, sex=female, city=New York)], Washington=[Test.Person(name=Lily, salary=7800, age=23, sex=female, city=Washington)]}, male={New York=[Test.Person(name=Tom, salary=8900, age=45, sex=male, city=New York), Test.Person(name=Owen, salary=9500, age=69, sex=male, city=New York)], Washington=[Test.Person(name=Jack, salary=7000, age=86, sex=male, city=Washington)]}}
3.6.4 接合(joining)
joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
String names = personList.stream().map(p -> p.getName()).collect(Collectors.joining(","));
System.out.println("所有员工的姓名:" + names);//Tom,Jack,Lily 字符串
List<String> list = Arrays.asList("A", "B", "C");
String string = list.stream().collect(Collectors.joining("-"));
System.out.println("拼接后的字符串:" + string);//A-B-C
3.6.5 归约(reducing)
Collectors类提供的reducing方法,相比于stream本身的reduce方法,增加了对自定义归约的支持。
final List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 45, "male", "New York"));
personList.add(new Person("Jack", 7000, 86, "male", "Washington"));
personList.add(new Person("Lily", 7800, 23, "female", "Washington"));
personList.add(new Person("Anni", 8200, 18, "female", "New York"));
personList.add(new Person("Owen", 9500, 69, "male", "New York"));
personList.add(new Person("Alisa", 7900, 75, "female", "New York"));
Integer sum = personList.stream().collect(Collectors.reducing(0, new Function<Person, Integer>() {
@Override
public Integer apply(Person person) {
return person.getSalary();//对象的哪一个属性作为规约的目标
}
}, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer salary1, Integer salary2) { //规约的具体动作
// 其中salary1 表示上一次规约的结果,salary2表示下一个被规约的元素的值
return salary1 + salary2;
}
}));
System.out.println("sum = " + sum);//sum=49300
3.7 排序(sorted)
sorted,中间操作。有两种排序:
sorted():自然排序,流中元素需实现Comparable接口
sorted(Comparator com):Comparator排序器自定义排序
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Sherry", 9000, 24, "female", "New York"));
personList.add(new Person("Tom", 8900, 22, "male", "Washington"));
personList.add(new Person("Jack", 9000, 25, "male", "Washington"));
personList.add(new Person("Lily", 8800, 26, "male", "New York"));
personList.add(new Person("Alisa", 9000, 26, "female", "New York"));
// 按工资升序排序(自然排序)
List<String> newList = personList.stream().sorted(Comparator.comparing(new Function<Person, Integer>() {
@Override
public Integer apply(Person person) {//根据person的哪个字段排序
return person.getSalary();
}
}))
.map(Person::getName)
.collect(Collectors.toList());
// 按工资倒序排序(reversed())
List<Person> newList1 = personList.stream().sorted(Comparator.comparing(new Function<Person, Integer>() {
@Override
public Integer apply(Person person) {
return person.getSalary();
}
}).reversed())//这里添加一个reversed,可以逆序排序
.collect(Collectors.toList());
// 先按工资再按年龄升序排序
List<Person> newList3 = personList.stream()
.sorted(Comparator.comparing(Person::getSalary)
.thenComparing(Person::getAge))
.collect(Collectors.toList());
// 先按工资再按年龄自定义排序(降序)
List<Person> newList4 = personList.stream().sorted((p1, p2) -> {
if (Objects.equals(p1.getSalary(), p2.getSalary())) {
return p2.getAge() - p1.getAge();
} else {
return p2.getSalary() - p1.getSalary();
}
}).collect(Collectors.toList());
3.8 提取/组合
流也可以进行合并、去重、限制、跳过等操作。
public class StreamTest {
public static void main(String[] args) {
String[] arr1 = { "a", "b", "c", "d" };
String[] arr2 = { "d", "e", "f", "g" };
Stream<String> stream1 = Stream.of(arr1);
Stream<String> stream2 = Stream.of(arr2);
// concat:合并两个流 distinct:去重
List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
// limit:限制从流中获得前n个数据
List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());
// skip:跳过前n个数据
List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());
System.out.println("流合并:" + newList);
System.out.println("limit:" + collect);
System.out.println("skip:" + collect2);
}
}
// 流合并:[a, b, c, d, e, f, g]
// limit:[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
// skip:[3, 5, 7, 9, 11]
总结
以上就是stream的所有的常规的用法了。日常开发中,我们尽量用stream处理集合,不仅方便快捷而且代码简单。以上我没有用lambda表达式,是为了方便初学者理解调用的哪个函数是接口。在实际开发中,尽量去用lambd表达式和方法引用去简化代码的书写。