java8 stream流的出现使得关于集合的代码变得更加简洁,易读性增强。
以下是几个常用的操作总结:
目录:
用例1:
1、anyMatch、allMatch、noneMatch
1.1 anyMatch
1.2 allMatch
1.3 noneMatch
2、collect
2.1 Collectors.toList 和 Collectors.toSet
3、map
4、filter
5、forEach
6、distinct
7、skip and limit
用例2:
1、Collectors.joining的三种方式
1.1 直接拼接:joining()
1.2 每个元素拼接时中间指定符号或者元素:joining(CharSequence delimiter)
1.3 中间指定符号或元素的同时增加前后缀:joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
2、peek
2.1peek和map的区别
用例3:
1、Collectors.toMap 和 toMap
Collectors.toMap、toMap
2、Collectors.groupingBy
用例集合一:
List<Integer> numbers = Arrays.asList(1, 2, 3, 3, 5, 8, 10, 10, 1, 12);
1、anyMatch、allMatch、noneMatch
1.1 anyMatch
if (numbers.stream().anyMatch(n -> n.equals(5))) {
System.out.println("anyMatch:任意一个匹配即为true");
}
1.2 allMatch
if (numbers.stream().allMatch(n -> n > 0)) {
System.out.println("allMatch:全部匹配即为true");
}
1.3 noneMatch
if (numbers.stream().noneMatch(n -> n > 100)) {
System.out.println("noneMatch:全部都不匹配即为true");
}
结果为:
anyMatch:任意一个匹配即为true
allMatch:全部匹配即为true
noneMatch:全部都不匹配即为true
总结:anyMatch为任意一个匹配即为true,allMatch为全部匹配才为true,noneMatch为全部都不匹配才为true
2、collect
2.1 Collectors.toList 和 Collectors.toSet
List<Integer> toList = numbers.stream().filter(n -> (n > 10 || n.equals(10))).collect(Collectors.toList());
Set<Integer> toSet = numbers.stream().filter(n -> (n > 10 || n.equals(10))).collect(Collectors.toSet());
System.out.println("toList:"+toList);
System.out.println("toSet:"+toSet);
结果为:
toList:[10, 10, 12]
toSet:[10, 12]
总结:toList和toSet都是将结果集放在容器中,需要注意的是,放入哪个容器就要遵循哪个容器的规则特点,即放入List容器中就是有序可重复,放入Set容器中就是无序且不可重复。通过上述例子也可以证明这一点
3、map
map即映射,用于映射每个元素对应到的结果
List<Integer> squaresList = numbers.stream().map(n-> n*n).collect(Collectors.toList());
// 结果为:[1, 4, 9, 9, 25, 64, 100, 100, 1, 144]
4、filter
见名知意,filter为过滤器的意思,可以按照条件筛选出自己想要的结果
List<Integer> filterList = numbers.stream().filter(n -> n > 5).collect(Collectors.toList());
// 结果为:[8, 10, 10, 12]
5、forEach
同理,和我们普通forEach一样
numbers.stream().forEach(n -> {
System.err.print( n + 1 + " ");
});
// 打印结果为:2 3 4 4 6 9 11 11 2 13(即通过循环给每个数+1并输出)
6、distinct
和mysql一样,去重的意思
List<Integer> distinctList = numbers.stream().filter(n -> n > 5).distinct()
.collect(Collectors.toList());
// 结果为:[8, 10, 12]
7、skip and limit
字面意思,分别是跳过和限制
long numSkip = numbers.stream().skip(2).count();
System.out.println("numSkip:" + numSkip); // 结果为:numSkip:8
long numLimit = numbers.stream().limit(2).count();
System.out.println("numLimit:" + numLimit); // 结果为:numLimit:2
可以看出来skip是跳过几个,limit 是限制几个
用例集合二:
List<String> strings = Arrays.asList("A","B","C","D");
1、Collectors.joining的三种方式
joining代表连接,支持三种方式:直接拼接;每个元素拼接时中间指定符号或者元素;中间指定符号或元素的同时增加前后缀
1.1 直接拼接:joining()
String joining1 = strings.stream().collect(Collectors.joining());
// 结果为 ABCD
1.2 每个元素拼接时中间指定符号或者元素:joining(CharSequence delimiter)
String joining2 = strings.stream().collect(Collectors.joining("-"));
// 结果为 A-B-C-D
1.3 中间指定符号或元素的同时增加前后缀:joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
String joining3 = strings.stream().collect(Collectors.joining("-", "[", "]"));
// 结果为 [A-B-C-D]
2、peek
peek通常用于debug操作,可以先看下面两个例子,寻找不同
strings.stream().peek(s-> System.out.print("peek:"+s+" "));
// 结果没有打印
strings.stream().peek(s-> System.out.print("peek:"+s+" ")).collect(Collectors.toList());
// 有输出,输出结果为: peek:A peek:B peek:C peek:D
通过上述举例,可以看出多了一个collect操作就能够输出了,这个原因为:peek是一个中间操作而非终止操作。这就引发另一个知识点:一个java8 的stream是由是三个部分组成:数据源、零个或者一个或者多个中间操作,零个或一个终止操作。中间操作是对数据的加工,是lazy操作,并不会立马启动,需要等待终止操作才会执行。只有加上终止操作,stream才会真正的执行。那么上述问题很容易看懂了,peek是一个中间操作,所以第一个例子并没有任何输出。peek通常拿来和map进行比较
2.1peek和map的区别
同上,先来看两个例子
strings.stream().peek(s->s.toLowerCase()).forEach(System.out::print);
// 打印结果为 ABCD
strings.stream().map(s->s.toLowerCase()).forEach(System.out::print);
// 打印结果为 abcd
上述结果可以看出来map对元素进行了真正的转换,而peek并没有。这时候你可能要总结了,peek不会改变元素,但是map可以改变元素,其实不对哦,ok,talk is cheap,show the code,多说无益,代码说话(此处代码用例为用例3)
List<People> peopleListPeek = peopleList.stream().filter(people -> people.getId().equals(2)).peek(people -> people.setName("peek修改实体元素值")).collect(Collectors.toList());
System.out.println("peek修改实体元素值" + peopleListPeek);
// 结果为:peek修改实体元素值[People(id=2, name=peek修改实体元素值, age=21)]
可以看到peek其实是可以改变元素的值的,那么peek到底和map什么区别呢?
先来看看peek和map的定义
Stream<T> peek(Consumer<? super T> action)
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
peek接收的是一个Consumer,map接收的是一个Function
Consumer是没有返回值的,他只对stream中的元素进行操作,但是操作之后并不会返回到stream中,所以stream中的元素还是原来的元素。但是Function是有返回值的,所有操作都会返回到stream中,即改变了stream中的元素。到这里可以看出,peek不是不可以改变元素的值,而是没有返回。
用例集合三:
People people1 = new People().setId(1).setAge(20).setName("张三");
People people2 = new People().setId(2).setAge(21).setName("李四");
People people3 = new People().setId(1).setAge(22).setName("王五");
List<People> peopleList = new ArrayList<>();
peopleList.add(people1);
peopleList.add(people2);
peopleList.add(people3);
@Data
@Accessors(chain = true)
public class People {
private Integer id;
private String name;
private Integer age;
}
1、Collectors.toMap 和 toMap
Collectors.toMap
二者是同一个意思,只是写法不同。使用了toMap函数后,返回的就是一个Map了,Map自然需要key和value,以下有三种情况
1.1 指明了当key重复时,value如何取值,且取后者,即 会覆盖
Map<Integer, People> map = peopleList.stream()
.collect(Collectors.toMap(people -> people.getId(), people -> people, (o, n) -> n));
System.out.println("Collectors.toMap(指明当key重复时,value如何取值finish_curriculum,且取后者,即会覆盖):" + map);
1.2 指明了当key重复时,value如何取值,取前者,即 不会覆盖
Map<Integer, People> map1 = peopleList.stream()
.collect(Collectors.toMap(people -> people.getId(), people -> people, (o, n) -> o));
System.out.println("Collectors.toMap1(指明当key重复时,value如何取值,取前者,即不会覆盖): " + map1);
1.3 未指明当key重复时,value如何取值
Map<Integer, People> map2 = peopleList.stream()
.collect(Collectors.toMap(people -> people.getId(), people -> people));
System.out.println("Collectors.toMap1(未指明当key重复时,value如何取值): " + map2);
上述打印结果:
Collectors.toMap(指明当key重复时,value如何取值finish_curriculum,且取后者,即会覆盖):{1=People(id=1, name=王五, age=22), 2=People(id=2, name=李四, age=21)}
Collectors.toMap1(指明当key重复时,value如何取值,取前者,即不会覆盖): {1=People(id=1, name=张三, age=20), 2=People(id=2, name=李四, age=21)}
Exception in thread "main" java.lang.IllegalStateException: Duplicate key People(id=1, name=张三, age=20)
首先,toMap()的第一个参数就是用来生成key值的,第二个参数就是用来生成value值的,第三个参数用在key冲突的情况下,如果新元素产生的key在Map中已经出现过了,第三个参数就会定义解决的办法。在这个例子中,people.getId()就是第一个参数,即为key,people本身就是第二个参数,即为value,而(o, n) -> o中的o和n就是旧value和新value。
其次,可以看出1.1 key相同是新value覆盖了旧value,1.2 key相同则是保持了旧value,而1.3则会运行时报错,报出:重复的key 这个错误,所以第三种写法是不对的(即不指明key冲突情况下的value取值问题)
toMap
toMap只是写法和Collectors.toMap不同,实现都是相同的,所以,同理
Map<Integer, People> map3 = peopleList.stream()
.collect(toMap(people -> people.getId(), people -> people, (o, n) -> n));
System.out.println("Collectors.toMap(指明当key重复时,value如何取值,且取后者,即会覆盖):" + map3);
Map<Integer, People> map4 = peopleList.stream()
.collect(toMap(people -> people.getId(), people -> people, (o, n) -> o));
System.out.println("Collectors.toMap1(指明当key重复时,value如何取值,取前者,即不会覆盖): " + map4);
// 运行时错误
Map<Integer, People> map5 = peopleList.stream()
.collect(toMap(people -> people.getId(), people -> people));
System.out.println("Collectors.toMap1(未指明当key重复时,value如何取值): " + map5);
结论与Collectors.toMap同理
2、Collectors.groupingBy
顾名思义,分组的意思
Map<Integer, List<People>> listMap = peopleList.stream().collect(Collectors.groupingBy(people -> people.getId()));
System.out.println("Collectors.groupingBy:"+listMap);
// 结果为:Collectors.groupingBy:{1=[People(id=1, name=张三, age=20), People(id=1, name=王五, age=22)], 2=[People(id=2, name=李四, age=21)]}
此例中,将people的id作为分组条件,可以看出结果中id为1的,即张三和王五被分到一组里,id为2的,即李四被分到一组
文末小结:暂时就这些啦,前两天朋友问了一个reduce操作,查了资料,但着实没有看懂,欢迎知道的大神留言评论