Lambda表达式和函数式编程
共 17622字,需浏览 36分钟
·
2021-03-23 21:19
点击关注,与你共同成长!
Lambda表达式和函数式编程
一.函数式编程概念
(a,b) -> {xxx}
参数 -> 方法体 左侧一个参数时()可以省略,右侧就一句方法体时{}可以省略
二.JDK8引入的函数是编程接口类
要想学习函数式编程一定要知道jdk提供的四种类型的函数式编程接口
1.Function<T, R> 该类型的方法接收一个T类型的参数,返回一个R类型的结果
2.Consumer<T> 该类型方法接收一个T类型的参数,无返回值
3.Supplier<T> 该类型方法不接收任何参数,返回一个T类型参数
4.Predicate<T> 该类型方法接收一个T类型的参数,返回一个Boolean类型返回值
5.Optional<T> 该类型方法既主要在lambda函数式编程中处理空值情况
三.流的创建方式
1.数组转流(Arrays.stream())
int[] a = {1, 2, 3};
Arrays.stream(a).peek(e -> log.info(e)).collect(Collectors.toList());
2.集合类型转流(Collection.stream())
List<User> userList = new ArrayList<>();
userList.stream().peek(user -> log.info(user)).collect(Collectors.toList());
3.任意类型对象转流(Stream.Of())
添加不同类型的对象会变成一个Object类型的流
Stream.of(1,2,3).peek(e -> log.info(e)).collect(Collectors.toList());
4.迭代器添加元素转流(Stream.iterate())
迭代器内第一个参数为初始值,第二个参数为一个lambda表达式,因为这个循环是个死循环所以这边limit了前10个元素
Stream.iterate(0,n -> n+1).limit(10).peek(e -> log.info(e)).collect(Collectors.toList());
5.generate生成流(Stream.generate())
迭代器内第一个参数为一个lambda表达式,因为这个循环是个死循环所以这边limit了前10个元素
Stream.generate(() -> Math.random()).limit(10).peek(e -> log.info(e)).collect(Collectors.toList());
6.StreamSupport生成流(Stream.generate())
其实集合转流的方式底层就是用的StreamSupport生成流的方式,大家可以去源码看下
Stream.generate()生成流的方式不常用,因为一般我们操作集合数组时直接用JDK封装好的转流方式就可以了,这边不做演示。
该方法有两个参数,第一个参数是迭代器Spliterator对象,第二个参数是是否启用并行流的true/false值
7.IntStream整形流
IntStream继承了BaseStream,有基础的流操作功能等
IntStream.range(0,5).boxed().peek(e -> log.info(e)).collect(Collectors.toList());
range()左闭右开,rangeClosed()左右都是闭区间,boxed()方法将基本类型转包装类型
8.Stream.builder()构造器生成流
Stream.builder()这种方式需要最后调用build()方法,本质上和Stream.Of()相同
Stream.Builder<User> userBuilder= Stream.builder();
userBuilder.add(new User()).add(new User()).add(new User()).build().collect(Collectors.toList());
四.常见操作符
1.中间操作符
User对象,假设User对象具有id,name,age三个属性,下面的例子以操作User对象为例
User user = new User();
List<User> userList = new ArrayList<>();
filter():过滤符合条件的数据流传给下层操作符处理
Optional<User> user1 = userList.stream().filter(user -> user.getAge() > 20).findFirst();
map():遍历数据,可在map中将原始对象转换为其他类型对象后返回新对象数据流传给下层操作符操作
peek():一般用于打印流操作中间状态的元素详情等,一般并无实际意义
findAny():返回流中的第一个元素,在串行流中和findFirst()功能一样,在并行流中返回的是最快处理完的那个线程的数据,所以说在并行流操作中,对数据没有顺序上的要求,那么findAny的效率会比findFirst要快的
Optional<User> user2 = userList.stream().filter(user -> user.getAge() > 20).findAny();
findFirst(): 返回流中的第一个元素
Optional<User> user1 = userList.stream().filter(user -> user.getAge() > 20).findFirst();
sort():排序,数字类型默认升序,中文和英文等按字典序排序,可以传入自定义的比较器(第一个参数compareTo()第二个参数就是升序,第二个参数compareTo()第一个参数就是降序)
userList.stream().sort().collect(Collectors.toList());
userList.stream().sort(Compartor.reverseOrder()).collect(Collectors.toList());
flatMap():参数是流,主要使用场景是处理高阶嵌套的流,将高阶流扁平化。例如:父子对象常见的集合属性
第一个应用场景:一个用户可能有多重角色,典型一对多父子类型
userList.stream().flatMap(user -> user.getRoles().stream()).peek(role -> log.info(role)).collect(Collectors.toList());
第二个应用场景:在流中产生了Optional元素,想要取出Optional中的具体类型对象来操作
此时假设角色集合返回的是 Optional<List<Role>>
类型
userList.stream().map(user->user.getRoles().stream()).flatMap(Optional::stream).peek(role->log.info(role)).count());
2.终端操作符(count、min、max具有统计意义)
User对象,假设User对象具有id,name,age三个属性,下面的例子以操作User对象为例
User user = new User();
List<User> userList = new ArrayList<>();
forEach():遍历集合,非并行流时流中的元素是顺序性的,并行流时流中的元素不能保证是顺序性的
将所有用户的年龄都改为20岁
userList.stream().forEach(user -> user.setAge(20));
forEachOrder():遍历集合,主要用于在并行流中想按排序的顺序操作流中元素,如果不是并行流那么forEachOrder和forEach没有任何区别
按年龄大小输出用户名称
userList.parallelStream().sorted(Comparator.comparing(User::getAge)).forEachOrdered(user -> log.info("用户名称" + user.getName()));
anyMatch():集合中有任何一个满足条件的数据就会返回true,反之返回false(存在短路的优点有结果了就会返回,不会继续遍历下去)
boolean result = userList.stream().anyMatch(user -> user.getAge() > 20);
如果anyMatch()方法中的函数体很长也可以这样操作
Predicate<User> predicate = user -> user.getAge() > 20;
boolean result = userList.stream().anyMatch(predicate);
allMatch():集合中所有数据都满足条件的数据才会返回true,任意一条数据不满足条件都会返回false
集合中有任何一个满足条件的数据就会返回true,反之返回false
boolean result = userList.stream().anyMatch(user -> user.getAge() > 20);
如果allMatch()方法中的函数体很长也可以这样操作
Predicate<User> predicate = user -> user.getAge() > 20;
boolean result = userList.stream().allMatch(predicate);
noneMatch():集合中没有任何一个匹配条件的元素时返回true,反之返回false
boolean result = userList.stream().noneMatch(user -> user.getAge() > 20);
如果noneMatch()方法中的函数体很长也可以这样操作
Predicate<User> predicate = user -> user.getAge() > 20;
boolean result = userList.stream().noneMatch(predicate);
count():统计满足条件的用户数
long count = userList.stream().filter(user -> user.getAge() > 20).count();
min():排序后取出最小的用户数据
Optional<User> min = userList.stream().min(Comparator.comparing(User::getAge));
max():排序后取出最大的用户数据
Optional<User> max = userList.stream().max(Comparator.comparing(User::getAge));
reduce():执行归集操作,某种程度上和Collect作用类似,设计上reduce应该和不可变得对象一起工作,如果对象是可变的,也可以得到结果,但是不是线程安全的,性能要弱于Collect,但是很灵活
第一个参数是初始值(可以不设置,不设置默认流中的第一个元素为初始值),第二个参数是个函数,函数的第一个参数是累加器,第二个参数是当前值,第三个参数是在并行流时会每个分片处理的线程会有一个临时的值,这个参数为合并策略。
累加器什么意思呢?就是第一次进来累加器取初始值,然后每次循环用当前的值加在累加器上,累加器相当于值得总和,reduce也是循环处理对象的
userList.stream().map(User::getAge).reduce(0,(Integer acc, Integer curr)-> Integer.sum(acc,curr));
userList.parallelStream().reduce(
Collections.EMPTY_LIST,
(acc,curr) ->{
List<User> newAcc = new ArrayList<>();
newAcc.addAll(acc);
newAcc.addAll(curr);
return newAcc;
},
(left,right)->{
List<User> merged = new ArrayList<>();
merged.addAll(left);
merged.addAll(right);
return merged;
}
);
3.常见集合对象收集器
toList():将结果收集为一个List集合
Stream.iterate(0,n -> n+1).collect(Collectors.toList());
toSet():将结果收集为一个Set集合,Set集合元素不会重复
Stream.iterate(0,n -> n+1).collect(Collectors.toSet());
toMap():将结果收集为一个Map集合,键值对的形式,收集为Map集合时,有3种参数类型的重载方法可选
2个参数的情况,toMap()中的第一个参数代表要收集Map结构的key,第二个参数代表要收集Map结构的value 其中Function.identity()和user -> user是等价的它代表输入和输出相同是Function中的一个静态方法(大家可以看下源码)
userList.stream().collect(Collectors.toMap(User::getId, user -> user));
userList.stream().collect(Collectors.toMap(User::getId, Function.identity()));
3个参数的情况,toMap()中的第三个参数代表当Key产生了重复值,那么在第三个参数方法中处理(用于合并的方法参数)
userList.stream().collect(Collectors.toMap(User::getId, user -> user, (oldValue,newValue) -> oldValue));
4个参数的情况,toMap()中的第四个参数代表构建Map的工厂,一般用于不想返回系统默认的HashMap结构,比如返回TreeMap等
userList.stream().collect(Collectors.toMap(User::getId, user -> user, (oldValue,newValue) -> oldValue, TreeMap::new));
toCollection():将结果收集为一个自定义的集合类型
TreeSet treeSet = userList.stream().collect(Collectors.toCollection(() -> new TreeSet(Comparator.comparing(User::getAge))));
4.收集器中的聚合计算,分组统计和收集器
首先我们来说下收集器中的聚合函数哈,虽然在数据库层面提供了分组,求平均值,计算数量,最大值,最小值等功能,但不代表我们没有在Lambda中完成上述操作的需求,因为毕竟是在内存中完成的聚合计算,有的时候性能会比数据库层面要提升很多
averagingXXX():求平均值,可以转为3中数字类型(Double,Integer,Long)
所有用户年龄的平均值
Integer aveAge = userList.stream().collect(Collectors.averagingInt(User::getAge));
Double aveAge = userList.stream().collect(Collectors.averagingDouble(User::getAge));
Long aveAge = userList.stream().collect(Collectors.averagingLong(User::getAge));
summingXXX():求和,可以转为3中数字类型(Double,Integer,Long)
Integer aveAge = userList.stream().collect(Collectors.summingInt(User::getAge));
Double aveAge = userList.stream().collect(Collectors.summingDouble(User::getAge));
Long aveAge = userList.stream().collect(Collectors.summingLong(User::getAge));
counting():计数,计算满足条件的对象或值的数量
Map<String, Long> stringLongMap = userList.stream().collect(Collectors.groupingBy(User::getName, Collectors.counting()));
maxBy():取最大值,方法中需要传进去一个比较器,不然它不知道按哪一个值比较大小,返回一个Optional对象
Optional<User> user = userList.stream().collect(Collectors.maxBy(Comparator.comparing(User::getAge)));
minBy():取最小值,方法中需要传进去一个比较器,不然它不知道按哪一个值比较大小,返回一个Optional对象
Optional<User> user = userList.stream().collect(Collectors.minBy(Comparator.comparing(User::getAge)));
summarizingXXX():为了方便我们操作,这个方法里统计了上面所有的值,返回一个XXXSummaryStatistics对象,我们可以按需取值
IntSummaryStatistics intSummaryStatistics = userList.stream().collect(Collectors.summarizingInt(User::getAge));
LongSummaryStatistics longSummaryStatistics = userList.stream().collect(Collectors.summarizingLong(User::getAge));
DoubleSummaryStatistics doubleSummaryStatistics = userList.stream().collect(Collectors.summarizingDouble(User::getAge));
groupingBy():以某一个值分组,默认返回一个Map,groupingBy方法中可以继续下一步的流操作(downstream),一般在业务中和mapping连用比较多
User对象转为UserDto对象:
Map<Long, List<UserDto>> map = userList.stream().collect(Collectors.groupingBy(
User::getId,
Collectors.mapping(
user -> new UserDto(user.getId(), user.getName(), user.getAge()),
Collectors.toList())
));
区域仓地址信息按省份id分组后计算市区的数量:
Map<Long, Long> cityCountMap = Optional.ofNullable(warehouseInfo.getAddr()).orElse(new ArrayList<>()).stream().collect(Collectors.groupingBy(WarehouseAddr::getAddrId1, Collectors.counting()));
区域仓地址信息按省份id分组后将市区组装为一个List集合
Map<Long, List<Long>> haveStockAreaMap = Optional.ofNullable(warehouseInfo.getAddr()).orElse(new ArrayList<>()).stream().collect(Collectors.groupingBy(WarehouseAddr::getAddrId1, Collectors.mapping(
WarehouseAddr::getAddrId2, Collectors.toList())));
partitioningBy():partitioningBy和groupingBy都是用于将数据进行分组的函数。
两者的区别主要是参数返回值不同,partitioningBy又被称为分区函数,重载的分区函数可以传递下游流操作,比如继续分组等
看源码可以看出函数的参数一个Predicate接口,那么这个接口的返回值是boolean类型的,也只能是boolean类型,然后他的返回值是Map的key是boolean类型,也就是这个函数的返回值只能将数据分为两组也就是ture和false两组数据。
public static <T>
Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
return partitioningBy(predicate, toList());
}
public static <T, D, A>
Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate,
Collector<? super T, A, D> downstream){
}
public static <T, K> Collector<T, ?, Map<K, List<T>>>
groupingBy(Function<? super T, ? extends K> classifier) {
return groupingBy(classifier, toList());
}
joining():主要用于字符串的连接,一般用3个参数的重载方法,第一个参数是以"符号”连接每个对象,第二个参数是整体返回对象的前缀,第三个参数是整体返回对象的后缀
以一个拼接访问参数为例:
Map<String,String> paramMap = new HashMap();
paramMap.put("name","张三");
paramMap.put("age","20");
paramMap.put("email","123@qq.com");
paramMap.keySet().stream().map(key - > key + "=" paramMap.get(key)).sorted().collect(Collectors.joining("&","http://localhost:8080/api?",""))
mapping():和常用中间操作符map()功能类似,第二个参数为下游流操作函数,主要处理中间类型转换等,可以一直用流操作串下去
List<String> list = Lists.newArrayList("bb", "ddd", "cc", "a");
Map<Integer,TreeSet<String>> result = list.stream().collect(
Collectors.groupingBy(
String::length,
Collectors.mapping(
String::toUpperCase,
Collectors.filtering(
s -> s.length() > 1,
Collectors.toCollection(TreeSet::new)
))));
collectingAndThen():一般用于先收集一个集合后,再对收集后的集合做一些操作
Map<String, Double> collect1 = userList.stream().collect(
Collectors.groupingBy(
User::getName,
Collectors.collectingAndThen(
Collectors.toList(),
e -> {
return e.stream().collect(Collectors.averagingDouble(User::getAge));
}
)
));
reducing():和reduce操作类似
五.Optional流操作
Optional是Java8新增的在java.util包下,主要用来辅助处理Java流式操作中的null值,它在返回结果之上又封装了一层,封装的这层永远不会出现null值,来确保我们在用lambda流操作时不会中断
1.生成Optional对象的方式
因为其构造方法是私有的,所以只能通过静态的构造器来创建Optional对象
Optional.<>of():生成Optional对象,接收的对象不能为null否则抛出NullPointerException异常
Optional.of(userList).ifPresent(users -> users.stream().filter(user -> user.getAge() > 20).count());
Optional.<>ofNullable():生成Optional对象,接收的对象可以为null,如果为null则内部返回Optional.<>empty()对象
Optional.ofNullable(userList).orElse(new ArrayList<>());
Optional.<>empty():生成一个空Optional对象,源码注释上写着不能保证这个对象是单例的,具体原因还没搞清,各位有懂得可以评论区为笔者解答下(笑出鹅叫)
2.Optional常见操作符
isPresent:判断Optional值是否存在,存在返回true,不存在返回false
boolean present = Optional.ofNullable(userList).isPresent();
if (present){
//TODO 业务逻辑
}
else {
//TODO 业务逻辑
}
orElse:Optional的值为null时,要返回的常量值或对象
Optional.ofNullable(userList).orElse(new ArrayList<>());
orElseGet:Optional的值为null时,要执行一个方法并返回一个值
Optional.ofNullable(userList).orElseGet(()->new ArrayList<User>());
orElseThrow:Optional的值为null时,要抛出的异常
Optional.of(userList).orElseThrow(()->new Exception());
or:Optional的值为空时,我们既不抛异常也不返回一个默认值下面的操作还要返回一个Optional对象时用or
ifPresent:如果Optional的值存在,下面的操作不需要返回一个流而要对值做一些处理时调用,传入一个方法
Optional.<List<User>>of(userList).ifPresent(users -> users.stream().filter(user -> user.getAge() > 20).count());
ifPresentOrElse:对于下一步的操作不需要返回一个流而要对值做一些处理时调用,有值情况用一个方法处理,无值情况也用一个方法处理(要传入两个方法操作Optional中的对象)
以上,便是今天的分享,希望大家喜欢,觉得内容不错的,欢迎「分享」「赞」或者点击「在看」支持,谢谢各位。