Stream流工作中都爱用,多实例详解过滤、匹配、归并、并行流等超完整笔记~(由浅入深的函数式编程②)

函数式编程

Functional Programming

函数式编程到底是什么?

笔者看来,当我们工作中的某个业务逻辑比较复杂时,我们都可以思考一个问题:如何将这类业务里各要素之间的映射关系抽象出来呢?
而当我们在程序世界抽象出来这个关系,知道我们要做什么后,我们便通过操作各种数据来达成我们的输出目的,这就是函数式编程的思想。

函数式编程优点

  • 并发多线程编程,可以高效处理大数量集合
  • 减少嵌套
  • 提高代码可读性

函数式编程的基石入门便是Lambda表达式,不了解的话可以看看笔者之前的一篇文章:
Lambda表达式:保姆级学习总结~(由浅入深的函数式编程①)

Stream流

概述

这是一个可以对集合或数组做链状的流式操作。
stream流也是函数式编程最重要的一块。

举一个通俗例子:
工厂需要制作一个玩具🐱‍🚀,给了一堆零件🎇🎆✨🎡(集合或数组)来,在这一条流水线的传送带上,我们需要通过筛选、拼装等操作将过来的零件组装成我们需要的那个玩具(目的)。
这个例子可以大概描述出stream流所做的事情,归根结底,重点在于通过各种操作来达成输出目的

使用

实例

需求:未成年防沉迷(手动狗头)
分析:获取到用户集合,打印所有年龄小于18的用户名,注意去重

List<User> users = getUsers();
users.stream()//把集合转换成流
	.distinct()//去重
	.fileter(user -> user.getAge()<18)//过滤
	.foreach(user -> System.out.println(user.getName()))//遍历打印

常用操作

创建流
Integer[] array = {0, 1, 2, 3, 4}
//第一种创建方法
Stream<Integer> stream = Arrays.stream(array);
//第二种创建方法
Stream<Integer> stream = Stream.of(array);

//第三种创建方法:双列集合
Map<String, Integer> map = new HashMap<>();
map.put("a", 20);
map.put("b", 17);
//转化成单列集合
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
Stream<Map.Entry<String, Integer>> stream = entrySet.stream();

中间操作

即创建流获取到数据后,进行的各种操作

  • filter
    可以对流传递过来的数据进行条件过滤,将符合条件的留在流中。
    (符合要求的零件留在流水线的传送带上)
    实例:
List<User> users = getUsers();
//过滤姓名长度大于1的用户
users.stream().filter(user->user.getName().length()>1);
  • map
    可以计算或者转换流里面的数据。

    实例:
//打印所有用户的姓名(转换)
List<User> users = getUsers();
users.stream().map(user -> user.getName());
		      .forEach(s->System.out.println(s));//终结操作
//将用户的积分乘2
List<User> users = getUsers();
users.stream().map(user->user.getScore())
			  .map(score->score*2)
  • distinct
    可以去除流里面重复的数据

    note:是通过Object的equals方法来判断是否是相同对象,可以重写equals方法
    实例:
List<User> users = getUsers();
users.stream().distinct()
  • sorted
    可以对流中的数据进行排序

    实例:
//按年龄降序排序,并且去重
List<User> users = getUsers();
users.stream()
			  .distinct()
			  .sorted((o1, o2)->o2.getAge()-o1.getAge())

note:如果调用空参的sorted()方法,则需要流中的数据实现Comparable的接口:

public class User implements Comparable<User>{
	private Long id;
	private String name;
	private Integer age;
	private Integer score;
	private List<equipment> equipments;
	...//其他元素
	
	@Override
	public int compareTo(User o){
		return o.getAge()-this.getAge();
	}
}
  • limit
    对流里面的数据进行限制

    实例:
//只取前3个用户
List<User> users = getUsers();
users.stream().limit(3)
  • skip
    跳过流里面的前n个数据,返回剩下的数据。

    实例:
//只取除了年龄最小的用户之外的用户
//即可以先排序,跳过第一个
List<User> users = getUsers();
users.stream()
			  .sorted((o1, o2)->o1.getAge()-o2.getAge())
			  .skip(1)
  • flatMap
    可以将一个对象转换成多个对象然后放入流形成新的数据。

    (比如:流水线上将一个零件拆解、改装成多个零件)
    实例1:
