Java函数式接口

Java函数式接口概述

Java函数式接口(Functional Interface)是Java 8引入的一个重要特性,它指的是那些有且仅有一个抽象方法的接口(但可以有多个默认方法或静态方法)。这一特性与Lambda表达式紧密相关,因为Lambda表达式可以作为函数式接口的实现。
函数式接口在Java中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可
以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。

常用的应用场景

  1. 作为方法的参数
    函数式接口最常见的用途是作为方法的参数。这种方式使得方法可以接受不同类型的函数作为参数,增加了代码的灵活性和复用性。例如,java.util.Collections类中的sort方法可以接受一个Comparator接口作为参数,而Comparator正是一个函数式接口。
  2. 集合的遍历和操作
    在Java 8中,java.util.function包提供了丰富的函数式接口,如Consumer、Function、Predicate等,这些接口广泛用于集合的遍历、转换和过滤等操作。例如,使用List的forEach方法配合Consumer接口可以简洁地遍历集合;使用Stream API结合Function和Predicate接口可以实现对集合的复杂查询和转换。
  3. 异步编程
    在Java中,函数式接口也常用于异步编程场景。例如,CompletableFuture类提供了多种方法,允许你以非阻塞的方式编写异步代码。这些方法经常接受函数式接口作为参数,以便在异步操作完成时执行特定的回调函数。
  4. 事件监听和处理
    在GUI编程或任何需要事件监听的应用中,函数式接口可以作为事件处理器的类型。当事件发生时,相应的函数式接口实现(如通过Lambda表达式)会被调用,执行相应的处理逻辑。
  5. 简化代码
    函数式接口和Lambda表达式相结合,可以极大地简化代码。它们允许以更简洁、更表达性的方式编写复杂的逻辑,特别是在需要传递简单函数或行为作为参数时。
    常用函数式接口示例
    Runnable:无参数、无返回值的函数式接口,常用于需要执行无参数任务的场景。
    Supplier:不接受参数,但返回一个类型为T的值的函数式接口。
    Consumer:接受一个类型为T的参数,但不返回任何结果的函数式接口。
    Function<T, R>:接受一个类型为T的输入参数,并返回一个类型为R的结果的函数式接口。
    Predicate:接受一个类型为T的输入参数,并返回一个布尔值的函数式接口,通常用于条件判断。 结论 Java函数式接口的应用场景广泛,它们不仅提高了代码的灵活性和复用性,还简化了复杂逻辑的实现。通过合理使用函数式接口和Lambda表达式,可以编写出更加简洁、表达性更强的Java代码。
    函数式接口应用场景
    Java 函数式接口是 Java 8 引入的一个核心概念,用于支持函数式编程。函数式接口是一个只有一个抽象方法的接口,可以用作 lambda 表达式或方法引用的目标。Java 提供了许多内置的函数式接口,这些接口广泛应用于各种编程场景。以下是一些常见的应用场景:
  6. 集合操作
    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());
  1. 事件处理
    函数式接口常用于事件处理,特别是在 GUI 编程或处理回调函数时。
ActionListener: 用于处理按钮点击等 GUI 事件。
button.addActionListener(e -> System.out.println("Button clicked!"));
  1. 多线程编程
    在多线程编程中,函数式接口简化了线程的创建和执行。
Runnable: 用于定义一个无返回值的任务,通常用于线程执行。
new Thread(() -> System.out.println("Thread running")).start();
Callable<V>: 与 Runnable 类似,但可以返回结果或抛出异常。
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<Integer> future = executor.submit(() -> 42);
  1. 自定义函数式接口
    有时,你可能需要定义自己的函数式接口,用于特定场景。
    示例:定义一个处理某种特定业务逻辑的接口。
@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);
}

Calculator addition = (a, b) -> a + b;
int result = addition.calculate(5, 3); // Output: 8
  1. 异常处理
    通过使用 Function 或 Consumer 等函数式接口,可以更优雅地处理异常。
    示例:对异常进行包装处理。
Function<String, Integer> safeParseInt = str -> {
    try {
        return Integer.parseInt(str);
    } catch (NumberFormatException e) {
        return 0;
    }
};
  1. 策略模式
    使用函数式接口可以轻松实现策略模式,让代码更具灵活性。
    示例:不同的策略使用不同的函数式接口实现。
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
  1. 延迟执行
    函数式接口也可以用于定义延迟执行的操作。
    示例:使用 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());
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

思静语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值