JavaSE-09-函数编程(实战与原理)
JavaSE-09-函数编程(实战与原理)
在学习Java泛型时,我们了解了泛型的强大,可以增强类型安全,减少模板代码;泛型可以使用在类、接口、方法上,而确定泛型的类型,则是在创建对象、使用方法时、获取实现接口时定义。泛型是在引用数据类型的基础上进行了抽象,类型抽象化。那么本文接下来要讨论的是函数编程,其依赖于函数接口,其Java内置的函数接口,基本都是泛型化的,只有实现使用时才能确定具体类型。
而函数接口,只有一个抽象方法的接口,这样的特征促使了Lambda表达式的出现应用,能推导就能省略的规则,所以进一步推导出了方法引用,这个语法糖,目的是将长得一样形式一样的已有方法进行复用,所以这是一步步层层递进的过程,抽象的过程,让代码更简洁,简洁意味着代码看起来美,像是自然语言在描述,当然一开始使用是不怎么习惯的,很多时候看Lambda表达式或方法引用,脑子里都会出现白屏的现象(一片空白),没有连贯的思路,有点丧失信心的焦虑感,同时看函数接口,带上泛型机制,双层抽象扑面而来,懵逼感会更强,你是否有过这样的体验呢?
与此同时,我们会有另外一种感觉:Stream流API用久了,真好用,会用,是因为设计者设计了非常多简单易用的Stream方法,处理数据流来得心应手,像是自然语言在描述处理过程,“代码像诗一样优雅”,“数据处理说明书”;但是剖析里面的源码,就会一脸懵逼抽象,太多的泛型符号?、K、V、U,函数接口。出现:会用但源码看不太懂,而且只能在Stream流式API这个圈子里打转和使用,无法自己进行创造新的Lambda圈子,你是否有过这样的心路历程呢?
但如果,理顺了其中的逻辑,省略逻辑,简洁逻辑,那么刻意练习后,你会由衷发现函数之美,lambda表达式之优雅,方法引用之精妙之处。
大概的推理逻辑:只有一个抽象方法的接口A -> 需要作为入参
方法1:定义实现类A1 ,再创建实现类对象 new A1()
方法2:只使用一次,那么匿名内部类创建匿名对象,new A{xxx}
方法3:使用lambda表达式,在2的基础上简化,因为最终目的是使用实现了接口的抽象方法,而且只有一个,所以可以推导,没有第二、三个方法,所以简化成:(参数)-> {抽象方法实现},突出了方法实现的逻辑,省略了一大推类定义、对象创建的代码,因为底层JVM帮你做了,优化了,因为只要该Lambda表达式的形式满足入参的类型,其他都好说。
方法4:方法引用,突然发现自己以前写的方法里面,有类C一个方法B其实现逻辑,就是我想要的,那么何必再写一遍呢?直接复用就行啦,因为类加载进来后,方法区会存储着方法的信息,所以拿到了的Class对象实例,就可以反射出来,获取方法对象,然后执行。这些JVM说都帮你做,因为可推导(JVM能找到)即可省略,最终直接入参里:C::B
有了这么一个总的认知,可以快速建立使用的感觉。本文采用另外的方式讲解,从一个完整的案例入手,逐步分析使用了什么样的知识点,带着问题出发,剖析底层原理。如果是想快速掌握StreamAPI的使用的,也可以参考模仿。
一、订单实战案例
本文基于日常开发中一个常见场景:订单处理,进行模拟,从获取数据源开始,然后流式处理数据,呈现StreamAPI的强大与优雅。
1.1 定义订单实体Order
record关键字,JDK16+,成员属性全部使用final修饰,线程安全,同时提供全参构造器,不可继承,隐式继承Record类,非常适用于数据传输对象DTO。获取属性:xx.字段名,即可!
public record Order (String orderId, String userId, Double amount, LocalDateTime createAt, String status) { }
1.2 定义订单服务OrderService
mock数据源,实际是来源于数据库或其他地方,一般都是集合列表、Map映射则转为EntrySet。
Stream流式操作的源头是数据集合,中间操作大部分都是涉及遍历,如:map,filter等,也就是每个数据集合的元素都会执行一遍调用方定义Lambda表达式,返回都是一个新的流。
终结操作,涉及数据集合归约,聚合成一个统计值-单值,如:avg、max、min等;另外一种是返回数据集合列表,如:toList、toSet、toMap等,同时也可以分组统计groupByXX,一般最终结果都是Map类型;forEach也是一个终结操作,表示消费流元素,一般用于打印。
整个流式过程是惰性的,什么是惰性?中间操作不会马上发生,只有触发了终结操作,数据源才会按照设计好的(自己定义的一系列过滤filter、转换映射map)流程进行调用。
中间操作可以使用多次,每次返回一个新的流;终结操作只能使用一次,使用完,流也就关闭了。流式操作不会改变原有的数据源,所以可以放心大胆地使用,它只会给你提供一个的结果。很好地保护了数据源。
本订单服务类,设计了常见的题目,用于展示Stream流式API的强大。基本,数据库层面编写sql进行数据统计的业务需求,使用StreamAPI也可以搞定,比如:where过滤、group by 分组、order by排序、max、min、count、sum等聚合统计等。
package com.water.base.lambda; import java.time.LocalDateTime; import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.Map.Entry.comparingByValue; public class OrderService { private final List<Order> orders = List.of( new Order ( "001" , "userA" , 12.8 , LocalDateTime.now().minusDays( 7 ), "PAID" ), new Order ( "002" , "userB" , 22.8 , LocalDateTime.now().minusDays( 14 ), "PAID" ), new Order ( "003" , "userA" , 16.97 , LocalDateTime.now().minusDays( 6 ), "PENDING" ), new Order ( "004" , "userC" , 19.81 , LocalDateTime.now().minusDays( 1 ), "PAID" ), new Order ( "005" , "userC" , 32.18 , LocalDateTime.now().minusDays( 12 ), "PAID" ), new Order ( "006" , "userB" , 42.12 , LocalDateTime.now().minusDays( 5 ), "PAID" ) ); // 1、获取已支付订单列表 public List<Order> getOrdersOnPaid () { return orders .stream() .filter(item -> "PAID" .equals(item.status())) .toList(); } // 2、统计每个用户的订单数量,降序排列 public Map<String, Long> countUserOrderNum () { return getOrdersOnPaid() .stream() .filter(o -> o.userId() != null ) .collect(Collectors.groupingBy( Order::userId, TreeMap:: new , Collectors.counting())) .entrySet() .stream() .sorted(Map.Entry.<String, Long>comparingByValue().reversed()) .collect(Collectors.toMap( Map.Entry<String, Long>::getKey, Map.Entry<String, Long>::getValue, (oldVal, newVal) -> oldVal, LinkedHashMap:: new )); } // 3、按用户分组计算订单总金额 public Map<String, Double> getUserTotalAmountMap () { return getOrdersOnPaid() .stream() .collect(Collectors.groupingBy(Order::userId, Collectors.summingDouble(Order::amount))); } // 4、获取近七天内的订单 public List<Order> listIn7Days () { LocalDateTime sevenDaysAgo = LocalDateTime.now().minusDays( 7 ); return getOrdersOnPaid() .stream() .filter(o -> o.createAt().isAfter(sevenDaysAgo)) .toList(); } // 5、计算每位用户的平均订单金额 public Map<String, Double> getAvgAmountForUserId () { return getOrdersOnPaid() .stream() .collect(Collectors.groupingBy(Order::userId, Collectors.averagingDouble(Order::amount))); } // 6、计算每位用户的最高金额订单 public Map<String, Double> getMaxAmountForUser () { return getOrdersOnPaid() .stream() .collect(Collectors.groupingBy(Order::userId, Collectors.maxBy(Comparator.comparingDouble(Order::amount)) )) .entrySet() .stream() .collect(Collectors.toMap( Map.Entry::getKey, e -> e.getValue().map(Order::amount).orElse( 0.0 ) )); } // 7、获取特殊userID public String getSpecUserId () { return getOrdersOnPaid() .stream() .skip( 2L ) // 跳过前两个 .peek(System.out::println) // 打印 .filter(t -> t.amount() > 40 ) .peek(System.out::println) // 看看过滤后有哪些订单 .map(Order::userId) .peek(u -> { System.out.println( "id: " + u); System.out.println( "下一个是什么?这里peek方法可用于调试流式中间操作,看看目前有什么元素" ); }) .sorted() .findFirst() .orElse( "defaultUser" ); } // 使用函数式接口构建组合查询条件 public List<Order> getOrderByCondition (OrderPredicate orderPredicate) { return orders .stream() .filter(orderPredicate::test) // 方法引用(对象引用实例方法),格式匹配,并不是调用方法test .toList(); } public List<Order> getOrderByCondition2 (Predicate<Order> orderPredicate) { return orders .stream() .filter(orderPredicate) // 就是该类型 Predicate<Order> .toList(); } }
1.3 自定义订单过滤器
这是独属于Order订单入参的过滤器,单个抽象方法,所以是函数式接口,其实可以使用Predicate<Order>代替,效果等同。但,这么设计的好处是清晰明了可扩展其他默认方法等,和服务方法更密切。
接口的默认方法,只能是实例对象调用,所以可以理解是给所有实现类准备的公共方法。条件判断业务逻辑,基础而核心,使用【&&、||、!】对应【且、或、非(取反)】嵌套条件,进行链式调用,非常方便优雅。
注意,进行默认方法实现的时候,因为返回是函数接口,所以直接使用Lambda表达式,并且告诉自己,(参数)-> {实现逻辑},参数是形参,方法的设计在函数接口中,不涉及实参。实参,是真正调用的时候,外界会传进来,或者跟着【背包】进来,默认final修饰或值不变的变量。
package com.water.base.lambda; // 自定义函数式接口,订单过滤器 public interface OrderPredicate { boolean test (Order order) ; default OrderPredicate and (OrderPredicate and) { // 且 return o->test(o) && and.test(o); } default OrderPredicate or (OrderPredicate or) { // 或 return o -> test(o) || or.test(o); } default OrderPredicate not () { // 非 return o -> !test(o); } }
1.4 调用测试
在main方法中,new订单服务,然后进行调用打印。最后涉及过滤条件的方法getOrderByCondition或getOrderByCondition2可以自由组合过滤条件进行测试,可以直观地感受到函数式接口的魅力。
package com.water.base.lambda; import java.time.LocalDateTime; import java.util.Map; import java.util.function.Predicate; import java.util.stream.Stream; public class OrderTest { public static void main (String[] args) { OrderService orderService = new OrderService (); // 1、获取用户已支付订单列表 orderService.getOrdersOnPaid().forEach(System.out::println); // 2、统计每个用户的订单数量,降序 System.out.println( "用户ID: 数量" ); orderService.countUserOrderNum().forEach((k, v) -> System.out.println(k + ": " + v)); printLine(); // 3、按用户分组计算订单总金额 System.out.println( "用户ID: 订单总额(元)" ); orderService.getUserTotalAmountMap().forEach((k, v) -> System.out.println(k + ": " + v)); printLine(); // 4、获取最近7天内的订单 System.out.println( "七天内的订单列表:" ); System.out.println(orderService.listIn7Days()); printLine(); // 5、计算每位用户的平均订单金额 System.out.println( "用户ID: 订单均值(元)" ); orderService.getAvgAmountForUserId().forEach((k, v) -> System.out.println(k + ": " + v)); printLine(); // 6、 计算每位用户的最高订单金额 System.out.println( "用户ID: 订单金额(元)" ); orderService.getMaxAmountForUser().forEach((k, v) -> System.out.println(k + ": " + v)); printLine(); // 7、获取特殊userID System.out.println( "特殊userID:" + orderService.getSpecUserId()); printLine(); // 8、获取userId = userA或userB的,已支付订单 OrderPredicate c1 = o -> "PAID" .equals(o.status()) && ( "userA" .equals(o.userId()) || "userB" .equals(o.userId())); Predicate<Order> c2 = o -> "PAID" .equals(o.status()) && ( "userA" .equals(o.userId()) || "userB" .equals(o.userId())); System.out.println(orderService.getOrderByCondition(c1)); printLine(); System.out.println(orderService.getOrderByCondition2(c2)); printLine(); // 添加一个条件:订单金额大于20 OrderPredicate c3 = o -> o.amount() > 20 ; // 添加一个条件:订单金额小于40,引用外面静态变量c33 OrderPredicate c4 = c1.and(c3).or(c33).not(); // 等同理解 && || !,布尔表达式 System.out.println(orderService.getOrderByCondition(c4)); printLine(); } // 定义订单过滤器,本质是一个只有一个抽象接口的实现,匿名内部类-》匿名对象-》lambda化 static OrderPredicate c33 = o -> o.amount() < 40 ; // 定义订单过滤器,lambda化-》方法引用(复用已有方法) static OrderPredicate c44 = OrderTest::over50; public static boolean over50 (Order order) { return order != null && order.amount() > 50 ; } public static void printLine () { System.out.println( "==============================================================================" ); } }
1.5 某次结果输出
每个“===”是一个分隔标记,可以边改变方法,边看测试,验证文中的理论,印象更深刻。以下是某次调试结果:
Order[orderId= 001 , userId=userA, amount= 12.8 , createAt= 2025 - 07 -12T18: 43 : 57.361465200 , status=PAID] Order[orderId= 002 , userId=userB, amount= 22.8 , createAt= 2025 - 07 -05T18: 43 : 57.361465200 , status=PAID] Order[orderId= 004 , userId=userC, amount= 19.81 , createAt= 2025 - 07 -18T18: 43 : 57.361465200 , status=PAID] Order[orderId= 005 , userId=userC, amount= 32.18 , createAt= 2025 - 07 -07T18: 43 : 57.361465200 , status=PAID] Order[orderId= 006 , userId=userB, amount= 42.12 , createAt= 2025 - 07 -14T18: 43 : 57.361465200 , status=PAID] 用户ID: 数量 userB: 2 userC: 2 userA: 1 ============================================================================== 用户ID: 订单总额(元) userA: 12.8 userB: 64.91999999999999 userC: 51.99 ============================================================================== 七天内的订单列表: [Order[orderId= 004 , userId=userC, amount= 19.81 , createAt= 2025 - 07 -18T18: 43 : 57.361465200 , status=PAID], Order[orderId= 006 , userId=userB, amount= 42.12 , createAt= 2025 - 07 -14T18: 43 : 57.361465200 , status=PAID]] ============================================================================== 用户ID: 订单均值(元) userA: 12.8 userB: 32.459999999999994 userC: 25.995 ============================================================================== 用户ID: 订单金额(元) userA: 12.8 userB: 42.12 userC: 32.18 ============================================================================== Order[orderId= 004 , userId=userC, amount= 19.81 , createAt= 2025 - 07 -18T18: 43 : 57.361465200 , status=PAID] Order[orderId= 005 , userId=userC, amount= 32.18 , createAt= 2025 - 07 -07T18: 43 : 57.361465200 , status=PAID] Order[orderId= 006 , userId=userB, amount= 42.12 , createAt= 2025 - 07 -14T18: 43 : 57.361465200 , status=PAID] Order[orderId= 006 , userId=userB, amount= 42.12 , createAt= 2025 - 07 -14T18: 43 : 57.361465200 , status=PAID] id: userB 下一个是什么?这里peek方法可用于调试流式中间操作,看看目前有什么元素 特殊userID:userB ============================================================================== [Order[orderId= 001 , userId=userA, amount= 12.8 , createAt= 2025 - 07 -12T18: 43 : 57.361465200 , status=PAID], Order[orderId= 002 , userId=userB, amount= 22.8 , createAt= 2025 - 07 -05T18: 43 : 57.361465200 , status=PAID], Order[orderId= 006 , userId=userB, amount= 42.12 , createAt= 2025 - 07 -14T18: 43 : 57.361465200 , status=PAID]] ============================================================================== [Order[orderId= 001 , userId=userA, amount= 12.8 , createAt= 2025 - 07 -12T18: 43 : 57.361465200 , status=PAID], Order[orderId= 002 , userId=userB, amount= 22.8 , createAt= 2025 - 07 -05T18: 43 : 57.361465200 , status=PAID], Order[orderId= 006 , userId=userB, amount= 42.12 , createAt= 2025 - 07 -14T18: 43 : 57.361465200 , status=PAID]] ============================================================================== [] ==============================================================================
二、函数
函数,数学上:y = f(x),通过x入参,执行f函数(方法、规则)运算,得到y。函数,具有无副作用的特点,意味着无论调用多少次,都不会产生其他影响,能影响y的只有x,f作为规则存在,不随时间改变,就像是制定好流程,按部就班执行,只是input输入不同,导致output输出改变而已。所以,函数是无情的。
2.1 无状态
无状态,其实也就是无副作用,函数执行不涉及状态改变。在编程中,函数也是方法的一种表现形式,只是方法有些是不纯的,执行了方法,可能会对其他成员属性,对象属性,变量产生影响,这就是副作用。
所谓状态,指的是对象属性值的变化,受到影响变化了(如:set变量值,直接赋值),那么就说该对象的状态发生了变化,使之改变的方法,不是函数,但其也是可以使用Lambda表达式,因为Lambda表达式跟形式、可推导有关,跟内部实现导致的状态变化无关。
示例代码:
public class Student { // 状态(属性值)只有行为(方法)才能改变 private int stuNo; priavte String name; private int countSetInvokeName; private static int num = 1 ; public static int nextNo () { // 每调用一次,num+1,改变了num属性,状态发生变化 return num++; } public void setName () { // 该方法就是专门用来改变对象状态的,setXX,而且是改变两个属性值 this .name = name; countSetInvokeName++; } public String getName () { // 该方法只是一个获取属性信息,没有改变对象的状态,无状态 return name; } }
2.2 不变性final
那么Lambda表达式如果需要携带外部实参进入方法实现里面,该怎么办呢?这种情况一般发生调用的方法体里面,进行Lambda表达式设计{实现逻辑},需要使用到业务逻辑处理过程产生的局部变量,或成员变量,那么这时就得使用【背包】技术了,其实就是该实参变量一般使用final显式修饰,一般基础类型,都得final修饰,如:
student类:
public class Student { private String name; private int age; public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } public void setAge ( int age) { this .age = age; } @Override public String toString () { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}' ; }
测试类:
public class LambdaTest2 { public static void main (String[] args) { // 1、基础数据类型,如:int、byte、short、long这些,若要背包,建议添加final显式修饰,避免后续更改值报错 int a = 100 ; final int b = 10 ; // final 修饰,不变性,才能进入到lambda方法体内,不变才能成为规则的一部分 int result = getResult(a,()->{ System.out.println( "从外面带过来的变量值:" +b); return 99 *b; }); // b = 21; // b若没final修饰,则可以任意改变,那么此时lambda里面的b也会改变,所以是不允许的 System.out.println( "结果:" +result); // 2、对象的地址值,不改变即可,对象属性可以改变 final Student student = new Student (); // final可以显式修饰避免修改引用值,如果省略,需要保证隐式final(也就是不再将student指向其他对象地址值) student.setName( "water" ); student.setAge( 18 ); System.out.println( "学生A初始信息:" +student.getName()+ ", " +student.getAge()); System.out.println( "=============" ); // student = new Student(); // 不可以再次改变student变量的引用地址值, int result2 = getResult(student.getAge(),()->{ System.out.println( "学生A进来了:" +student.getName()+ ", " +student.getAge()); student.setName( "Bob" ); student.setAge( 20 ); System.out.println( "学生A信息(状态)变更:" +student.getName()+ ", " +student.getAge()); return student.getAge(); }); System.out.println( "结果2:" +result2); System.out.println( "现在学生A的信息:" +student.getName()+ ", " +student.getAge()); } public static int getResult ( int num, Supplier<Integer> randomNumFactory) { return randomNumFactory == null ? num : num + randomNumFactory.get(); } } }
输出:
从外面带过来的变量值: 10 结果: 1090 学生A初始信息:water, 18 ============= 学生A进来了:water, 18 学生A信息(状态)变更:Bob, 20 结果 2 : 38 现在学生A的信息:Bob, 20
三、函数接口
函数接口:只有一个抽象方法的接口,使用@FuntionInterface修饰,则可以使用Lambda表达式简化,突出抽象方法的实现。JDK8+可以有default默认方法(实例对象调用),static静态方法(接口.xx调用)。
3.1 消费者Consumer
package java.util.function; import java.util.Objects; /** * Represents an operation that accepts a single input argument and returns no * result. Unlike most other functional interfaces, { @code Consumer} is expected * to operate via side-effects. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is { @link #accept(Object)}. * * @param <T> the type of the input to the operation * * @since 1.8 */ @FunctionalInterface public interface Consumer <T> { /** * Performs this operation on the given argument. * * @param t the input argument */ void accept (T t) ; /** * Returns a composed { @code Consumer} that performs, in sequence, this * operation followed by the { @code after} operation. If performing either * operation throws an exception, it is relayed to the caller of the * composed operation. If performing this operation throws an exception, * the { @code after} operation will not be performed. * * @param after the operation to perform after this operation * @return a composed { @code Consumer} that performs in sequence this * operation followed by the { @code after} operation * @throws NullPointerException if { @code after} is null */ default Consumer<T> andThen (Consumer<? super T> after) { // 一个接一个消费,可以搞任务执行链 Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
抽象方法:void accept(T t);
默认方法:andThen,一个接一个消费,可以setA-》setB-》等等,任务执行链等,返回组合后的消费者(A-B-C-D)按顺序执行,有一个抛异常,则后面都不会执行。你可以选择继承然后添加新的默认方法andThenWithNoThrowException,选择不抛异常;
Lambda表达式:t -> {} ;
出入参:泛型T,一个入参,无返回;
类比:setter方法,使用入参去赋值属性;
思想:消耗很多物资,却一毛不拔,不回礼;
代码示例:
public class FunctionInterfaceTest { public static void main (String[] args) { // 体会消费接口行为参数化,行为在方法体内执行,实参传递方式,分为外面和里面 test_consume( "appleCake" , // 外面入参 new BigDecimal ( "20" ), // 外面入参 list -> { // 自定义消费者实现 System.out.println( "一共去了" + list.size() + "个地方玩!" ); StringJoiner joiner = new StringJoiner ( "," , "*****" , "*****" ); list.forEach(joiner::add); System.out.println( "这些地方如下:" + joiner); }, List.of( "Beijing" , "Guangzhou" , "Shenzhen" , "Shanghai" ) // 提供给上述消费者实现的实参 ); } private static void printLine () { System.out.println( "=============================================================" ); } // 添加final 修饰表示不可变,便于方法体内其他消费实现使用 public static void test_consume ( final String foodInput, final BigDecimal priceInput, Consumer<List<String>> haveFunWith, final List<String> places) { // case1:定义consume实现[吃东西] Consumer<String> eatFood = food -> { System.out.println( "give " + food + " some ice, please" ); System.out.println( "Tell you boy, I just ate something like " + food); }; // 调用 eatFood.accept(foodInput); printLine(); // case2:定义consumer实现【花多少钱】 Consumer<BigDecimal> howMuch = price -> { BigDecimal result = switch (foodInput) { // 使用背包,里面的不可变局部变量 case "apple" -> BigDecimal.ONE.multiply(price); case "applePile" -> BigDecimal.TWO.multiply(price); case "appleCake" -> BigDecimal.TEN.multiply(price); default -> price; }; System.out.println(foodInput + " cost me " + result + " $" ); }; // 调用 howMuch.accept(priceInput); printLine(); // case3:方法外定义Consumer实现【去了哪些地方玩】 Objects.requireNonNull(haveFunWith, "input can not be null" ); haveFunWith.accept(places); } }
输出:
give appleCake some ice, please Tell you boy, I just ate something like appleCake ============================================================= appleCake cost me 200 $ ============================================================= 一共去了 4 个地方玩! 这些地方如下:*****Beijing,Guangzhou,Shenzhen,Shanghai*****
3.2 生产者Supplier
源码:
/** * Represents a supplier of results. * * <p>There is no requirement that a new or distinct result be returned each * time the supplier is invoked. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is { @link #get()}. * * @param <T> the type of results supplied by this supplier * * @since 1.8 */ @FunctionalInterface public interface Supplier <T> { /** * Gets a result. * * @return a result */ T get () ; }
格式:T get()
出入参:0参有返回
类比:getter方法,无参构造this::new,提供者
场景:xx工厂提供xx,线程提供者(类似线程工厂)
示例代码:
package com.water.base.lambda; import java.util.Arrays; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; public class _02_SupplierTest { public static void main (String[] args) throws InterruptedException { // 打印随机名称 String[] names = { "Amy" , "John" }; System.out.println(getRandomName(names)); System.out.println(getRandomName( null )); printLine(); // 获取线程t1,执行打印任务 Thread t1 = newThread( "water_Thread" , () -> { System.out.println(Thread.currentThread().getName() + " print something..." ); System.out.println( "result: " + Arrays.toString(names)); }); // 获取线程t2,执行打印任务:每隔一秒打印一次 Thread t2 = newThread( null , () -> { int loop = 10 ; int count = 1 ; while (count <= loop) { try { TimeUnit.SECONDS.sleep( 1L ); } catch (InterruptedException e) { throw new RuntimeException (e); } System.out.println(Thread.currentThread().getName() + " print num: " + count); count++; } }); // 定义根据随机名称长度*100,获得多少钱 Supplier<Integer> moneyWithNameLenMultiple = ()->names[random.nextInt( 0 , names.length)].length() * 100 ; System.out.println( "获得多少钱:" +getWhatYouWant(moneyWithNameLenMultiple)); printLine(); // 定义任务生产者,打印加了多少钱 Supplier<Runnable> addMoneyTask = ()-> ()-> System.out.println(Thread.currentThread().getName()+ " 加了多少钱:" +names.length + 100 ); Thread t3 = newThread( "addMoney" , addMoneyTask.get()); // 编排线程执行顺序 System.out.println( "执行线程t2" ); t2.start(); t2.join(); printLine(); System.out.println( "执行线程t1" ); t1.start(); t1.join(); printLine(); System.out.println( "执行线程3" ); t3.start(); t3.join(); printLine(); System.out.println( "over ..." ); } static final Random random = new Random (); // 1、定义生产者【产生个随机名字】 public static String getRandomName (String[] input) { if (input == null ) { input = new String [] { "water" , "Bob" , "Mary" , "Mike" }; } final String[] names = input; Supplier<String> nameSupplier = ()-> names[random.nextInt( 0 ,names.length) % names.length]; return nameSupplier.get(); } // 2、定义生产者【提供新线程】 public static Thread newThread (String threadName, Runnable task) { if (threadName == null ) { threadName = System.currentTimeMillis()+ "_Thread" ; } final String finalName = threadName; Supplier<Thread> threadSupplier = ()-> new Thread (task,finalName); return threadSupplier.get(); } // 3、自定义生产者,总是返回调用者期待的 public static <T> T getWhatYouWant (Supplier<T> supplier) { return supplier.get(); } private static void printLine () { System.out.println( "=====================================================" ); } }
输出:
Amy water ===================================================== 获得多少钱: 400 ===================================================== 执行线程t2 1752983593563_Thread print num: 1 1752983593563_Thread print num: 2 1752983593563_Thread print num: 3 1752983593563_Thread print num: 4 1752983593563_Thread print num: 5 1752983593563_Thread print num: 6 1752983593563_Thread print num: 7 1752983593563_Thread print num: 8 1752983593563_Thread print num: 9 1752983593563_Thread print num: 10 ===================================================== 执行线程t1 water_Thread print something... result: [Amy, John] ===================================================== 执行线程 3 addMoney 加了多少钱: 2100 ===================================================== over ...
3.3 断言Predicate
源码:
package java.util.function; import java.util.Objects; /** * Represents a predicate (boolean-valued function) of one argument. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is { @link #test(Object)}. * * @param <T> the type of the input to the predicate * * @since 1.8 */ @FunctionalInterface public interface Predicate <T> { /** * Evaluates this predicate on the given argument. * * @param t the input argument * @return { @code true} if the input argument matches the predicate, * otherwise { @code false} */ boolean test (T t) ; /** * Returns a composed predicate that represents a short-circuiting logical * AND of this predicate and another. When evaluating the composed * predicate, if this predicate is { @code false}, then the { @code other} * predicate is not evaluated. * * <p>Any exceptions thrown during evaluation of either predicate are relayed * to the caller; if evaluation of this predicate throws an exception, the * { @code other} predicate will not be evaluated. * * @param other a predicate that will be logically-ANDed with this * predicate * @return a composed predicate that represents the short-circuiting logical * AND of this predicate and the { @code other} predicate * @throws NullPointerException if other is null */ default Predicate<T> and (Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } /** * Returns a predicate that represents the logical negation of this * predicate. * * @return a predicate that represents the logical negation of this * predicate */ default Predicate<T> negate () { return (t) -> !test(t); } /** * Returns a composed predicate that represents a short-circuiting logical * OR of this predicate and another. When evaluating the composed * predicate, if this predicate is { @code true}, then the { @code other} * predicate is not evaluated. * * <p>Any exceptions thrown during evaluation of either predicate are relayed * to the caller; if evaluation of this predicate throws an exception, the * { @code other} predicate will not be evaluated. * * @param other a predicate that will be logically-ORed with this * predicate * @return a composed predicate that represents the short-circuiting logical * OR of this predicate and the { @code other} predicate * @throws NullPointerException if other is null */ default Predicate<T> or (Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } /** * Returns a predicate that tests if two arguments are equal according * to { @link Objects#equals(Object, Object)}. * * @param <T> the type of arguments to the predicate * @param targetRef the object reference with which to compare for equality, * which may be { @code null} * @return a predicate that tests if two arguments are equal according * to { @link Objects#equals(Object, Object)} */ static <T> Predicate<T> isEqual (Object targetRef) { return ( null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); } /** * Returns a predicate that is the negation of the supplied predicate. * This is accomplished by returning result of the calling * { @code target.negate()}. * * @param <T> the type of arguments to the specified predicate * @param target predicate to negate * * @return a predicate that negates the results of the supplied * predicate * * @throws NullPointerException if target is null * * @since 11 */ @SuppressWarnings("unchecked") static <T> Predicate<T> not (Predicate<? super T> target) { Objects.requireNonNull(target); return (Predicate<T>)target.negate(); } }
格式:boolean test(T t);
理解:过滤器,入参可以是一个实体对象,根据其属性过滤
类比:过滤网
场景:订单过滤,权限过滤,filter
示例代码:
package com.water.base.lambda; import java.time.LocalDateTime; import java.util.List; import java.util.function.Predicate; /** * 断言,过滤器 * 场景:员工表,根据各种条件过滤员工信息 */ public class _03_PredicateTest { private static final LocalDateTime now = LocalDateTime.now(); private static final List<Person> PERSON_LIST = List.of( new Person ( "001" , "p1" ,Status.WORK.name(),now.minusDays( 7 )), new Person ( "002" , "p2" ,Status.LEAVE.name(),now.minusDays( 5 )), new Person ( "003" , "p3" ,Status.WORK.name(),now.minusDays( 5 )), new Person ( "004" , "p4" ,Status.LEAVE.name(),now.minusDays( 2 )), new Person ( "005" , "p5" ,Status.LEAVE.name(),now.minusDays( 5 )) ); enum Status { WORK, // 在职 LEAVE // 离职 } public static void main (String[] args) { // 1、查询全部在职员工 System.out.println(getByCondition(p-> Status.WORK.name().equals(p.status()))); printLine(); // 2、查询2天前入职的员工 System.out.println(getByCondition(p->p.join().isBefore(LocalDateTime.now().minusDays( 2 )))); printLine(); // 3、最近3天入职,但现在已离职的员工 Predicate<Person> c1 = p->p.join().isAfter(LocalDateTime.now().minusDays( 3 )); Predicate<Person> c2 = p->Status.LEAVE.name().equals(p.status()); System.out.println(getByCondition(c1.and(c2))); } private static void printLine () { System.out.println( "=====================================================" ); } public static List<Person> getByCondition (Predicate<Person> condition) { return PERSON_LIST .stream() .filter(condition) .toList(); } } record Person (String id, String name, String status, LocalDateTime join) {}
输出:
[Person[id= 001 , name=p1, status=WORK, join= 2025 - 07 -13T17: 05 : 44.181833600 ], Person[id= 003 , name=p3, status=WORK, join= 2025 - 07 -15T17: 05 : 44.181833600 ]] ===================================================== [Person[id= 001 , name=p1, status=WORK, join= 2025 - 07 -13T17: 05 : 44.181833600 ], Person[id= 002 , name=p2, status=LEAVE, join= 2025 - 07 -15T17: 05 : 44.181833600 ], Person[id= 003 , name=p3, status=WORK, join= 2025 - 07 -15T17: 05 : 44.181833600 ], Person[id= 004 , name=p4, status=LEAVE, join= 2025 - 07 -18T17: 05 : 44.181833600 ], Person[id= 005 , name=p5, status=LEAVE, join= 2025 - 07 -15T17: 05 : 44.181833600 ]] ===================================================== [Person[id= 004 , name=p4, status=LEAVE, join= 2025 - 07 -18T17: 05 : 44.181833600 ]]
3.4 映射转换Function
源码:
package java.util.function; import java.util.Objects; /** * Represents a function that accepts one argument and produces a result. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is { @link #apply(Object)}. * * @param <T> the type of the input to the function * @param <R> the type of the result of the function * * @since 1.8 */ @FunctionalInterface public interface Function <T, R> { /** * Applies this function to the given argument. * * @param t the function argument * @return the function result */ R apply (T t) ; /** * Returns a composed function that first applies the { @code before} * function to its input, and then applies this function to the result. * If evaluation of either function throws an exception, it is relayed to * the caller of the composed function. * * @param <V> the type of input to the { @code before} function, and to the * composed function * @param before the function to apply before this function is applied * @return a composed function that first applies the { @code before} * function and then applies this function * @throws NullPointerException if before is null * * @see #andThen(Function) */ default <V> Function<V, R> compose (Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } /** * Returns a composed function that first applies this function to * its input, and then applies the { @code after} function to the result. * If evaluation of either function throws an exception, it is relayed to * the caller of the composed function. * * @param <V> the type of output of the { @code after} function, and of the * composed function * @param after the function to apply after this function is applied * @return a composed function that first applies this function and then * applies the { @code after} function * @throws NullPointerException if after is null * * @see #compose(Function) */ default <V> Function<T, V> andThen (Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } /** * Returns a function that always returns its input argument. * * @param <T> the type of the input and output objects to the function * @return a function that always returns its input argument */ static <T> Function<T, T> identity () { return t -> t; } }
格式:U apply(T t);
类比:映射器、转换器、计算器、有输入输出;
场景:获取商品Map,用于前端下拉展示;数据转换,字符串“20”-》数值 20
代码示例:
package com.water.base.lambda; import java.math.BigDecimal; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; /** * 映射器、转换器、计算器 * 场景:商品处理 */ public class _04_FunctionTest { private static final List<Goods> GOODS_LIST = List.of( new Goods ( "G001" , "牛奶" , new BigDecimal ( "4.5" ), 200 , "盒" ,GoodsStatus.UP.name()), new Goods ( "G002" , "旺旺饼干" , new BigDecimal ( "8.5" ), 100 , "包" ,GoodsStatus.DOWN.name()), new Goods ( "G003" , "冰红茶" , new BigDecimal ( "3.5" ), 150 , "瓶" ,GoodsStatus.UP.name()), new Goods ( "G004" , "巧克力" , new BigDecimal ( "5.0" ), 10 , "块" ,GoodsStatus.DOWN.name()), new Goods ( "G005" , "棒棒糖" , new BigDecimal ( "0.5" ), 250 , "个" ,GoodsStatus.UP.name()) ); enum GoodsStatus { UP, // 上架 DOWN // 下架 } public static void main (String[] args) { // 1、获取所有商品价格 System.out.println( "商品价格列表:" ); System.out.println(func(Goods::price)); printLine(); // 2、获取所有商品信息列表,如:牛奶-15元/件-剩余80件 System.out.println( "商品信息:" ); System.out.println(func(g-> String.format( "%s-%.2f元/%s-库存剩余%d%s" , g.name(),g.price().doubleValue(),g.unit(),g.num(),g.unit()))); printLine(); // 3、获取商品下拉列表map System.out.println( "ID: 名称" ); getGoodsNameMap().forEach((k,v)-> System.out.println(k+ ": " +v)); printLine(); // 4、获取所有下架商品 System.out.println( "所有下架商品:" ); GOODS_LIST.stream().filter(g->GoodsStatus.DOWN.name().equals(g.status())) .map(Goods::name) .forEachOrdered(System.out::println); } private static void printLine () { System.out.println( "====================================================" ); } public static Map<String,String> getGoodsNameMap () { return GOODS_LIST .stream() .collect(Collectors.toMap(Goods::id,Goods::name)); } public static <T> List<T> func (Function<Goods,T> handler) { return GOODS_LIST .stream() .map(handler) .toList(); } } record Goods (String id, String name, BigDecimal price, Integer num,String unit, String status) {}
输出:
商品价格列表: [ 4.5 , 8.5 , 3.5 , 5.0 , 0.5 ] ==================================================== 商品信息: [牛奶- 4.50 元/盒-库存剩余 200 盒, 旺旺饼干- 8.50 元/包-库存剩余 100 包, 冰红茶- 3.50 元/瓶-库存剩余 150 瓶, 巧克力- 5.00 元/块-库存剩余 10 块, 棒棒糖- 0.50 元/个-库存剩余 250 个] ==================================================== ID: 名称 G002: 旺旺饼干 G001: 牛奶 G004: 巧克力 G003: 冰红茶 G005: 棒棒糖 ==================================================== 所有下架商品: 旺旺饼干 巧克力
四、Lambada表达式
Lambda表达式:针对函数接口的实现类,使用时的简化,使用箭头函数表示接口实现类的匿名对象。根据可推导即可省略原则,可以省略()、return、{}等符号,唯一需要注意的是其Lambda表达式形式,一定是和抽象方法出入参形式一样的。
-
- 0参无返回,
() -> {...},如:Runnable线程执行任务的逻辑
- 0参无返回,
-
- 1参无返回,
t -> {...},如:Consumer消费者
- 1参无返回,
-
- 0参有返回,
() -> a,如:Supplier生产者(提供者)
- 0参有返回,
-
- 1参有返回,
t -> u,如:Function映射转换计算(输入输出模式)
- 1参有返回,
-
- 多参无返回,
(a,b)-> {...},如:BiConsumer两个入参的消费者
- 多参无返回,
-
- 其他格式,根据函数接口匹配
注意:
-
- 只有一个入参,()可省略
-
- 方法体只有一条语句,{} 可以省略
-
- 方法体只有一条语句,return可以省略
-
- 多个参数使用(),逗号隔开,并且方法体内使用时注意形参的数据类型,是根据抽象方法入参顺序来的,一一对应
-
- 方法体内,若需要外部变量,则该变量必须final显式修饰,若不final修饰也得隐式保证,即后续不更改该变量值,这里final的是变量值,对象则是地址值(所以对象属性可以改变),是为了便于将该变量放在【背包】里面,随着该Lambda表达式(匿名对象)随处传递,其实就是拷贝了一个不可变的副本。
-
- 抽象方法实现,和普通方法实现没什么两样,需要注意的是Lambda表达式形参的顺序,形参的数据类型。注意分清楚声明定义和调用。
-
- Lambda表达式代表的匿名对象,先声明定义,然后在合适的时机调用
-
- 方法入参时的Lambda表达式,就是抽象方法的一种实现!
五、方法引用
方法引用:对Lambda表达式的进一步简化,对已有方法的复用,因为Lambda表达式和已有方法的形式、结构相匹配,所以可以直接引用::过来。因为类加载时,其方法信息已经存储在方法区,所以JVM可以很容易就获取方法信息,然后作为函数接口抽象方法的一种实现。
-
- 静态方法引用,类名::静态方法
-
- 实例方法引用,类名::实例方法
-
- this引用,this::实例方法,引用本类实例方法,在实例方法中使用
-
- super引用,super::实例方法,引用父类实例方法,在子类实例方法中使用
代码示例
- super引用,super::实例方法,引用父类实例方法,在子类实例方法中使用
package com.water.base.lambda; import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; public class RefTest { // 对象内属性:姓名 private String name; // 无参构造 public RefTest () {} // 有参构造 public RefTest (String name) { this .name = name; } public void setName (String name) { this .name = name; // 在实例方法中引用其他实例方法,this Supplier<Integer> lenSupplier = this ::nameLength; System.out.println( "名称长度:" +lenSupplier.get()); } // 实例方法:获取姓名的长度 public Integer nameLength () { Objects.requireNonNull(name, "name is null" ); Function<RefTest,Integer> lenSupplier = RefTest::count; System.out.println( "静态方法count引用,名称长度是多少:" +lenSupplier.apply( this )); return this .name.length(); } // 实例方法:姓名长度是否超过5 public boolean isNameLengthOver5 () { Predicate<RefTest> judge = RefTest::judgeOver5; System.out.println( "静态方法JudgeOver5引用,名称长度是否大于5:" +judge.test( this )); return this .name != null && this .name.length() > 5 ; } @Override public String toString () { // 在实例方法中引用父类实例方法,super Supplier<String> superObjIndex = super ::toString; System.out.println( "地址:" +superObjIndex.get()); return name != null ? name : "defaultName" ; } public static RefTest of () { return new RefTest (); } // 静态方法:统计内容长度 public static Integer count (RefTest content) { return content == null ? 0 : (content.name == null ? 0 : content.name.length()); } // 静态方法:判断内容超过5 public static boolean judgeOver5 (RefTest content) { return content != null && content.name != null && content.name.length() > 5 ; } public static void main (String[] args) { // 定义生产者 Supplier<RefTest> s1 = ()-> new RefTest (); Supplier<RefTest> s2 = ()-> new RefTest ( "water" ); Supplier<RefTest> s3 = RefTest:: new ; Supplier<RefTest> s4 = RefTest::of; // 使用生产者 RefTest r1 = s1.get(); RefTest r2 = s2.get(); RefTest r3 = s3.get(); RefTest r4 = s4.get(); System.out.println( "r1:" +r1); System.out.println( "r2:" +r2); System.out.println( "r3:" +r3); System.out.println( "r4:" +r4); printLine(); // 2、定义消费者 Consumer<RefTest> c1 = t -> t.setName( "Kobe Brand" ); // 使用 c1.accept(r1); System.out.println( "r1被消费后:" +r1); printLine(); // 3、定义断言 Predicate<RefTest> p1 = p->p.name.length() > 5 ; Predicate<RefTest> p2 = RefTest::isNameLengthOver5; Predicate<RefTest> p3 = RefTest::judgeOver5; // 使用 System.out.println( "p1: " +p1.test(r1)); System.out.println( "p2: " +p2.test(r2)); System.out.println( "p3: " +p3.test(r3)); printLine(); // 4、定义转换、计算器 Function<RefTest,Integer> f1 = rt -> rt.name.length(); Function<RefTest,Integer> f2 = RefTest::nameLength; Function<RefTest,Integer> f3 = RefTest::count; // 使用 System.out.println( "f1: " +f1.apply(r1)); System.out.println( "f2: " +f2.apply(r2)); System.out.println( "f3: " +f3.apply(r3)); } private static void printLine () { System.out.println( "================================================" ); } }
输出:
地址:com.water.base.lambda.RefTest@39ed3c8d r1:defaultName 地址:com.water.base.lambda.RefTest@71dac704 r2:water 地址:com.water.base.lambda.RefTest@123772c4 r3:defaultName 地址:com.water.base.lambda.RefTest@2d363fb3 r4:defaultName ================================================ 静态方法count引用,名称长度是多少: 10 名称长度: 10 地址:com.water.base.lambda.RefTest@39ed3c8d r1被消费后:Kobe Brand ================================================ p1: true 静态方法JudgeOver5引用,名称长度是否大于 5 : false p2: false p3: false ================================================ f1: 10 静态方法count引用,名称长度是多少: 5 f2: 5 f3: 0
六、参数行为化
参数行为化,指的是函数接口作为方法的参数之一,Lambda的表达式逻辑部分,本质就是抽象方法的实现,也就是行为,所以相当于在方法入参中,增加了一个函数接口类型参数,而该数据类型只有方法,并且只有一个,在实体类中,成员方法一般用于改变成员属性的状态,同时体现对象的一种行为。
综上,参数行为化,是Lambda表达式的强大表现,该行为可以随调用者定义,变的是具体实现,不变的是这个函数接口抽象方法的样式。其实,入参的数据类型为父类、接口,同样有这样的表现形式。但三者有不同:
-
- 入参父类类型,
new Father{覆盖父类方法},匿名子类继承该父类,并且new了个对象
- 入参父类类型,
-
- 入参接口,
new Callback {success、fail},实现success、fail抽象方法,匿名实现类,并且new了个对象
- 入参接口,
-
- 入参函数接口,可以 跟入参接口一样,但只有一个抽象方法;但同时可以使用Lambda表达式简化,若有可复用方法,进一步使用方法引用::更简洁,如:
a-> {a...},String::valueof
- 入参函数接口,可以 跟入参接口一样,但只有一个抽象方法;但同时可以使用Lambda表达式简化,若有可复用方法,进一步使用方法引用::更简洁,如:
代码示例:
package com.water.base.lambda; import java.util.function.Function; import java.util.function.Predicate; /** * 知识点:参数行为化 * 1、参数类型为函数接口 * 2、使用匿名内部类方式 * 3、使用lambda表达式 * 4、使用方法引用 */ public class LambdaAsParamTest { public static void main (String[] args) { // 1、常规参数 invoke( "driver" , new Father (), new DefaultCallback ()); printLine(); // 2、匿名内部类:重写父类方法、实现接口,匿名对象参数 invoke( "doctor" , new Father () { @Override public boolean isGood (String job) { System.out.println( "覆盖父类方法" ); return job != null && job.startsWith( "c" ); // cook } }, new Callback () { @Override public void onSuccess () { System.out.println( "执行成功" ); } @Override public void onFail () { System.out.println( "执行失败" ); } }); printLine(); // 3、函数式接口,lambda表达式,方法引用,参数行为化 final String task = "calOrderAmount" ; call(task, t -> t.toLowerCase().contains( "order" ), LambdaAsParamTest::cal, () -> { System.out.println( "任务操作成功:" + task); System.out.println( "触发配送业务" ); }, () -> { System.out.println( "操作异常" ); System.out.println( "发送短信提醒:" + task + " 执行失败,请查看日志" ); } ); } // 匹配:Function<String,Boolean>,可以使用方法引用 LambdaAsParamTest::cal public static boolean cal (String task) { return task != null && task.length() > 3 ; } private static void printLine () { System.out.println( "====================================================" ); } public static void invoke (String job, Father father, Callback callback) { if (father.isGood(job)) { callback.onSuccess(); } else { callback.onFail(); } } public static void call (String task, Predicate<String> check, Function<String, Boolean> invoke, SuccessCallback success, FailCallback fail) { if (check.test(task)) { Boolean result = invoke.apply(task); if (result) { success.onSuccess(); } else { fail.onFail(); } } else { fail.onFail(); } } } class Father { public boolean isGood (String job) { System.out.println( "I am father" ); return job != null && job.length() > 5 ; } } interface Callback { void onSuccess () ; void onFail () ; } class DefaultCallback implements Callback { @Override public void onSuccess () { System.out.println( "success" ); } @Override public void onFail () { System.out.println( "fail" ); } } @FunctionalInterface interface SuccessCallback { void onSuccess () ; } @FunctionalInterface interface FailCallback { void onFail () ; }
输出:
I am father success ==================================================== 覆盖父类方法 执行失败 ==================================================== 任务操作成功:calOrderAmount 触发配送业务
七、Stream流
StreamAPI可以说是Lambda表达式、方法引用的极致体现,这套API的入参几乎都是函数式接口,中间操作的返回则是返回一个新的Stream流,所以中间操作可以像是链条一样串联起来,像水一样流动,每到一个中间节点,就执行其处理逻辑,而巧妙的是入参是函数式接口,所以就可以让调用者很方便地自定义自己的逻辑,优雅,实在优雅,封装了不变的框架,留给足够的扩展空间,具体实现是什么?交由自己决定,而且表达方式如此简洁。
如果没有函数式接口,Lambda表达式,方法引用这个语法糖,难以想象需要写多少冗余的类模板代码,使用起来就没那么方便了,而且看起来就像是一坨shi。所以,函数式接口,Lambda表达式、方法引用,这些简洁的表达方式,正是StreamAPI的好搭子。
可以说,JDK8中,推出了函数式接口、Lambda表达式规则、方法引用语法糖这套利器,是核心基础;而StreamAPI就是这个理论的实现和践行,同时将for循环遍历处理集合的缺点和繁琐给彻底优化掉,采用StreamAPI既能实现数据集合的处理需求,性能上也有所提升,特别是针对大数据,更是可以采用并行处理方式,底层采用多线程方式处理,更能凸显性能优异。
性能高,同时十分简洁优雅,像是在使用自然语言定义数据处理流程并执行,StreamAPI毫无疑问已经成为了现在Java开发者常用的类库。
7.1 举例说明
这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模型”。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而最右侧的数字3是最终结果。
这里的filter、map、skip都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法count执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。
备注:“Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。
7.2 获取流方式
java.util.stream.Stream<T>是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)
获取一个流非常简单,有以下几种常用的方式:
- 所有的
Collection集合都可以通过stream默认方法获取流; Stream接口的静态方法of可以获取数组对应的流。
方式1 : 根据Collection获取流
首先,java.util.Collection接口中加入了default方法stream用来获取流,所以其所有实现类均可获取流。
public class StreamTest { public static void main (String[] args) { List<String> list = new ArrayList <>(); // ... Stream<String> stream1 = list.stream(); Set<String> set = new HashSet <>(); // ... Stream<String> stream2 = set.stream(); Vector<String> vector = new Vector <>(); // ... Stream<String> stream3 = vector.stream(); } }
方式2 : 根据Map获取流
java.util.Map接口不是Collection的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况:
public class StreamTest { public static void main (String[] args) { Map<String, String> map = new HashMap <>(); // ... Stream<String> keyStream = map.keySet().stream(); Stream<String> valueStream = map.values().stream(); Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream(); } }
方式3 : 根据数组获取流
如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以Stream接口中提供了静态方法of,使用很简单:
public class StreamTest { public static void main (String[] args) { String[] array = { "water" , "Mike" , "Bob" , "Mary" }; Stream<String> stream = Stream.of(array); } }
备注:
of方法的参数其实是一个可变参数,所以支持数组。
7.3 常用方法
流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
- 终结方法(终结操作):返回值类型不再是
Stream接口自身类型的方法,因此不再支持类似StringBuilder那样的链式调用。本小节中,终结方法包括count和forEach方法。 - 非终结方法(中间操作):返回值类型仍然是
Stream接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为非终结方法。)
在上述介绍的各种方法中,凡是返回值仍然为Stream接口的为函数拼接方法,它们支持链式调用;而返回值不再为Stream接口的为终结方法,不再支持链式调用。如下表所示:
| 方法名 | 方法作用 | 方法种类 | 是否支持链式调用 |
|---|---|---|---|
| count | 统计个数 | 终结 | 否 |
| forEach | 逐一处理 | 终结 | 否 |
| filter | 过滤 | 函数拼接 | 是 |
| limit | 取用前几个 | 函数拼接 | 是 |
| skip | 跳过前几个 | 函数拼接 | 是 |
| map | 映射 | 函数拼接 | 是 |
| concat | 组合 | 函数拼接 | 是 |
7.4 Stream流UML图
返回值为Stream类型的则为中间操作,其他为终结操作。可以看到基本所有入参都是函数式接口。
7.5 中间操作
一般涉及元素过滤、映射、平铺等操作。
filter过滤
map映射
flatmap平铺
peek用于调试
……
7.6 终结操作
一般为将集合,归约为单值,或者新集合,或消费。
foreach遍历
reduce归约
collect收集
……
7.7 Collectors收集器
作为collect收集操作的入参,收集器,告诉api,最终采集的结果是List?Set?Map
toList收集为列表
toSet收集为集合
toMap收集为映射,注意其key不能为null,否则报错
……
其收集器API一览
7.8 map元素排序
目标:返回排序后的Map
使用的toMap方法,需要使用固定排序的LinkedHashMap,默认是HashMap,顺序不保证,而TreeMap是自动按自然排序,并不是操作流时,自定义的排序规则,所以得选择LinkedHashMap。
示例:
public Map<String, Long> countUserOrderNum () { return getOrdersOnPaid() .stream() .filter(o -> o.userId() != null ) .collect(Collectors.groupingBy( Order::userId, TreeMap:: new , Collectors.counting())) .entrySet() .stream() .sorted(Map.Entry.<String, Long>comparingByValue().reversed()) .collect(Collectors.toMap( Map.Entry<String, Long>::getKey, Map.Entry<String, Long>::getValue, (oldVal, newVal) -> oldVal, LinkedHashMap:: new )); }
7.9 Stream综合代码示例
package com.water.base.lambda; import java.math.BigDecimal; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; /** * 常用Stream-API */ public class StreamAPITest { private static final List<ConsumeOrder> ORDER_LIST = List.of( new ConsumeOrder ( 1L , "C001" , new BigDecimal ( "20" ), "张三" ,OrderStatus.PAID.name(),List.of( new ConsumeOrderItem ( 1L , 1L , "炒时蔬" , new BigDecimal ( "2" ), 2 ), new ConsumeOrderItem ( 2L , 1L , "香菇炒肉" , new BigDecimal ( "3" ), 4 ), new ConsumeOrderItem ( 3L , 1L , "凉拌黄瓜" , new BigDecimal ( "1" ), 4 ) )), new ConsumeOrder ( 2L , "C002" , new BigDecimal ( "30" ), "李四" ,OrderStatus.RETURNED.name(),List.of( new ConsumeOrderItem ( 4L , 2L , "炖汤" , new BigDecimal ( "1" ), 10 ), new ConsumeOrderItem ( 5L , 2L , "牛扒" , new BigDecimal ( "5" ), 3 ), new ConsumeOrderItem ( 6L , 2L , "凉拌黄瓜" , new BigDecimal ( "1" ), 5 ) )), new ConsumeOrder ( 3L , "C003" , new BigDecimal ( "18" ), "赵五" ,OrderStatus.PAID.name(),List.of( new ConsumeOrderItem ( 7L , 3L , "炒时蔬" , new BigDecimal ( "2" ), 2 ), new ConsumeOrderItem ( 8L , 3L , "香菇炒肉" , new BigDecimal ( "3" ), 3 ), new ConsumeOrderItem ( 9L , 3L , "凉拌木耳" , new BigDecimal ( "2.5" ), 2 ) )) ); enum OrderStatus { CREATED, // 已创建 PENDING, // 进行中 PAID, // 已支付 RETURNED, // 已退款 CLOSED // 已关闭 } public static void main (String[] args) { // 1、【filter】过滤出已支付订单 System.out.println(ORDER_LIST.stream().filter(o->OrderStatus.PAID.name().equals(o.status())).toList()); printLine(); // 2、【map、flatMap、max】找出订单明细中单价最高的菜品 String name = ORDER_LIST.stream().flatMap(o -> o.items().stream()) .max(Comparator.comparing(ConsumeOrderItem::price)) .map(ConsumeOrderItem::dishName) .orElse( "一样" ); System.out.println( "单价最高菜品:" +name); printLine(); // 3、【collect,count,grouping,forEach】按订单状态分组并统计订单数 System.out.println( "状态: 数量" ); ORDER_LIST.stream().collect(Collectors.groupingBy(ConsumeOrder::status,Collectors.counting())) .forEach((k,v)-> System.out.println(k+ ": " +v)); printLine(); // 4、【reduce】统计订单总金额 BigDecimal sum = ORDER_LIST.stream().map(ConsumeOrder::totalAmount).reduce(BigDecimal.ZERO, BigDecimal::add); System.out.println( "订单总金额:" +sum); printLine(); // 5、【toSet】订单ID集合 System.out.println(ORDER_LIST.stream().map(ConsumeOrder::id).collect(Collectors.toSet())); printLine(); // 6、【distinct、toList】从订单明细中收集订单ID System.out.println(ORDER_LIST.stream().flatMap(o -> o.items().stream()) .map(ConsumeOrderItem::consumeId) .distinct() .toList()); } private static void printLine () { System.out.println( "======================================================================" ); } } record ConsumeOrder (Long id, String orderNo, BigDecimal totalAmount, String name, String status, List<ConsumeOrderItem> items) {} record ConsumeOrderItem (Long id,Long consumeId, String dishName, BigDecimal price, Integer num) {}
输出:
[ConsumeOrder[id= 1 , orderNo=C001, totalAmount= 20 , name=张三, status=PAID, items=[ConsumeOrderItem[id= 1 , consumeId= 1 , dishName=炒时蔬, price= 2 , num= 2 ], ConsumeOrderItem[id= 2 , consumeId= 1 , dishName=香菇炒肉, price= 3 , num= 4 ], ConsumeOrderItem[id= 3 , consumeId= 1 , dishName=凉拌黄瓜, price= 1 , num= 4 ]]], ConsumeOrder[id= 3 , orderNo=C003, totalAmount= 18 , name=赵五, status=PAID, items=[ConsumeOrderItem[id= 7 , consumeId= 3 , dishName=炒时蔬, price= 2 , num= 2 ], ConsumeOrderItem[id= 8 , consumeId= 3 , dishName=香菇炒肉, price= 3 , num= 3 ], ConsumeOrderItem[id= 9 , consumeId= 3 , dishName=凉拌木耳, price= 2.5 , num= 2 ]]]] ====================================================================== 单价最高菜品:牛扒 ====================================================================== 状态: 数量 RETURNED: 1 PAID: 2 ====================================================================== 订单总金额: 68 ====================================================================== [ 1 , 2 , 3 ] ====================================================================== [ 1 , 2 , 3 ]
7.10 并行流Parallel
并行流在大数据时开启,有助于提高性能,底层会多线程执行,自行优化。
Collectors.of 源码:
/** * Returns a new { @code Collector} described by the given { @code supplier}, * { @code accumulator}, { @code combiner}, and { @code finisher} functions. * * @param supplier The supplier function for the new collector * @param accumulator The accumulator function for the new collector * @param combiner The combiner function for the new collector * @param finisher The finisher function for the new collector * @param characteristics The collector characteristics for the new * collector * @param <T> The type of input elements for the new collector * @param <A> The intermediate accumulation type of the new collector * @param <R> The final result type of the new collector * @throws NullPointerException if any argument is null * @return the new { @code Collector} */ public static <T, A, R> Collector<T, A, R> of (Supplier<A> supplier, BiConsumer<A, T> accumulator, BinaryOperator<A> combiner, Function<A, R> finisher, Characteristics... characteristics) { Objects.requireNonNull(supplier); Objects.requireNonNull(accumulator); Objects.requireNonNull(combiner); Objects.requireNonNull(finisher); Objects.requireNonNull(characteristics); Set<Characteristics> cs = Collectors.CH_NOID; if (characteristics.length > 0 ) { cs = EnumSet.noneOf(Characteristics.class); Collections.addAll(cs, characteristics); cs = Collections.unmodifiableSet(cs); } return new Collectors .CollectorImpl<>(supplier, accumulator, combiner, finisher, cs); }
代码示例:
Stream.of( 1 , 2 , 3 , 4 ) .parallel() .collect(Collector.of( () -> { System.out.printf( "%-12s %s%n" ,mark(), "create" ); return new ArrayList <Integer>(); }, (list, x) -> { List<Integer> old = new ArrayList <>(list); list.add(x); System.out.printf( "%-12s %s.add(%d)=>%s%n" ,mark(), old, x, list); }, (list1, list2) -> { List<Integer> old = new ArrayList <>(list1); list1.addAll(list2); System.out.printf( "%-12s %s.add(%s)=>%s%n" , mark(),old, list2, list1); return list1; }, list -> list, Collector.Characteristics.IDENTITY_FINISH )); private static String mark () { return "water" ; }
输出:
water create water [].add( 3 )=>[ 3 ] water create water [].add( 1 )=>[ 1 ] water create water [].add( 2 )=>[ 2 ] water create water [].add( 4 )=>[ 4 ] water [ 3 ].add([ 4 ])=>[ 3 , 4 ] water [ 1 ].add([ 2 ])=>[ 1 , 2 ] water [ 1 , 2 ].add([ 3 , 4 ])=>[ 1 , 2 , 3 , 4 ]
八、总结
函数编程,在java中,指的是使用函数式接口编程,简化为Lambda表达式,方法引用。Stream流API是对for循环遍历操作元素的一次重大优化,针对数据源有多种方式构建Stream流,像水流一样逐步处理元素,而中间操作,是惰性的,只有触发终结操作才会执行。Lambda表达式的延迟执行特性,其实就是参数行为化的时候,自定义的实现只是抽象方法的实现,并不是马上调用执行,要区分看来;它的真正执行地方是在方法里面。
函数的无状态、无副作用特性,是简化为lambda表达式,成为一个函数对象,随处传递的关键因素。如果,调用函数产生了副作用,这还能有效传递和使用吗?结果都不可预期了。另外,如果希望外部的变量,也能参与Lambda的逻辑实现,可以使用背包技术,这个过程是编译器或JVM帮你做的,但唯一要求就是这个变量必须是不变的,最好就是使用final显式修饰,因为要成为函数的一部分,规则的一部分,参与进来的变量值是不能改变的。但是对象的属性是可以改变的,因为只要变量指向的地址值不变即可。
方法引用,本质就是复用已有能匹配上函数式接口抽象方法样式的方法,因为有现成的,所以不用重复设计,是lambda表达式的 进一步简化。
综上,本文通过许多案例帮助读者掌握函数编程的重要知识点,我们要知其然,也要知其所以然。多敲代码,多思考总结,才能更深刻体会函数编程的精妙之处。
更多推荐


所有评论(0)