//取出所有用户的装备名,对重复数据去重
List<User> users = getUsers();
users.stream()
	 .flatMap(user -> user.getEquipments().stream())
	 .distinct()
	 .forEach(equipment ->
	         System.out.println(equipment.getName()));

实例2:

//打印用户装备里的所有分类,去重
//并且不能出现格式为:防具,武器(一件装备只能一个分类)
//需要分割字符串了
List<User> users = getUsers();
users.stream()
	 .flatMap(user -> user.getEquipments().stream())
	 //对流当中的装备去重
	 .distinct()
	 .flatMap(equipment->
	    Arrays.stream(equipment.getCategory()
	 	.split(",")))
	 //对流当中装备类型进行去重
	 .distinct()
	 .forEach(category -> System.out.println(category));
终结操作

即达成输出目的的一环,也是必须存在的一环。

  • forEach
    遍历流里面的元素,然后根据情况进行操作(打印或者其他复杂操作)

    实例:
List<User> users = getUser();
users.stream()
     .map(user->user.getName())
     .forEach(n -> System.out.println(n));
  • count
    给特定元素计数
    实例:
//计算用户装备数量,注意去重
List<User> users = getUser();
long count = users.stream()
				  .flatMap(user->user.getEquipment().stream)
				  .distinct()
				  .count()
System.out.println(count);
  • max/min
    找到流中数据的最值。

    实例:
//打印用户装备的最高分、最低分
List<User> users = getUser();
Optional<Integer> max = users.stream()
     						 .flatMap(user->user.getEquipment().stream())
     						 .map(equipment->equipment.getScore())
     						 .max((o1, o2) -> o1-o2);
System.out.println(max.get());

Optional<Integer> min = users.stream()
     						 .flatMap(user->user.getEquipment().stream())
     						 .map(equipment->equipment.getScore())
     						 .min((o1, o2) -> o1-o2);
System.out.println(min.get());

注意:既然是终结操作,那么用了max就不能再用min,要用另一种只能重新对流操作。

可以看到这里出现了 Optional 这样一个稍显陌生的内容,暂时不影响stream流的基本理解和使用。

但如果要继续学习,还是可以看看笔者的下篇文章Optional(创建、过滤、判断、数据转换)、函数式接口、方法引用(由浅入深的函数式编程③) 来了解关于Stream流里面涉及的Optional和其他函数式编程的知识。🕵️‍♀️

  • collect
    将当前的流转换成一个集合。

    实例1:
//获取一个存放所有用户名的集合
List<User> users = getUser();
List<String> nameList = users.stream()
							 .map(user->user.getName())
							 .collect(Collectors.tolist());
System.out,println(nameList);

实例2:

//获取一个所有装备名的set集合
List<User> users = getUser();
Set<Equipment> equipments = users.stream()
								 .flatMap(user->user.getEquipment().stream())
								 .collect(Collectors.toSet());
System.out.println(equipments);

实例3:

//获取一个Map集合,map的key为用户名,value为List<Equipment>
List<User> users = getUser();
Map<String, List<Equipment>> map = users.stream()
										.collect(Collector.toMap(user->user.getName(),
											 user->user.getEquipment()));
System.out.println(map);					   .
查找与匹配
  • anyMatch
    判断是否有符合匹配条件的元素,返回结果为boolean类型

    (有一个就返回为true)
    实例:
//判断是否有五十岁以上的用户
List<User> users = getUser();
boolean is = users.stream()
	 			  .anyMatch(user->user.getAge()>50);
System.out.println(is);
  • allMatch
    判断是否所有的元素都符合条件,也是boolean类型

    实例:
//判断用户是否都是成年人
List<User> users = getUser();
boolean is = users.stream()
				  .allMatch(user -> user.getAge() >= 18);
System.out.println(is);				  
  • noneMatch
    判断流里面的数据是否都不符合匹配条件,同样是boolean类型

    实例:
//判断是否用户都不超过55岁
List<User> users = getUser();
boolean is = users.stream()
				  .noneMatch(user -> user.getAge() >= 55);
System.out.println(is);					  
  • findAny
    获取流里面的任意一个元素,并不一定是流里的第一个元素。

    实例:
