当前位置: 首页 > ai >正文

JDK1.8函数式编程实战(附日常工作案例,仅此一篇耐心看完彻底搞懂)

JDK1.8函数式编程实战(附日常工作案例,仅此一篇耐心看完彻底搞懂

  • JDK1.8函数式编程背景
    • 一、函数式编程简介
    • 二、Lambda表达式:函数式编程的基石
    • 三、函数接口:Lambda表达式的目标类型
      • 3.0 常用函数接口
      • 3.1、Consumer 接口:消费数据
        • 1. 接口定义
        • 2. 示例代码
        • 3. 代码解析
        • 4. 工作实践
      • 3.2、Supplier 接口:提供数据
        • 1. 接口定义
        • 2. 示例代码
        • 3. 代码解析
        • 4. 工作实践
      • 3.3、Function 接口:转换数据
        • 1. 接口定义
        • 2. 示例代码
        • 3. 代码解析
        • 4. 工作实践
      • 3.4、Predicate 接口:条件判断
        • 1. 接口定义
        • 2. 示例代码
        • 3. 代码解析
        • 4. 工作实践
    • 四、总结

JDK1.8函数式编程背景

JDK8新特性函数式编程,极大地提升了Java代码的简洁性、可读性和灵活性。本文将阐述函数接口的日常应用,良心文章,感谢支持。

一、函数式编程简介

简介可不看,简单一句话,抽象出一种工具方法,可以理解为一种或一类模板,它不关心具体怎么实现,只关心怎么定义(入参,出参),至于具体的实现,就是咱程序员去自定义编写代码实现的。
简介:(函数式编程是一种编程范式,它强调将函数作为编程的基本单元,函数可以作为变量传递、作为参数传递给其他函数,甚至可以作为函数的返回值。与传统的面向对象编程不同,函数式编程更注重函数的纯粹性和不可变性,避免修改状态和可变数据,从而减少副作用,提高代码的可预测性和可维护性)。

二、Lambda表达式:函数式编程的基石

Lambda表达式(省略,不熟悉的铁子可以找个详细的博客看看,此文重点仅针对函数编程的实践).

三、函数接口:Lambda表达式的目标类型

函数接口是只包含一个抽象方法的接口,它是Lambda表达式的目标类型。JDK8提供了@FunctionalInterface注解来明确指示一个接口是函数接口,这有助于编译器检查接口是否符合函数式接口的定义。这里插一嘴,就算没有@FunctionalInterface注解标识,只要符合上述条件,它也是函数式接口。

3.0 常用函数接口

JDK1.8的java.util.function包提供了大量常用的函数式接口,但工作中常用的例举以下4种:

  • Consumer<T>:接受一个T类型参数,没有返回值,通常用于处理操作。
  • Supplier<T>:不接受参数,返回一个T类型结果,通常用于提供数据。
  • Function<T, R>:接受一个T类型参数,返回一个R类型的结果,常用于转换操作。
  • Predicate<T>:接受一个类型参数,返回一个布尔值,通常用于条件判断。
    其它如BiFunction<T, U, R>三个参数的,大家可自行研究,原理其实都一样。
    题外话(个人见解)
    如果初次见,可千万别被吓到,就把它理解为几个事先规定好了的接口,其中的抽象方法(如apply、accept)也别觉得有多高大上,你自己定义一个函数式接口,随意命名方法名和接口名一样可以实现同样的功能。
    示例代码如下:
public class FunctionTest {public static void main(String[] args) {// Function写法String str1 = "abc";String resp = printTest(str1, p -> p.toUpperCase());System.out.println("resp = " + resp);System.out.println("str1 = " + str1);String resp2 = printTest(str1, String::toUpperCase);System.out.println("resp2 = " + resp2);System.out.println("****************************************");// 自定义函数接口(MyFunction)写法String myResp = printMyTest(str1, p -> p.toUpperCase());System.out.println("myResp = " + myResp);System.out.println("str1 = " + str1);String myResp2 = printMyTest(str1, String::toUpperCase);System.out.println("myResp2 = " + myResp2);}public static String printTest(String req, Function < String, String > function){return function.apply(req);};// 自定义的MyFunction < String, String >作为形参public static String printMyTest(String req, MyFunction < String, String > function){return function.test(req);};// 此处故意用U、Z泛型以及test方法名区分Function的泛型和apply方法,演示自定义和原生Function功能一样@FunctionalInterfacepublic interface MyFunction<U, Z> {Z test(U req);}
}

由此可见我们可以把jdk8提供的函数接口,看作一种或一类约定(或模板)即可,如果看不懂没关系,咱们接着往下细聊。
另外,这四类,其实可以理解为只有一类,那就是Function<T, R>,其他3类,都可以用Function实现,那为什么还要做区分呢,当然是区分后有很多优点,比如:简单,易读,分类,方便等。比如Counsumer,用它我们就很清楚的知道,用它的时候就是只接收数据,并处理一些逻辑,不用返回任何东西。其他类型同理。

3.1、Consumer 接口:消费数据

1. 接口定义

Consumer 接口表示一个接受单个输入参数且无返回值的操作。它定义了一个抽象方法 accept,用于对输入参数执行某些操作,如果初次接触,还真不一定能理解这是啥意思,可以理解为调用accept方法的时候,就是拿到规定的实参执行这个Cousumer的逻辑,就记住就行。

@FunctionalInterface
public interface Consumer<T> {void accept(T t);// 默认方法(如 andThen)用于组合多个 Consumerdefault Consumer<T> andThen(Consumer<? super T> after) {Objects.requireNonNull(after);return (T t) -> { accept(t); after.accept(t); };}
}
2. 示例代码
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;public class ConsumerExample {public static void main(String[] args) {List<String> names = Arrays.asList("Alice", "Bob", "Charlie");// 使用 Consumer 打印每个名字Consumer<String> printName = name -> System.out.println("Hello, " + name + "!");// 此处.forEach(Consumer<? super T> action) 方法形参就是一个Consumernames.forEach(printName);// 使用 andThen 组合多个 ConsumerConsumer<String> greetAndLength = printName.andThen(name -> System.out.println("The length of your name is: " + name.length()));System.out.println("Combining consumers:");names.forEach(greetAndLength);}
}
3. 代码解析
  • 定义了一个 Consumer<String> 类型的 printName,用于打印问候语。

  • 使用 ListforEach 方法遍历集合,并对每个元素应用 printName

  • 使用 andThen 方法将 printName 和另一个 Consumer 组合起来,先打印问候语,再打印名字长度。

    是不是被这东西吓到了,都没调用方法,就直接实现了功能。这里需要解释以下,List的foreach方法形参正是函数Consumer action,那么它会循环list去执行Consumer函数的accept方法,也就是我们自定义的Consumer,即 System.out.println("Hello, " + name + "!") 这段代码,这样大概就理解了函数接口的作用了。

default void forEach(Consumer<? super T> action) {Objects.requireNonNull(action);for (T t : this) {action.accept(t);}
}
4. 工作实践

工作中的真实案例,SpringBoot项目整合xxljob后,jobHandler需要写多个job时,每个job打印日志,处理异常基本都一样,并且没有返回值,以前大家都是直接复制粘贴,并修改关键词和handle方法名。这是典型的可用Consumer函数抽取重复代码处理逻辑。
原有代码如下:

@XxlJob("demo1JobHandler")public ReturnT<String> demo1JobHandler(String param) throws Exception {long b = System.currentTimeMillis();XxlJobLogger.log("demo1JobHandler-Job,param:{} start..... time:{}",param, DateUtil.format(new Date(),DateUtil.FULL_FORMAT));demo1Service.handle(param);XxlJobLogger.log("demo1JobHandler-Job, end SUCCESS..... time:{},耗时:{}ms", DateUtil.format(new Date(),DateUtil.FULL_FORMAT),System.currentTimeMillis()-b);return ReturnT.SUCCESS;}@XxlJob("demo2JobHandler")public ReturnT<String> monthDeliveryJobHandler(String param) throws Exception {long b = System.currentTimeMillis();XxlJobLogger.log("demo2JobHandler-Job,param:{} start..... time:{}",param, DateUtil.format(new Date(),DateUtil.FULL_FORMAT));demo2Service.handle(param);XxlJobLogger.log("demo2JobHandler-Job, end SUCCESS..... time:{},耗时:{}ms", DateUtil.format(new Date(),DateUtil.FULL_FORMAT),System.currentTimeMillis()-b);return ReturnT.SUCCESS;}@XxlJob("demo3JobHandler")public ReturnT<String> syncMonthMedal(String param) throws Exception {long b = System.currentTimeMillis();XxlJobLogger.log("demo3JobHandler-Job,param:{} start..... time:{}",param, DateUtil.format(new Date(),DateUtil.FULL_FORMAT));demo3Service.handle(param);XxlJobLogger.log("demo3JobHandler-Job, end SUCCESS..... time:{},耗时:{}ms", DateUtil.format(new Date(),DateUtil.FULL_FORMAT),System.currentTimeMillis()-b);return ReturnT.SUCCESS;}...下面还有很多相同日志相同结构的job

由于项目比较大,有很多的job,那就意味着需要很多次复制,这样就显得非常没有水准,而且多的时候非常影响心情,总觉得自己在做一些没有含金量的东西,用Consumer抽取出excute方法(名字可根据业务取名),改造后代码如下:

	// 抽取出excute方法,形参(param, keys, consumer)public void excute(String param, String keys, Consumer<String> consumer){XxlJobLogger.log(keys + "-Job, start.....");try {consumer.accept(param);} catch (Exception e) {e.printStackTrace();XxlJobLogger.log(keys + "-Job error, cause:{}", e.getMessage());return ReturnT.FAIL;}XxlJobLogger.log(keys + "-Job SUCCESS");return ReturnT.SUCCESS;}// 抽取出excute方法后,jobHandler可以按如下方式编写。@XxlJob("syncMonthMedalJobHandler")public ReturnT<String> syncMonthMedal(String param) throws Exception {excute(param, "demo1JobHandler", empAgingScheduledService::monthconfigureTasks);}@XxlJob("demo1JobHandler")public ReturnT<String> demo1JobHandler(String param) throws Exception {excute(param, "demo2JobHandler", demo2Service::handle);}@XxlJob("demo2JobHandler")public ReturnT<String> monthDeliveryJobHandler(String param) throws Exception {excute(param, "demo3JobHandler", demo2Service::handle);}/***  ... ...* 此处还有很多job*/

说明:
我们稍微分析下就可以知道,以前每个job打印日志、处理异常基本都一样,只有关键字和执行逻辑不一样。这样我们改造思路就是,把日志、处理异常等封装到一个方法,把处理逻辑用函数接口Consumer去做形参,这样我们就可以避免复制很多重复代码,代码看上去也更简约。

3.2、Supplier 接口:提供数据

1. 接口定义

Supplier 接口表示一个不接受参数但返回一个结果的操作。它定义了一个抽象方法 get,用于返回结果。

@FunctionalInterface
public interface Supplier<T> {T get();
}
2. 示例代码
import java.util.function.Supplier;
import java.util.Random;public class SupplierExample {public static void main(String[] args) {// 使用 Supplier 生成随机数Supplier<Integer> randomNumberSupplier = () -> new Random().nextInt(100);// 生成并打印 5 个随机数System.out.println("Random numbers:");for (int i = 0; i < 5; i++) {System.out.println(randomNumberSupplier.get());}}
}
3. 代码解析
  • 定义了一个 Supplier<Integer> 类型的 randomNumberSupplier,用于生成随机数。
  • 调用 get 方法 5 次,每次生成并打印一个随机数。
4. 工作实践

日常项目中,由于不同条件获取某个对象的集合,但是转化或其他逻辑都 一样,可以抽取出来,用Supplier实现。
原有代码如下:

  • 抽取的方法
private List<User> getStudentList1(int age, int score) {// 据某些条件如score、age等年龄从库里查出User的list集合return new ArrayList<>();
}
private List<User> getStudentList2(int age, int score, String name) {// 据某些条件如score、age等年龄从库里查出User的list集合return new ArrayList<>();
}
private List<Student> transferSupplierList(String token, Supplier<List<User>> supplier) {System.out.println("token 校验等... " + token);List<User> list = supplier.get();// 获取到list后,再进行转化List<Student> students = transferList(list);// log打印日志等...System.out.println("打印日志等... ");return students;
}private static List<Student> transferList(List<User> list) {return list.stream().map(p -> {return Student.builder().englishName(p.getName()).grade(p.getGrade()).build();}).toList();
}
  • 传统写法
int age = 16;
int score = 85;
String name = "小风";
String token = "KLCVHBSDKNGKAELJHGADNGS4DFAS432FG3A4S6";List<User> list1 = getStudentList1(age, score);
List<User> list2 = getStudentList2(age, score, name);
// 根据某些条件如score、age等年龄从库里查出User的list1集合,那么我们就需要在这个方法里先获取list再transfer
List<Student> students = transferList(list1);
List<Student> students2 = transferList(list2);

采用Supplier改造后:

List<Student> studentsSupplier1 = transferSupplierList(token, () -> getStudentList1(age, score));
List<Student> studentsSupplier2 = transferSupplierList(token, () -> getStudentList2(age, score, name));

如果有多个既查询,又要转化的,那么会显得非常臃肿。改造后把查询和转化合并到一起,那么逻辑就更清晰简单,结构上更加合理,可能例子不是特别典型,仅供参考,重在理解其原理。

3.3、Function 接口:转换数据

1. 接口定义

Function 接口表示一个接受一个输入参数并返回一个结果的函数。它定义了一个抽象方法 apply,用于对输入参数进行转换并返回结果。

@FunctionalInterface
public interface Function<T, R> {R apply(T t);// 默认方法(如 andThen、compose)用于组合多个 Functiondefault <V> Function<V, R> compose(Function<? super V, ? extends T> before) {Objects.requireNonNull(before);return (V v) -> apply(before.apply(v));}default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {Objects.requireNonNull(after);return (T t) -> after.apply(apply(t));}
}
2. 示例代码
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;public class FunctionExample {public static void main(String[] args) {List<String> names = Arrays.asList("Alice", "Bob", "Charlie");// 使用 Function 将名字转换为大写Function<String, String> toUpperCase = String::toUpperCase;List<String> upperCaseNames = names.stream().map(toUpperCase).collect(Collectors.toList());System.out.println("Upper case names: " + upperCaseNames);// 使用 andThen 组合多个 FunctionFunction<String, Integer> nameLength = String::length;Function<String, Integer> toUpperCaseAndGetLength = toUpperCase.andThen(nameLength);System.out.println("\nLength of upper case names:");names.forEach(name -> System.out.println(name + " -> " + toUpperCaseAndGetLength.apply(name)));}
}
3. 代码解析
  • 定义了一个 Function<String, String> 类型的 toUpperCase,用于将字符串转换为大写。
  • 使用 Streammap 方法将集合中的每个名字转换为大写,并收集到新的列表中。
  • 使用 andThen 方法将 toUpperCasenameLength 组合起来,先转换为大写,再计算长度。
4. 工作实践

日常项目中,需要包装第三方接口,入参出参一致时,可以用Function实现。
原有代码如下:

@Autowired
MyService myService;
@Autowired
private DemoService1 demoService1;@PostMapping("/test/findByReq")
public ResultResp<DemoDto> getByCourier(@RequestHeader("token") String token, @RequestBody DemoReq req) {return demoService1.findByReq(req, token);
}@PostMapping("/test/findByName")
public ResultResp findByName(@RequestBody DemoReq req, @RequestHeader String token) {return demoService1.findByName(req, token);
}@PostMapping("/test/findByTel")
public ResultResp<DemoDto> findByTel(@RequestHeader("token") String token, @RequestBody DemoReq req) {return demoService1.findByTel(req, token);
}@RequestMapping("/test/getByCode")
public ResultResp getByCode(@RequestHeader("token") String token, @RequestBody DemoReq req) {return demoService1.getByCode(req, token, TestEnum.ReqType.GET_BY_CODE.getCode());
}
  • service
public ResultResp<DemoDto> findByReq(DemoReq req, String token) {log.info("token: {}, DemoService1 getList reqParam: {}", token, JSONObject.toJSONString(req));UserInfo userInfo = userService.checkToken(token);if (null == userInfo) {ResultResp resultResp = new ResultResp<>();resultResp.setCode(CodeEnum.NOT_ALLOWED.getCode());resultResp.setMessage(CodeEnum.NOT_ALLOWED.getMessage());return resultResp;}ResultResp<DemoDto> resultResp = demoApiFeignClient.findByReq(req);DemoDto data = Optional.ofNullable(resultResp.getData()).orElse(new DemoDto());filterAndTransfer(req, data, data.getList());log.info("token: {}, DemoService1 getList req: {}, resp: {}", token, JSONObject.toJSONString(req), JSONObject.toJSONString(resultResp));return resultResp;
}public ResultResp findByName(DemoReq req, String token) {log.info("token: {}, DemoService1 getList reqParam: {}", token, JSONObject.toJSONString(req));UserInfo userInfo = userService.checkToken(token);if (null == userInfo) {ResultResp resultResp = new ResultResp<>();resultResp.setCode(CodeEnum.NOT_ALLOWED.getCode());resultResp.setMessage(CodeEnum.NOT_ALLOWED.getMessage());return resultResp;}ResultResp<DemoDto> resultResp = demoApiFeignClient.findByName(req);DemoDto data = Optional.ofNullable(resultResp.getData()).orElse(new DemoDto());filterAndTransfer(req, data, data.getList());log.info("token: {}, DemoService1 getList req: {}, resp: {}", token, JSONObject.toJSONString(req), JSONObject.toJSONString(resultResp));return resultResp;
}// 其余两个方法类似
  • 问题:
    重复代码过多,我们可以用Function抽取重复代码,优化后如下:
@RequestMapping("/test/getByCode")public ResultResp getByCode(@RequestHeader("token") String token, @RequestBody DemoReq req) {return demoService1.handlGetByCode(req, token, TestEnum.ReqType.GET_BY_CODE.getCode());}@RequestMapping("/test/getById")public ResultResp getById(@RequestHeader("token") String token, @RequestBody DemoReq req) {return demoService1.handlGetByCode(req, token, TestEnum.ReqType.GET_BY_ID.getCode());}@RequestMapping("/test/getByName")public ResultResp functionUseRatioList(@RequestHeader("token") String token, @RequestBody DemoReq req) {return demoService1.handlGetByCode(req, token, TestEnum.ReqType.GET_BY_NAME.getCode());}@RequestMapping("/test/getByTel")public ResultResp reportList(@RequestHeader("token") String token, @RequestBody DemoReq req) {return demoService1.handlGetByCode(req, token, TestEnum.ReqType.GET_BY_TEL.getCode());}
  • service
    public ResultResp handlGetByCode(DemoReq req, String token, String code) {log.info("token: {}, code: {}, DemoService1 handlGetByCode reqParam: {}", token, code, JSONObject.toJSONString(req));UserInfo userInfo = userService.checkToken(token);if (null == userInfo) {ResultResp resultResp = new ResultResp<>();resultResp.setCode(CodeEnum.NOT_ALLOWED.getCode());resultResp.setMessage(CodeEnum.NOT_ALLOWED.getMessage());return resultResp;}Function<DemoReq, ResultResp> function = excuteMap.get(code);ResultResp resultResp = null;try {resultResp = function.apply(req);} catch (Exception e) {log.error("token: {}, DemoService1 handlGetByCode {} feign req: {}, error: ", token, code, JSONObject.toJSONString(req), e);}log.info("token: {}, DemoService1 handlGetByCode {} feign req: {}, resp: {}", token, code, JSONObject.toJSONString(req), JSONObject.toJSONString(resultResp));return null == resultResp ? new ResultResp<>(CodeEnum.SERVER) : resultResp;}Map<String, Function<DemoReq, ResultResp>> excuteMap = new HashMap<>();@PostConstructpublic void init() {excuteMap.put(TestEnum.ReqType.GET_BY_ID.getCode(), demoApiFeignClient::findById);excuteMap.put(TestEnum.ReqType.GET_BY_CODE.getCode(), demoApiFeignClient::getByCode);excuteMap.put(TestEnum.ReqType.GET_BY_NAME.getCode(), demoApiFeignClient::findByName);excuteMap.put(TestEnum.ReqType.GET_BY_TEL.getCode(), demoApiFeignClient::findByTel);}
  • 小结:
    这里采用Map预加载的方式,实现抽取出重复代码,让api调用方根据code选择Function获取数据,下面采用第二种方式实现。
public ResultResp handlGetByFunction(DemoReq req, String token, Function<DemoReq, ResultResp> function) {log.info("token: {}, DemoService1 handlGetByFunction reqParam: {}", token, JSONObject.toJSONString(req));UserInfo userInfo = userService.checkToken(token);if (null == userInfo) {ResultResp resultResp = new ResultResp<>();resultResp.setCode(CodeEnum.NOT_ALLOWED.getCode());resultResp.setMessage(CodeEnum.NOT_ALLOWED.getMessage());return resultResp;}ResultResp resultResp = null;try {resultResp = function.apply(req);} catch (Exception e) {log.error("token: {}, DemoService1 handlGetByFunctionfeign req: {}, error: ", token, JSONObject.toJSONString(req), e);}log.info("token: {}, DemoService1 handlGetByFunctionfeign req: {}, resp: {}", token, JSONObject.toJSONString(req), JSONObject.toJSONString(resultResp));return null == resultResp ? new ResultResp<>(CodeEnum.SERVER) : resultResp;}
  • Controller
    @RequestMapping("/test/getByCodeNew")public ResultResp getByCodeNew(@RequestHeader("token") String token, @RequestBody DemoReq req) {return demoService1.handlGetByFunction(req, token, demoApiFeignClient::getByCode);}@RequestMapping("/test/getByIdNew")public ResultResp getByIdNew(@RequestHeader("token") String token, @RequestBody DemoReq req) {return demoService1.handlGetByFunction(req, token, demoApiFeignClient::findById);}@RequestMapping("/test/getByNameNew")public ResultResp functionUseRatioListNew(@RequestHeader("token") String token, @RequestBody DemoReq req) {return demoService1.handlGetByFunction(req, token, demoApiFeignClient::findByName);}@RequestMapping("/test/getByTelNew")public ResultResp reportListNew(@RequestHeader("token") String token, @RequestBody DemoReq req) {return demoService1.handlGetByFunction(req, token, demoApiFeignClient::findByTel);}
  • 小结
    这种实现方式可以随用随写,不用再去维护枚举,以及预加载map,两种方式可以在实际工作中选用最合适的方式,各有各的优势。当然有人就要说了aop也能实现,不用钻牛角尖哈,这里重在理解其思想,能在工作中活学活用才是最好的方式。
  • 思考:
    这种方式也存在着一定的弊端,就是如果入参出参类型不一致,这种情况就比较尴尬了,那么我们咋办呢,采用泛型可以实现。
    public <R, T> R handlGetByFunctionRT(T req, String token,Function<T, R> function){log.info("token: {}, DemoService1 handlGetByFunction reqParam: {}", token, JSONObject.toJSONString(req));UserInfo userInfo = userService.checkToken(token);if (null == userInfo) {return null;}R resultResp = null;try {resultResp = function.apply(req);} catch (Exception e) {log.error("token: {}, DemoService1 handlGetByFunctionfeign req: {}, error: ", token, JSONObject.toJSONString(req), e);}log.info("token: {}, DemoService1 handlGetByFunctionfeign req: {}, resp: {}", token, JSONObject.toJSONString(req), JSONObject.toJSONString(resultResp));return null;}
  • feign
public interface DemoApiFeignClient {ResultResp<DemoDto> findByReq(DemoReq req);ResultResp<DemoDto> findById(DemoReq req);ResultResp<DemoDto> findByName(DemoReq req);ResultResp<DemoDto> findByTel(DemoReq req);ResultResp<DemoDto> getByCode(DemoReq req);// 两个入参出参和上面都不一样的方法String findByTelNew(String str);JSONObject getByCodeNew(Integer i);}
  • Controller
    @RequestMapping("/test/getByTelNew2")public ResultResp reportListNew2(@RequestHeader("token") String token, @RequestBody DemoReq req) {String resp = demoService1.handlGetByFunctionRT(req.getCode(), token, demoApiFeignClient::findByTelNew);return new ResultResp<>(resp);}@RequestMapping("/test/getByTelNew3")public ResultResp reportListNew3(@RequestHeader("token") String token, @RequestBody DemoReq req) {JSONObject resp = demoService1.handlGetByFunctionRT(req.getScore(), token, demoApiFeignClient::getByCodeNew);return new ResultResp<>(resp);}
  • 总结:
    通过定义泛型的方式就可以实现入参出参多样化的函数式策略模式的实现,会更加灵活,当然不仅局限于Function,如果有多个参数可以使用BiFunction或自己定义函数式接口。

3.4、Predicate 接口:条件判断

1. 接口定义

Predicate 接口表示一个接受一个输入参数并返回一个布尔值的函数。它定义了一个抽象方法 test,用于对输入参数进行条件判断。

@FunctionalInterface
public interface Predicate<T> {boolean test(T t);// 默认方法(如 and、or、negate)用于组合多个 Predicatedefault Predicate<T> and(Predicate<? super T> other) {Objects.requireNonNull(other);return (t) -> test(t) && other.test(t);}default Predicate<T> or(Predicate<? super T> other) {Objects.requireNonNull(other);return (t) -> test(t) || other.test(t);}default Predicate<T> negate() {return (t) -> !test(t);}
}
2. 示例代码
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;public class PredicateExample {public static void main(String[] args) {List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");// 使用 Predicate 过滤长度大于 3 的名字Predicate<String> lengthGreaterThan3 = name -> name.length() > 3;List<String> filteredNames = names.stream().filter(lengthGreaterThan3).collect(Collectors.toList());System.out.println("Names with length > 3: " + filteredNames);// 使用 and 和 or 组合多个 PredicatePredicate<String> startsWithA = name -> name.startsWith("A");Predicate<String> startsWithAOrLengthGreaterThan3 = startsWithA.or(lengthGreaterThan3);System.out.println("\nNames starting with 'A' or length > 3:");names.forEach(name -> {if (startsWithAOrLengthGreaterThan3.test(name)) {System.out.println(name);}});}
}
3. 代码解析
  • 定义了一个 Predicate<String> 类型的 lengthGreaterThan3,用于判断名字长度是否大于 3。
  • 使用 Streamfilter 方法过滤集合中的元素,只保留满足条件的元素。
  • 使用 or 方法将 startsWithAlengthGreaterThan3 组合起来,判断名字是否以 “A” 开头或长度大于 3。
4. 工作实践

日常项目中,需要封装第三方接口,入参出参一致时,可以用Function实现。
原有代码如下:
采用Predicate抽取出重复代码如下:

// 大部分这样写的
List<Student> collect = studentList.stream().filter(p -> p.getEnglishName().startsWith("abc") && p.getScore() >= 30).collect(Collectors.toList());
// 后面会抽取出一个方法
List<Student> collect1 = studentList.stream().filter(p -> filterByCodeAndName(p)).collect(Collectors.toList());
// 当前类的方法::
List<Student> collect2 = studentList.stream().filter(Test::filterByCodeAndName).collect(Collectors.toList());
// 创建Predicate
int scoreMax = 85;
List<Student> collect3 = studentList.stream().filter(getPredicate(scoreMax)).collect(Collectors.toList());private static Predicate<Student> getPredicate(int scoreMax) {return p -> filterByCodeAndName(p) && filteTotal(scoreMax, p);
}private static boolean filteTotal(int scoreMax, Student p) {return p.getTotal() >= scoreMax;
}private static boolean filterByCodeAndName(Student p) {boolean result = false;// 例如此处有其他判断,例如调用第三方接口获取响应拿到结果后的判断等等result = p.getEnglishName().startsWith("abc") && p.getScore() >= 30;// 例如此处还有其他判断...return result;
}
  • 总结
    相对来说还是比较简单的,日常工作中暂时还没有遇到其他类型的实践,大家可以自己多研究下。

四、总结

JDK1.8 的 ConsumerSupplierFunctionPredicate 等接口为 Java 开发者提供了强大的函数式编程工具。通过这些接口,开发者可以更加简洁、灵活地处理数据,实现各种复杂的逻辑。无论是消费数据、提供数据、转换数据还是进行条件判断,这些接口都能提供优雅的解决方案,这些都是官方话术,不用拘泥于形式。在实际开发中,合理使用这些接口可以显著提高代码的可读性和可维护性。日常如:抽取出冗余代码,类似aop功能,统一第三方调用等,均可以提高工作效率。
以上,仅供参考。

http://www.xdnf.cn/news/15402.html

相关文章:

  • 力扣73:矩阵置零
  • redis红锁
  • 微信小程序开发-桌面端和移动端UI表现不一致问题记录
  • 自然语言指令驱动的工业机器人协同学习系统:大语言模型如何重塑智能体协作范式
  • Containerd容器技术详解
  • 拥抱 Spring Boot:开启 Java 后端开发的“快车道”
  • 2025阿里云黑洞恢复全指南:从应急响应到长效防御的实战方案
  • AJAX 开发中的注意点
  • C++ Qt插件开发样例
  • Python初学者笔记第十三期 -- (常用内置函数)
  • 【鸿蒙HarmonyOS】鸿蒙app开发入门到实战教程(二):封装自定义可复用组件
  • 深入解析环境变量:从基础概念到系统级应用
  • kdump生成转储文件调试内核崩溃、死机
  • Java 栈和队列
  • linux 系统依赖包查询命令汇总
  • IPM31主板E3300usb键盘鼠标安装成功Sata接口硬盘IDE模式server2003-nt-5.2.3790
  • python 的包管理工具pip poetry、conda 和 pipenv 使用和安装
  • C 语言部分操作符详解 -- 进制转换,原码、反码、补码,位操作符,逗号表达式,操作符的优先级和结合性,整型提升,算术转换
  • 2025年小目标检测分享:从无人机视角到微观缺陷的创新模型
  • 【PTA数据结构 | C语言版】二叉树前序序列化
  • Python初学者笔记第十二期 -- (集合与字典编程练习题)
  • Vim多列操作指南
  • TCP可靠性设计的核心机制与底层逻辑
  • next.js 登录认证:使用 github 账号授权登录。
  • uni-app+vue3 来说一说前端遇到跨域的解决方案
  • 全连接神经网络
  • 10分钟搞定!Chatbox+本地知识库=你的私人语音导师:企业级全栈实现指南
  • 自动微分模块
  • JAR 包冲突排雷指南:原理、现象与 Maven 一站式解决
  • 机载激光雷达目标识别:从点云到凝视成像的算法全景