Java函数式接口概述
Java函数式接口(Functional Interface)是Java 8引入的一个重要特性,它指的是那些有且仅有一个抽象方法的接口(但可以有多个默认方法或静态方法)。这一特性与Lambda表达式紧密相关,因为Lambda表达式可以作为函数式接口的实现。
函数式接口在Java中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可
以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
常用的应用场景
- 作为方法的参数
函数式接口最常见的用途是作为方法的参数。这种方式使得方法可以接受不同类型的函数作为参数,增加了代码的灵活性和复用性。例如,java.util.Collections类中的sort方法可以接受一个Comparator接口作为参数,而Comparator正是一个函数式接口。 - 集合的遍历和操作
在Java 8中,java.util.function包提供了丰富的函数式接口,如Consumer、Function、Predicate等,这些接口广泛用于集合的遍历、转换和过滤等操作。例如,使用List的forEach方法配合Consumer接口可以简洁地遍历集合;使用Stream API结合Function和Predicate接口可以实现对集合的复杂查询和转换。 - 异步编程
在Java中,函数式接口也常用于异步编程场景。例如,CompletableFuture类提供了多种方法,允许你以非阻塞的方式编写异步代码。这些方法经常接受函数式接口作为参数,以便在异步操作完成时执行特定的回调函数。 - 事件监听和处理
在GUI编程或任何需要事件监听的应用中,函数式接口可以作为事件处理器的类型。当事件发生时,相应的函数式接口实现(如通过Lambda表达式)会被调用,执行相应的处理逻辑。 - 简化代码
函数式接口和Lambda表达式相结合,可以极大地简化代码。它们允许以更简洁、更表达性的方式编写复杂的逻辑,特别是在需要传递简单函数或行为作为参数时。
常用函数式接口示例
Runnable:无参数、无返回值的函数式接口,常用于需要执行无参数任务的场景。
Supplier:不接受参数,但返回一个类型为T的值的函数式接口。
Consumer:接受一个类型为T的参数,但不返回任何结果的函数式接口。
Function<T, R>:接受一个类型为T的输入参数,并返回一个类型为R的结果的函数式接口。
Predicate:接受一个类型为T的输入参数,并返回一个布尔值的函数式接口,通常用于条件判断。 结论 Java函数式接口的应用场景广泛,它们不仅提高了代码的灵活性和复用性,还简化了复杂逻辑的实现。通过合理使用函数式接口和Lambda表达式,可以编写出更加简洁、表达性更强的Java代码。
函数式接口应用场景
Java 函数式接口是 Java 8 引入的一个核心概念,用于支持函数式编程。函数式接口是一个只有一个抽象方法的接口,可以用作 lambda 表达式或方法引用的目标。Java 提供了许多内置的函数式接口,这些接口广泛应用于各种编程场景。以下是一些常见的应用场景: - 集合操作
Java 8 引入了流(Stream)API,大大简化了对集合的操作。函数式接口在流操作中扮演着重要角色。
● Predicate<T>: 用于过滤集合中的元素。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> filteredNames = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
● Function<T, R>: 用于将集合中的元素映射为另一个值。
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
● Consumer<T>: 用于对集合中的每个元素执行操作。
names.forEach(System.out::println);
● Comparator<T>: 用于对集合进行排序。
List<String> sortedNames = names.stream()
.sorted(Comparator.comparing(String::length))
.collect(Collectors.toList());
- 事件处理
函数式接口常用于事件处理,特别是在 GUI 编程或处理回调函数时。
ActionListener: 用于处理按钮点击等 GUI 事件。
button.addActionListener(e -> System.out.println("Button clicked!"));
- 多线程编程
在多线程编程中,函数式接口简化了线程的创建和执行。
Runnable: 用于定义一个无返回值的任务,通常用于线程执行。
new Thread(() -> System.out.println("Thread running")).start();
Callable<V>: 与 Runnable 类似,但可以返回结果或抛出异常。
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<Integer> future = executor.submit(() -> 42);
- 自定义函数式接口
有时,你可能需要定义自己的函数式接口,用于特定场景。
示例:定义一个处理某种特定业务逻辑的接口。
@FunctionalInterface
public interface Calculator {
int calculate(int a, int b);
}
Calculator addition = (a, b) -> a + b;
int result = addition.calculate(5, 3); // Output: 8
- 异常处理
通过使用 Function 或 Consumer 等函数式接口,可以更优雅地处理异常。
示例:对异常进行包装处理。
Function<String, Integer> safeParseInt = str -> {
try {
return Integer.parseInt(str);
} catch (NumberFormatException e) {
return 0;
}
};
- 策略模式
使用函数式接口可以轻松实现策略模式,让代码更具灵活性。
示例:不同的策略使用不同的函数式接口实现。
interface Strategy {
int execute(int a, int b);
}
Strategy add = (a, b) -> a + b;
Strategy multiply = (a, b) -> a * b;
int result = add.execute(2, 3); // Output: 5
- 延迟执行
函数式接口也可以用于定义延迟执行的操作。
示例:使用 Supplier 接口来延迟计算某个值。
Supplier<Double> randomValue = () -> Math.random();
System.out.println(randomValue.get());
常用的内置函数式接口
Predicate: 代表一个参数为 T 的布尔表达式。
Function<T, R>: 代表一个参数为 T,返回类型为 R 的函数。
Consumer: 代表一个接受 T 参数且不返回值的操作。
Supplier: 代表一个不接受参数但返回类型为 T 的函数。
BiFunction<T, U, R>: 代表一个接受两个参数(T 和 U),返回类型为 R 的函数。
UnaryOperator: 代表一个操作,它接受一个参数并返回与输入类型相同的结果。
BinaryOperator: 代表一个操作,它接受两个相同类型的参数并返回与输入类型相同的结果。
通过这些函数式接口,Java 编程可以变得更加简洁、灵活,并能够充分利用现代多核处理器的优势进行并行处理。
Supplier、Consumer、predicate、Function
在JDK中为我们提供了大量的函数式接口,其中较为简单和常用的为以下4个
Supplier、Consumer、Predicate、Function作为Java中的函数式接口,各自在不同的业务场景中发挥着重要作用。以下是它们常用的业务场景概述:
Consumer(消费者):如果想要处理一个数据,但是不需要返回值,可以使用Consumer接口
predicate(判断):如果想要判断一个数据,并且需要一个布尔类型的返回值,可以使用predicate接口.
Function(函数):如果想要进行属性之间的转换,如String->Integer,则需要使用Function接口,
Function的泛型一般有两种类型,前面的类型表示传入的参数类型,后面的类型表示返回值类型
Supplier接口:
java.util.function.Supplier 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
Consumer接口:
java.util.function.Consumer 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定的.
Predicate接口:
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用 java.util.function.Predicate 接口。
Function接口:
java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件
总结
Supplier 用于懒加载或延迟生成值,常见于缓存、配置读取、对象创建等场景。
Consumer 用于处理或消费数据,没有返回值,常见于事件处理、数据存储、日志记录等场景。
Predicate 用于判断条件,常见于数据过滤、验证和搜索匹配等场景。
Function 用于数据转换和映射,常见于对象转换、数据处理和计算等场景。
这些函数式接口不仅简化了代码逻辑,还使得代码更具可读性和可维护性,在现代 Java 编程中非常实用。
Java 的 Supplier、Consumer、Predicate 和 Function 是四种常用的函数式接口,它们在实际业务开发中有着广泛的应用场景。
Supplier
Supplier 用于提供一个值或对象,通常在需要延迟加载、缓存、懒加载或生成对象的场景中使用。
业务场景:
延迟初始化:在需要时才创建对象或获取值,以提高性能。
默认值提供:用于返回默认值或在特定条件下生成值。
随机数生成:在需要生成随机数或其他计算值时使用。
示例:
// 延迟加载 - 仅在调用 get() 时才计算或生成值
Supplier randomValueSupplier = () -> Math.random();
System.out.println(randomValueSupplier.get());
// 从缓存中获取值,或者在值不存在时创建新值
Map<String, User> userCache = new HashMap<>();
Supplier defaultUserSupplier = () -> new User(“defaultUser”);
User user = userCache.getOrDefault(“user123”, defaultUserSupplier.get());
Consumer
Consumer 用于对给定的输入执行某些操作而不返回结果,通常用于处理数据、日志记录、消息传递等场景。
业务场景:
数据处理:在遍历集合时对每个元素执行操作,如打印、存储等。
日志记录:记录日志或将信息输出到控制台。
事件处理:响应按钮点击、消息接收等事件。
Predicate
Predicate 用于判断给定的输入是否满足某个条件,通常在过滤、验证、搜索等场景中使用。
业务场景:
数据过滤:根据条件过滤集合中的元素。
条件验证:验证用户输入、表单数据或配置是否符合要求。
搜索和匹配:在列表或集合中查找匹配的元素。
示例:
// 过滤出以 “A” 开头的名字
Predicate<String> startsWithAPredicate = name -> name.startsWith("A");
List<String> filteredNames = names.stream()
.filter(startsWithAPredicate)
.collect(Collectors.toList());
// 验证字符串是否为空
Predicate isEmpty = String::isEmpty;
boolean result = isEmpty.test(“Hello”); // Output: false
Function<T, R>
Function 用于将输入对象转换为输出对象,通常在转换、映射、计算等场景中使用。
业务场景:
对象转换:将一种类型的对象转换为另一种类型,如 DTO 到实体的转换。
数据映射:对集合中的每个元素进行映射,如将字符串转换为其长度。
计算操作:执行数学计算或基于输入值生成新值。
示例:
// 将字符串映射为其长度
Function<String, Integer> lengthFunction = String::length;
List<Integer> nameLengths = names.stream()
.map(lengthFunction)
.collect(Collectors.toList());
// 将 User 对象转换为 UserDTO 对象
Function<User, UserDTO> userToDtoFunction = user -> new UserDTO(user.getName(), user.getEmail());
UserDTO userDTO = userToDtoFunction.apply(new User("Alice", "alice@example.com"));
Consumer使用样例
批量处理:分页+多线程+消费队列
/**
* MQ延迟消息兜底策略
* 延迟对话处理入ES任务
*/
@Component
@Slf4j
public class DelayCallInfoToESJob {
@Resource
private CallSaveService callSaveService;
@Resource
private DialogRecordService dialogRecordService;
@Resource
private DialogRecordParamsService dialogRecordParamsService;
@Resource
private CallRecordParamsService callRecordParamsService;
@Resource
private CallRecordService callRecordService;
//线程数量设置为3
private final Supplier<ExecutorService> executorServiceSupplier = () -> Executors.newFixedThreadPool(3);
private final Consumer<List<DialogRecord>> consumer = this::dealDialog;
@XxlJob(value = "saveDelayCallInfoJob")
public ReturnT<String> execute() {
long startTime = System.currentTimeMillis();
int current = 1;
IPage<DialogRecord> pageList;
do {
Page<DialogRecord> page = new Page<>(current++, BATCH_PAGE_SIZE);
pageList = dialogRecordService.page(page);
//该表已无大量数据,全表扫描
log.info("开始执行延迟对话入库后同步到ES任务,需同步对话数量[{}]", pageList.getRecords().size());
int batchSize = 100; //一批数据1000 messageId
//多线程批量处理数据
BatchUtils.splitConsume(pageList.getRecords(), batchSize, executorServiceSupplier, "Thread", consumer);
} while (!pageList.getRecords().isEmpty());
log.info("延迟对话同步ES任务结束 use:{}", System.currentTimeMillis() - startTime);
XxlJobHelper.log("延迟对话同步ES任务结束 use:{}", System.currentTimeMillis() - startTime);
return ReturnT.SUCCESS;
}
/**
* 当前线程的处理逻辑
*
* @param dialogRecords
*/
private void dealDialog(List<DialogRecord> dialogRecords) {
if (!CollectionUtils.isEmpty(dialogRecords)) {
log.info(Thread.currentThread().getName() + "开始处理数据");
Map<String, List<DialogRecord>> dialogRecordMap = dialogRecords.stream().collect(Collectors.groupingBy(DialogRecord::getCallId));
List<String> callIds = new ArrayList<>(dialogRecordMap.keySet());
//获取messageId 用于后面删除
List<String> messageIds = dialogRecords.stream().map(DialogRecord::getMessageId).collect(Collectors.toList());
Map<String, List<DialogRecordParams>> dialogRecordParamMap = dialogRecordParamsService.getByCallIds(callIds)
.stream().collect(Collectors.groupingBy(DialogRecordParams::getCallId));
// 只查询1个月内的呼叫记录,这个任务正常只会有3天内的队列数据
LocalDate lastMonth = LocalDate.now().minusMonths(1);
Map<String, CallRecordParams> callRecordParamMap = callRecordParamsService.getByCallIds(callIds, lastMonth)
.stream().collect(Collectors.toMap(CallRecordParams::getCallId, Function.identity(), (o1, o2) -> o1));
// callRecordParamMap中没有的callId才需要查询,用于后面入库
Map<String, CallRecord> callRecordMap = callRecordService.getByCallIds(CollectionUtils.removeAll(callIds, callRecordParamMap.keySet()), lastMonth)
.stream().collect(Collectors.toMap(CallRecord::getCallId, Function.identity(), (o1, o2) -> o1));
try {
callSaveService.batchSaveDelayCall(messageIds, callIds, dialogRecordMap, dialogRecordParamMap, callRecordParamMap, callRecordMap);
//deaCallIds.clear();
} catch (Exception e) {
log.error(Thread.currentThread().getName() + "该批次处理报错" + e.getMessage(), e);
XxlJobHelper.log(Thread.currentThread().getName() + "该批次处理报错");
XxlJobHelper.log(e);
}
}
}
}
package com.yuntongxun.aihelper.api.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* @author XieHai
* @Description
* @Date 创建于 2021/7/8 下午4:28
*/
@Slf4j
public class BatchUtils {
/**
* 多线程分批处理数据
*
* @param tList 数据
* @param batchSize 每批数量
* @param executorServiceSupplier 线程池
* @param consumer 处理器
* @param <T> 数据类型
*/
public static <T> void splitConsume(List<T> tList, int batchSize,
Supplier<ExecutorService> executorServiceSupplier,
String name, Consumer<List<T>> consumer) {
if (tList.size() <= batchSize) {
consumer.accept(tList);
return;
}
List<List<T>> partitions = ListUtils.partition(tList, batchSize);
ExecutorService executorService = executorServiceSupplier.get();
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < partitions.size(); i++) {
String threadName = name + "-" + i;
List<T> partition = partitions.get(i);
futures.add(executorService.submit(() -> {
Thread.currentThread().setName(threadName);
consumer.accept(partition);
}));
}
for (Future<?> future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
log.error("future#get error", e);
}
}
}
}
package com.yuntongxun.aihelper.api.job;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.yuntongxun.aihelper.api.service.CallRecordService;
import com.yuntongxun.aihelper.api.service.ProcessStatisticService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author wangjs
*/
@Component
@Slf4j
public class ProcessStatisticJob {
@Resource
private ProcessStatisticService processStatisticService;
@Resource
private CallRecordService callRecordService;
private SimpleDateFormat sp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@XxlJob("processStatisticHandler")
public ReturnT<String> countProcessNodeExecutedInfo() {
long startTime = System.currentTimeMillis();
log.debug("开始统计流程执行情况");
try {
String lastTime = processStatisticService.getLastTime();
Date curTime = new Date();
String nowTime = sp.format(curTime);
callRecordService.getUnDealCallRecord(nowTime, lastTime, callRecordList ->
processStatisticService.countProcessStatisticInfo(callRecordList, curTime));
XxlJobHelper.log("统计流程执行情况结束 use:{}", System.currentTimeMillis() - startTime);
log.debug("统计流程执行情况结束 use:{}", System.currentTimeMillis() - startTime);
return ReturnT.SUCCESS;
} catch (Exception e) {
log.error(" 统计流程执行情况失败", e);
XxlJobHelper.log("统计流程执行情况失败 use:{}", System.currentTimeMillis() - startTime);
XxlJobHelper.log(e);
return ReturnT.FAIL;
}
}
}
public void getUnDealCallRecord(String nowTime, String lastUpdTime, Consumer<List<CallRecord>> consumer) throws ParseException {
String startTimeBegin = DateUtils.dateToSecStr(
StringUtils.isEmpty(lastUpdTime) ? new Date() : DateUtils.getDate(lastUpdTime), 60 * 24);
int current = 1;
IPage<CallRecord> pageList;
do {
Page<CallRecord> page = new Page<>(current++, BATCH_PAGE_SIZE);
LambdaQueryWrapper<CallRecord> queryWrapper = new LambdaQueryWrapper<>();
//结束时间最好,通话没有打完的情况
queryWrapper.isNotNull(CallRecord::getCallId)
.isNotNull(CallRecord::getEndTime)
.gt(CallRecord::getStartTime, startTimeBegin);
if (!StringUtils.isEmpty(lastUpdTime)) {
queryWrapper.ge(CallRecord::getEndTime, lastUpdTime);
}
queryWrapper.lt(CallRecord::getEndTime, nowTime);
queryWrapper.orderByAsc(CallRecord::getEndTime);
pageList = callRecordService.page(page, queryWrapper);
if (!pageList.getRecords().isEmpty()) {
consumer.accept(pageList.getRecords());
}
} while (!pageList.getRecords().isEmpty());
}
}