//获取任意一个年龄大于18岁的用户的姓名,存在则会输出
List<User> users = getUser();
Optional<User> user = users.stream()
	 					   .filter(user -> user.getAge()>18)
	 					   .findAny();
user.isPresent(user -> System.out.println(user.getName()));		 					   
  • findFirst
    显而易见,获取流里面第一个元素。

    实例:
//获取年龄最小的用户,并输出名字
List<User> users = getUser();
Optional<User> user = users.stream()
							//升序排序
						   .sorted((o1, o2) -> o1.getAge()-o2.getAge())
						   .findFirst();
System.out.println(user.getName());							   
归并(难点)
  • reduce
    对流里面的数据按照指定的计算方式来计算出一个结果。

    (缩减操作:即将流里面的各种元素进行组合,当传入一个初始值后,流会按照种种组合计算方式将初始值计算出一个新的结果,然后拿这个新的结果去跟后面的元素进行计算或者其他操作)
    内部计算方式如下:
T result = identity;//identity为方法参数传入的初始值
for (T element : this stream)
	//apply具体进行什么计算也是通过方法参数确定
	result = accumulator.apply(reslut, element)
return result;

实例1:

//使用reduce求所有用户的年龄和
List<User> users = getUser();
Integer sum = users.stream()
				   .distinct()
				   .map(user->user.getAge())
				   .reduce(identity:0, (result, element) -> result + element);
System.out.println(sum);

实例2:

//使用reduce求所有用户中年龄的最大值
List<User> users = getUser();
Integer max = users.stream()
				   .map(user->user.getAge())
				   //Integer.MIN_VALUE为初始化值,最小的
				   .reduce(Integer.MIN_VALUE, 
				     (result, element) -> result<element ? element:result);
System.out.println(max);				     

实例3:

//使用reduce求所有用户中年龄的最大值
List<User> users = getUser();
Integer min = users.stream()
				   .map(user->user.getAge())
				   //Integer.MAX_VALUE为初始化值,最大的
				   .reduce(Integer.MAX_VALUE, 
				     (result, element) -> result>element ? element:result);
System.out.println(min);				     

换一种方法,只传一个参数,一个参数的内部计算如下:

boolean foundAny = false;
T result = identity;
for (T element : this stream)
	//判断是否有这个元素
	if (!foundAny){
		foundAny = true;
		result = element;
	}else{
		result = accumulator.apply(reslut, element);
	}
	//把结果封装成Optional
return foundAny ? Optional.of(result) : Optional.empty();

实例3变形:

List<User> users = getUser();
Integer min = users.stream()
				   .map(user->user.getAge())
				   .reduce(new BinaryOperator<Integer>(){
						@Override
						public Integer apply(Integer result, Integer element){
							return result > element ? element : result;
						}
					})
System.out.println(min);					

注意点

  1. stream流是惰性求值
    即如果没有终结操作,中间操作是不会执行的。

  2. 流的使用是一次性的。
    当流经过一个终结操作后,便不能再加中间操作使用了。

  3. 一般不会影响原数据。
    只要不在代码内直接调用原数据的set方法,一般不会影响原数据。

高级用法

基本数据类型的优化

之前stream流里涉及到的参数和返回值很多都是引用数据类型,但在流里具体操作是基本数据类型,这样就需要装箱和拆箱,为了降低多次这样的时间损耗,则需要使用相关方法,如:
mapToInt, mapToLong, mapToDouble, flatMapToInt, flatMapToDouble等

List<User> users = getUser();
users.stream()
	 .mapToInt(user->user.getAge())
	 //这里已经是int类型了
	 .map(age -> age+1)
并行流

当流里面有大量数据的时候,可以将任务分配给多个线程去完成,即并行流。

  • 使用parallel()将串行流转换成并行流。
  • 使用parallelStream()直接获取并行流对象。

实例:

//高效率求和
Stream<Integer> stream = Stream.of(1,2,3,4,5,6,11,12,14,18,20);
Integer sum = stream.parallel()
					.filter(num->num>3)
					.reduce((result, element) -> result+element)
					.get();

啊👩‍🔧,到这里Stream流基本内容已经完成啦,此笔记算是小soul近日码字最多的一篇笔记,希望对大家有所帮助,也希望大家多多支持小soul!点赞关注拜托拜托~👱‍♀️

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值