Java函数式接口
有且只有一个抽象方法的接口被称为函数式接口.
@FunctionalInterface注解: 该注解可用于一个接口的定义上, 一旦使用该注解来定义接口, 编译器将会强制检查该接口是否确实有且仅有一个抽象方法, 否则将会报错.该注解不是必须的, 只要符合函数式接口的定义,那么这个接口就是函数式接口.
1. Java中函数式接口应用
Runnable 接口
@FunctionalInterface
public interface Runnable {
// 仅有一个run方法
public abstract void run();
}
Executor接口定义
public interface Executor {
// 接收一个Runnable对象
void execute(Runnable command);
}
线程池中使用submit()方法提交任务
public class ThreadPoolDemo {
private final static ThreadPoolExecutor EXECUTOR =
new ThreadPoolExecutor(8, 8, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1000000), new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
for (int i = 1; i <= 100; i ++){
// () -> System.out.println("hello world") 即表示一个Runnable对象, Java8之前为匿名内部类
EXECUTOR.execute(() -> System.out.println("hello world"));
// EXECUTOR.execute(new Runnable() {
// @Override
// public void run() {
// System.out.println("hello world");
// }
// });
}
}
}
2. 自定义函数接口
public interface ExecuteService {
void execute(String message);
}
public class Demo {
public void execute(ExecuteService service, String message){
service.execute(message);
}
public static void main(String[] args) {
Demo demo = new Demo();
String message = "你好";
demo.execute(message1 -> System.out.println(message1), message);
}
}
使用场景
在实际开发中, 经常会遇到针对多个业务过程统计表的统计查询.
比如:
指定一个时间段, 查询该时间段的统计数据
指定一个时间段, 查询该时间段的统计数据, 并根据时间分组(比如按每天)
在表的数据量较小的情况下, 一般可以直接进行统计.
SELECT COUNT(*) FROM 表 WHERE create_time >= #{T1} AND create_time <= #{T2};
如果表的数据量很大的情况下, 则COUNT的数据量可能很大, 导致SQL执行慢.
遇到这种情况一般先会针对SQL进行优化, 比如走索引. 如果走索引还是因为数据量大而导致查询慢,那么就需要将查询时间进行拆分, 拆分成一天一天查询.并开通多线程进行查询, 以多次IO数据库换取减少慢SQL操作.
SELECT COUNT(*) FROM 表 WHERE create_time = #{T};
实际场景应用
定义函数式接口
public interface OldFlowTrendCallback {
/**
* 查询数据趋势
* @param param
* @return
*/
List execute(OldFlowDashBoardParamDTO param);
}
定义针对批量多线程查询的接口方法
@Service
@Slf4j
public class OldFlowBatchSelectExecutor extends BaseBatchSelect {
/** 业务线程池*/
@Autowired
private ThreadPoolExecutor statThreadPoolExecutor;
public List execute(OldFlowTrendCallback callback, OldFlowDashBoardParamDTO dto){
Date startTime = dto.getStartTime();
Date endTime = dto.getEndTime();
// 判断是否查询的时间段是同一天, 如果是, 则直接执行
if (DateUtil.isTheSameDay(startTime, endTime)) {
setAssignDate(dto);
return callback.execute(dto);
}
// 创建队列, 接收异步线程执行结果
BlockingQueue>> queue = new LinkedBlockingQueue<>();
// 如果查询时间段为一个时间段, 那么则拆分成一天一天的查询处理
while (startTime.getTime() < endTime.getTime()) {
OldFlowDashBoardParamDTO dayParam = VOUtil.from(dto, OldFlowDashBoardParamDTO.class);
dayParam.setStartTime(startTime);
dayParam.setEndTime(startTime);
setAssignDate(dayParam);
// 通过线程池异步执行任务, 返回Futrue
Future> future = statThreadPoolExecutor.submit(() -> callback.execute(dayParam));
queue.add(future);
startTime = DateUtil.getNextDateFirst(startTime);
}
// 封装返回接口, 主要是使用Futrure接口get()方法, 阻塞获取异步线程执行的结果
return packBatchSelectResult(queue);
}
private void setAssignDate(OldFlowDashBoardParamDTO dto) {
Date startAssignDate = DateUtil.addDateDays(dto.getStartTime(), - dto.getAssignDateType());
Date endAssignDate = DateUtil.getYestodayFirst(dto.getStartTime());
dto.setStartAssignDate(startAssignDate);
dto.setEndAssignDate(endAssignDate);
}
}
业务方法
public List listOldFlowGroupByStatDate(OldFlowDashBoardParamDTO dto) {
// 通过OldFlowBatchSelectExecutor 统一处理拆分多线程查询
// (dayParam) -> effectiveFollowingMapper.listOldFlow(dayParam)): 即是OldFlowTrendCallback接口实现
return oldFlowBatchSelectExecutor.execute(((OldFlowTrendCallback) (dayParam) -> effectiveFollowingMapper.listOldFlow(dayParam)), dto);
}