java8 stream流 常用操作

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操作,查了资料,但着实没有看懂,欢迎知道的大神留言评论

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值