函数式编程
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);
注意点
-
stream流是惰性求值。
即如果没有终结操作,中间操作是不会执行的。 -
流的使用是一次性的。
当流经过一个终结操作后,便不能再加中间操作使用了。 -
流一般不会影响原数据。
只要不在代码内直接调用原数据的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!点赞关注拜托拜托~👱♀️