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流式操作的源头是数据集合,中间操作大部分都是涉及遍历,如:mapfilter等,也就是每个数据集合的元素都会执行一遍调用方定义Lambda表达式,返回都是一个新的流。

终结操作,涉及数据集合归约,聚合成一个统计值-单值,如:avgmaxmin等;另外一种是返回数据集合列表,如:toListtoSettoMap等,同时也可以分组统计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订单服务,然后进行调用打印。最后涉及过滤条件的方法getOrderByConditiongetOrderByCondition2可以自由组合过滤条件进行测试,可以直观地感受到函数式接口的魅力。

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表达式形式,一定是和抽象方法出入参形式一样的。

    1. 0参无返回,() -> {...},如:Runnable线程执行任务的逻辑
    1. 1参无返回,t -> {...},如:Consumer消费者
    1. 0参有返回,() -> a,如:Supplier生产者(提供者)
    1. 1参有返回,t -> u,如:Function映射转换计算(输入输出模式)
    1. 多参无返回,(a,b)-> {...},如:BiConsumer两个入参的消费者
    1. 其他格式,根据函数接口匹配

注意:

    1. 只有一个入参,()可省略
    1. 方法体只有一条语句,{} 可以省略
    1. 方法体只有一条语句,return可以省略
    1. 多个参数使用(),逗号隔开,并且方法体内使用时注意形参的数据类型,是根据抽象方法入参顺序来的,一一对应
    1. 方法体内,若需要外部变量,则该变量必须final显式修饰,若不final修饰也得隐式保证,即后续不更改该变量值,这里final的是变量值,对象则是地址值(所以对象属性可以改变),是为了便于将该变量放在【背包】里面,随着该Lambda表达式(匿名对象)随处传递,其实就是拷贝了一个不可变的副本。
    1. 抽象方法实现,和普通方法实现没什么两样,需要注意的是Lambda表达式形参的顺序,形参的数据类型。注意分清楚声明定义和调用。
    1. Lambda表达式代表的匿名对象,先声明定义,然后在合适的时机调用
    1. 方法入参时的Lambda表达式,就是抽象方法的一种实现!

五、方法引用

方法引用:对Lambda表达式的进一步简化,对已有方法的复用,因为Lambda表达式和已有方法的形式、结构相匹配,所以可以直接引用::过来。因为类加载时,其方法信息已经存储在方法区,所以JVM可以很容易就获取方法信息,然后作为函数接口抽象方法的一种实现

    1. 静态方法引用,类名::静态方法
    1. 实例方法引用,类名::实例方法
    1. this引用,this::实例方法,引用本类实例方法,在实例方法中使用
    1. 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表达式的强大表现,该行为可以随调用者定义,变的是具体实现,不变的是这个函数接口抽象方法的样式。其实,入参的数据类型为父类、接口,同样有这样的表现形式。但三者有不同:

    1. 入参父类类型,new Father {覆盖父类方法},匿名子类继承该父类,并且new 了个对象
    1. 入参接口,new Callback {success、fail},实现successfail抽象方法,匿名实现类,并且new了个对象
    1. 入参函数接口,可以 跟入参接口一样,但只有一个抽象方法;但同时可以使用Lambda表达式简化,若有可复用方法,进一步使用方法引用::更简洁,如:a-> {a...},String::valueof

代码示例:

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是最终结果。

这里的filtermapskip都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法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那样的链式调用。本小节中,终结方法包括countforEach方法。
  • 非终结方法(中间操作):返回值类型仍然是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表达式的 进一步简化。

综上,本文通过许多案例帮助读者掌握函数编程的重要知识点,我们要知其然,也要知其所以然。多敲代码,多思考总结,才能更深刻体会函数编程的精妙之处。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