记录一些了解的高逼格、好用的java代码
欢迎大家补充,共同学习
1. 函数式接口—@FunctionalInterface
好处:高逼格、代码收拢、解藕、统一处理
适用范围:具有共性的接口调用代码
举个栗子:
在我们平时的微服务开发中,调用其他服务的接口,通常要把接口调用部分做异常处理(try catch),或者打印异常日志或者结果日志,并且也有可能做一些统一的调用处理,比如微服务接口熔断等处理,这个时候可以适用函数式接口收拢所有的微服务调用集中处理
这是一个微服务接口
public interface TestServer {
T test();
void test2();
}
普通的方式调用上面的微服务方法
public class RpcTestServerImpl{
//引用微服务接口
private TestServer testServer;
public T test(){
try{
return testServer.test();
}catch (Exception e){
log.error("RPC error: ", e);
}
return null;
}
public void test2(){
try{
testServer.test2();
}catch (Exception e){
log.error("RPC error: ", e);
}
}
}
使用函数式接口后的写法:先定义统一调用类
//首先定义一个ServerExecutor
public class ServerExecutor {
//不需要返回值的
public static void execute(Runnable runnable) {
try {
//调用前和调用后可以做一些处理,比如熔断、日志等等
runnable.run();
//调用前和调用后可以做一些处理,比如熔断、日志等等
} catch (Exception e) {
log.error("RPC error: ", e);
}
}
//需要返回值的
public static <T> T executeAndReturn(Callable<T> callable) {
try {
//调用前和调用后可以做一些处理,比如熔断、日志等等
return callable.call();
} catch (Exception e) {
log.error("RPC invoke error", e);
}
return null;
}
}
使用统一定义的接口调用之后:
public class RpcTestServerImpl{
//引用微服务接口
private TestServer testServer;
public T test(){
return ServerExecutor.executeAndReturn(() -> testServer.test());
}
public T test2(){
ServerExecutor.execute(() -> testServer.test2());
}
}
扩展知识:
@FunctionalInterface
官方文档:https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html
通过文档可以知道:
1、该注解只能标记在有且仅有一个抽象方法的接口上。
2、JDK8接口中的静态方法和默认方法,都不算是抽象方法。
3、接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
4、该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
2. List分页工具类
平时开发中可能会遇到一批数据的批量查询,但是接口限制每次只能查10个,多了查询失败,这个时候就很烦,还的手动给这一批数据分页再去掉接口查询,这个时候就可以用到com.google.common.collect.Lists
工具的com.google.common.collect.Lists#partition
方法,这个方法可以帮我们自动拆分List,根据我们传入的分批大小,把大集合拆成多个小集合
好处:方便实用,不用手写
举个栗子:
List<Long> userIdList = new ArrayList<>();
userIdList.add(1L);
userIdList.add(2L);
userIdList.add(3L);
userIdList.add(4L);
userIdList.add(5L);
userIdList.add(6L);
userIdList.add(7L);
userIdList.add(8L);
List<List<Long>> partition = Lists.partition(userIdList, 3);
上面这个代码,就可以把8个元素的userIdList通过Lists.partition拆分成3个元素一个的小集合,而且顺序不变,以前不知道这个方法,被同事diss一波,然后记录一下
3. 分割符的字符串和集合互转
日常开发经常遇到这种:1,2,3,4,5,6
这种逗号分割的,要把他转成List,或者把一个List转成根据逗号分割的字符串,自己写的话就只能split加循环,这个时候又可以用到谷歌工具包
好处:方便实用,不用手写
举个栗子:
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import java.util.List;
public class CollectionUtil {
/**
* 把list的数据根据split拼接起来,自动过滤data中的null
* 栗子:data:[1,3,4,6] split:"." return: "1.3.4.6"
*/
public static String jointList(List data, String split){
return Joiner.on(split).skipNulls().join(data);
}
/**
* 把根据split分隔的字符串str变成List
* 栗子:str:"1.3.5.7.9" split:"." return:["1","3","5","7","9"]
*/
public static List<String> splitIntoList(String str, String split){
return Splitter.on(split).omitEmptyStrings().splitToList(str);
}
}
4. 使用策略模式消除大篇幅if else
日常开发经常会遇到需要大量的if else判断,然后执行对应的代码,如果业务复杂,可能会十几个或者几十个if else,对于代码的扩展性和可读性有很大影响,而且看起来就很low,所以我们可以用到策略模式来消除大量的if else,并且让代码更具有健壮性
好处:高逼格,代码健壮,扩展性强
举个栗子:
//举例老的if else
if ("xxx".equals(type)) {
System.out.println("do xxx");
} else if ("yyy".equals(type)) {
System.out.println("do yyy");
} else if ("zzz".equals(type)) {
System.out.println("do zzz");
} else if ("xyz".equals(type)) {
System.out.println("do xyz");
}
// 后面可能会有更多
随着业务发展,可能会有非常多的if else,每次添加逻辑都要改这里的if else,代码篇幅会非常长,而且不利于维护,还违反了开闭原则
这个时候我们可以用策略模式出来处理:
//定义策略执行接口
public interface DataProcessor {
Result handleData(Data data);
}
//定义每种数据的处理实现类
@Service("XxxProcessor")
public class XxxProcessor implements DataProcessor {
@Override
public Result handleData(Data data){
//do xxx ....
return null;
}
}
@Service("YyyProcessor")
public class YyyProcessor implements DataProcessor {
@Override
public Result handleData(Data data){
//do yyy ....
return null;
}
}
@Service("ZzzProcessor")
public class ZzzProcessor implements DataProcessor {
@Override
public Result handleData(Data data){
//do zzz ....
return null;
}
}
@Service("XyzProcessor")
public class XyzProcessor implements DataProcessor {
@Override
public Result handleData(Data data){
//do Xyz ....
return null;
}
}
每一个DataProcessor的实现类,就相当于一个if else中的处理逻辑执行器
然后定义一个枚举,把这些逻辑执行器管理起来:
public enum DataProcessorEnum {
XXX(1, "xxx业务描述", "XxxProcessor"),
YYY(2, "yyy业务描述", "YyyProcessor"),
ZZZ(3, "zzz业务描述", "ZzzProcessor"),
XYZ(4, "xyz业务描述", "XyzProcessor");
/**
* 业务type
*/
private Integer type;
/**
* 业务描述
*/
private String name;
/**
* 对应处理器
* {@link DataProcessor}
*/
private String processor;
public static DataProcessorEnum valueOf(Integer type) {
if (type == null) {
return null;
}
for (DataProcessorEnum dataProcessorEnum : DataProcessorEnum.values()) {
if (type.equals(dataProcessorEnum.getType())) {
return dataProcessorEnum;
}
}
return null;
}
//省略get等方法
}
当实际使用的时候,我们需要拿到对应处理器的实例,这里举例是在spring容器中为例,因为DataProcessor实现上都有@Service
注解,如果不在spring容器中,可以自己实现反射获取实例的方法,这里就不再写反射获取了,大多数情况我们都是在spring环境中适用的,所以需要工具从spring 中获取对应实例,提供一个spring实例获取工具,这个应该很常见:
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringUtil.applicationContext == null) {
SpringUtil.applicationContext = applicationContext;
}
}
//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//通过name获取 Bean.
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}
//通过class获取Bean.
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
//通过name,以及Clazz返回指定的Bean
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}
}
准备工作到这里应该算是完成了,实际使用中,可以这样操作:
public void dealData(Data bizData){
if(bizData == null){
return;
}
DataProcessorEnum dataProcessorEnum = DataProcessorEnum.valueOf(bizData.getType());
if(dataProcessorEnum == null){
//没有在枚举中匹配到处理器,说明业务参数不合法或者没有添加对应的业务枚举
return;
}
DataProcessor processor = SpringUtil.getBean(dataProcessorEnum.getProcessor(), DataProcessor.class);
if(processor == null){
//没有从spring容器中获取到对应处理器到实例,属于异常情况,检查枚举配置和处理器是否正确注入spring容器
return;
}
//交给对应到处理器去处理
Result result = processor.handleData(bizData);
//处理完成
}
到这里,使用策略模式消除if else就基本写完了,这里只是写出了基本到策略模式使用,还有其他更多的用法可以自己学习一下,而且其实我们使用策略模式前和使用后,感觉代码篇幅不减反增,肯定会想,这改造越改越复杂了呀
所以得出结论:if-else或switch case 这种分支判断的方式对于分支逻辑不多的简单业务,还是直观高效的。对于业务复杂,分支逻辑多,采用适当的模式技巧,会让代码更加清晰,容易维护,但同时类或方法数量也是倍增的。我们需要对业务做好充分分析,避免一上来就设计模式,避免过度设计!
另外再说一下,你就天天光写if else 和实习生有啥区别?怎么提升逼格?对吧
5. 当策略模式使用不值得的时候,如果优雅的处理大量if else
上面刚说完使用策略模式消除if else,立马先打脸自己,打脸自己才能成长,这次说一个使用java.util.function.Function
jdk1.8 函数接口方式处理大篇幅if else的方式
好处:高逼格、比策略模式看起来简单明了(但是需要了解函数式编程,很简单)
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class Test {
/**
* 传统的 if else 解决方法
* 当每个业务逻辑有 3 4 行时,用传统的策略模式不值得,直接的if else又显得不易读
*/
public String doBizService(String order) {
if ("type1".equals(order)) {
return "执行业务逻辑1";
} else if ("type2".equals(order)) {
return "执行业务逻辑2";
}else if ("type3".equals(order)) {
return "执行业务逻辑3";
}else if ("type4".equals(order)) {
return "执行业务逻辑4";
}else if ("type5".equals(order)) {
return "执行业务逻辑5";
}else if ("type6".equals(order)) {
return "执行业务逻辑6";
}else if ("type7".equals(order)) {
return "执行业务逻辑7";
}else if ("type8".equals(order)) {
return "执行业务逻辑8";
}else if ("type9".equals(order)) {
return "执行业务逻辑9";
}
return "不在处理的逻辑中返回业务错误";
}
//---------------------------高逼格分割线------------------------------------
/**
* 业务逻辑分派Map
* Function为函数式接口,
* 下面代码中 Function<String, Object> 的含义是接收一个String类型的变量用来获取你要执行哪个Function,实际使用中可自行定义
* Function执行完成返回一个Object类型的结果,这个结果就是统一的业务处理返回结果,实际使用中可自行定义
*/
private static Map<String, Function<String, Object>> checkResultDispatcher = new HashMap<>();
/**
* 初始化 业务逻辑分派Map 其中value 存放的是 lambda表达式
* 也可以依赖于spring的@PostConstruct 初始化checkResultDispatcher 根据各个技术栈调整
*/
static {
checkResultDispatcher.put("type1", order -> testService.handleTyep1(order));
checkResultDispatcher.put("type2", order -> testService.handleTyep2(order));
checkResultDispatcher.put("type3", order -> testService.handleTyep3(order));
checkResultDispatcher.put("type4", order -> testService.handleTyep4(order));
checkResultDispatcher.put("type5", order -> testService.handleTyep5(order));
checkResultDispatcher.put("type6", order -> testService.handleTyep6(order));
checkResultDispatcher.put("type7", order -> testService.handleTyep7(order));
checkResultDispatcher.put("type8", order -> testService.handleTyep8(order));
checkResultDispatcher.put("type9", order -> testService.handleTyep9(order));
}
public Object handleBizService(String type) {
//从逻辑分派Dispatcher中获得业务逻辑代码,result变量是一段lambda表达式
Function<String, Object> result = checkResultDispatcher.get(type);
if (result != null) {
//执行这段表达式获得String类型的结果
return result.apply(type);
}
return "不在处理的逻辑中返回业务错误";
}
}
补一下关于java.util.function
包下面各个类的简单了解
name | type | description |
---|---|---|
Consumer | Consumer< T > | 接收T对象,不返回值 |
Predicate | Predicate< T > | 接收T对象并返回boolean |
Function | Function< T, R > | 接收T对象,返回R对象 |
Supplier | Supplier< T > | 提供T对象(例如工厂),不接收值 |
UnaryOperator | UnaryOperator< T > | 接收T对象,返回T对象 |
BiConsumer | BiConsumer<T, U> | 接收T对象和U对象,不返回值 |
BiPredicate | BiPredicate<T, U> | 接收T对象和U对象,返回boolean |
BiFunction | BiFunction<T, U, R> | 接收T对象和U对象,返回R对象 |
BinaryOperator | BinaryOperator< T > | 接收两个T对象,返回T对象 |
使用Function
处理策略需求确实很方便很好用,但是有一个问题,就是在Function
中无法抛出非RuntimeException
的异常,很多时候我们自定义的异常并不是继承自RuntimeException
的,这个时候使用Function
就会被异常困扰,这个时候我们可以模仿Function
来自定义一个我们自己的FunctionWithException
:
/**
* 一个可以抛出Exception的Function
* 是java.util.function.Function的变体
* 主要解决java.util.function.Function中不能抛出非RuntimeException的问题
*/
@FunctionalInterface
public interface FunctionWithException<T, R> {
R apply(T t) throws Exception;
}
然后再做一个FunctionWithException
的包装类:
import java.util.function.Function;
/**
* FunctionWithException的包装器
*/
public class ExceptionWrapper {
public static <T, R> Function<T, R> wrap(FunctionWithException<T, R> function) {
return t -> {
try {
return function.apply(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
}
这个时候我们就可以不使用java.util.function.Function
,而使用我们自己的FunctionWithException
,使用例子如下:
private static Map<String, Function<String, Object>> checkResultDispatcher = new HashMap<>();
@PostConstruct
public void init() {
checkResultDispatcher.put("type1", ExceptionWrapper.wrap(this::handleTyep1));
checkResultDispatcher.put("type2", ExceptionWrapper.wrap(this::handleTyep2));
}
public Object handleBizService(String type) throws Exception {
//从逻辑分派Dispatcher中获得业务逻辑代码,result变量是一段lambda表达式
Function<String, Object> result = checkResultDispatcher.get(type);
if (result == null) {
// 这个BizException是自定义的异常 它继承自Exception.class
throw new BizException("参数异常");
}
try {
return result.apply(type);
} catch (Exception e) {
// 捕获内部的异常,转成我们自定义的异常处理
throw new BizException(e.getMessage());
}
}
private Object handleTyep1(String type){
//...
}
private Object handleTyep2(String type){
//...
}
这样就可以完美的处理java.util.function.Function
的异常抛出问题了.
6. 通用的redis获取值并处理空缓存方法
日常开发会遇到一种缓存无效的情况:普通用获取redis值的时候,发现获取到的是空字符串,这个时候就会去查库,然后mysql库里面查询结果也是空的,这个时候就会有问题,如果一直调用这个key获取数据,缓存查不到,数据库也查不到,如果高频调用就会对数据库造成压力。对于这种情况,可以在库里查询为空的时候,给redis里面存一个空缓存{}
,当下次查询redis的时候,得到的不再是空字符串""
,对于这种逻辑,本来是需要自己写逻辑的,然后每个这种都要写一遍判断逻辑,后来在开发中遇到同事(大佬)写的一个通用方法,很好用,记录下来:
public class CacheConstant {
/**
* 各类空缓存对象value
*/
public static final String EMPTY_CACHE = "{}";
}
public static <T> T get(String key, Long timeout, Class<T> clazz, Callable<T> getFunction, Runnable exceptionRunnable) {
RedisService redisService = SpringUtil.getBean(RedisService.class);
if (redisService == null) {
log.error("Redis查询接口 初始化异常");
return null;
}
if (StringUtils.isBlank(key)) {
return null;
}
String cacheValue = redisService.get(key);
T object = null;
if (StringUtils.isNotBlank(cacheValue)) {
if (CacheConstant.EMPTY_CACHE.equals(cacheValue)) {
return null;
}
try {
object = JSONObject.parseObject(cacheValue, clazz);
} catch (Exception e) {
log.error("RedisUtil get error, key:[" + key + "], cacheValue:[" + cacheValue + "]", e);
}
if (object != null) {
return object;
} else {
//说明解析发生了异常,调用解析异常处理,比如可以做一些缓存过期处理之类的操作
if(exceptionRunnable != null){
exceptionRunnable.run();
}
}
}
try {
if(getFunction != null){
object = getFunction.call();
}
} catch (Exception e) {
log.error("getFunction.call error", e);
return null;
}
cacheValue = object != null ? JsonUtil.object2json(object) : CacheConstant.EMPTY_CACHE;
redisService.set(key, cacheValue, timeout);
return object;
}
7. CompletableFuture基本使用
//创建第一个任务CompletableFuture 使用线程池taskExecutor
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> countVO.setTotal(baseDao.selectCount(queryWrapper)), taskExecutor);
//创建第二个任务CompletableFuture 使用线程池taskExecutor
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> countVO.setTotal(baseDao.selectCount(queryWrapper)), taskExecutor);
//创建第三个任务CompletableFuture 使用线程池taskExecutor
CompletableFuture<Void> future3 = CompletableFuture.runAsync(() -> countVO.setTotal(baseDao.selectCount(queryWrapper)), taskExecutor);
//添加三个CompletableFuture到allFuture
CompletableFuture<Void> allFuture = CompletableFuture.allOf(future1, future2, future3);
// allFuture 等待三个任务执行完成并结束,超时时间200毫秒
allFuture.get(200, TimeUnit.MILLISECONDS);
该用法类似于CountDownLatch的使用,创建多个异步任务,在主线程中等待每个任务执行完成
CompletableFuture的方法有很多,各种组合可以实现不同的功能。大致通过方法名称可以总结为:
创建类
- runAsync 异步执行,无返回值
- supplyAsync 异步执行,有返回值
- anyOf 任意一个执行完成,就可以进行下一步动作
- allOf 全部完成所有任务,才可以进行下一步任务
接续类
- 以Async结尾的方法,都是异步方法,对应的没有Async则是同步方法,一般都是一个异步方法对应一个同步方法。
- 以Async后缀结尾的方法,都有两个重载的方法,一个是使用内容的forkjoin线程池,一种是使用自定义线程池
- 以run开头的方法,其入口参数一定是无参的,并且没有返回值,类似于执行Runnable方法。
- 以supply开头的方法,入口也是没有参数的,但是有返回值
- 以Accept开头或者结尾的方法,入口参数是有参数,但是没有返回值
- 以Apply开头或者结尾的方法,入口有参数,有返回值
- 带有either后缀的方法,表示谁先完成就消费谁
- whenCompleteAsync:处理完成或异常,无返回值
- handleAsync:处理完成或异常,有返回值
状态取值类
- join 合并结果,等待
- get 合并等待结果,可以增加超时时间;get和join区别,join只会抛出unchecked异常,get会返回具体的异常
8. @ConditionalOnMissingBean使用
@ConditionalOnMissingBea该注解表示,如果存在它修饰的类的bean,则不需要再创建这个bean。日常代码中写公共jar包代码,经常会有接口需要默认实现和自定义实现,jar包中写公共实现,如果需要对接口实现自定义实现,就可以使用@ConditionalOnMissingBean快速实现
/**
* 默认实现配置
*/
@Configuration
public class DefaultConfiguration {
@Bean
@ConditionalOnMissingBean(name = "TestDefaultService")//指定Bean的名称
public TestDefaultService testService() {
return (param) -> {
//...默认公共实现
};
}
}
@Qualifier("TestDefaultService")//指定Bean名称
@Resource
private TestDefaultService testDefaultService;
这样使用之后,当手动实现了TestDefaultService
并指定BeanName为“TestDefaultService”
,比如在接口实现类上这样写之后
@Service("TestDefaultService")
手动实现的接口就会被注入,而默认实现的就不会被注入,如果不手动实现接口,那么默认实现就会被注入。
9. 使用函数式编程实现代理模式
先上代码:
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 返回值
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class Test1DTO extends TestBaseDTO {
private String name;
}
import lombok.Data;
/**
* 返回值基类
*/
@Data
public class TestBaseDTO {
private Integer value;
}
public interface Test1Service {
TestBaseDTO handle() throws Exception;
}
public class Test1Util {
public static <R extends TestBaseDTO> R execute(Test1Service test1Service) throws Exception {
System.out.println("在执行之前做一些事情");
R handle = (R) test1Service.handle();
System.out.println("在执行之后做一些事情");
return handle;
}
}
/**
* 测试类
*/
public class Test1 {
public static void main(String[] args) throws Exception {
TestBaseDTO execute = Test1Util.execute(() -> {
// 这里可以写想要执行的逻辑代码
// 返回值可以自定义 只要继承TestBaseDTO就行
Test1DTO testBaseDTO = new Test1DTO();
testBaseDTO.setValue(1);
testBaseDTO.setName("test");
return testBaseDTO;
});
System.out.println(execute);
}
}
如上代码,就是通过函数式编程搞了一个Test1Util
来处理逻辑代码执行时被代理的逻辑。这个方式可改造的方式很多,可以轻易改造成适合自己的业务的写法。
10. 根据数学公式计算结果的工具类
依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress</artifactId>
<version>3.2.0</version>
</dependency>
工具类:
/**
* 公式计算工具
*
* @author jingchuan
*/
public class FormulaCalculateUtil {
public static final ExpressRunner runner = new ExpressRunner(true, false);
public static final DefaultContext<String, Object> context = new DefaultContext<>();
/**
* 根据计算公式 和 公式中每个元素的值 计算出结果
*
* @param formula 计算公式
* @param map 公式中每个元素的值
* @param <T>
* @return
* @throws Exception
*/
public static <T> T calculate(String formula, Map<String, Object> map) throws Exception {
if (map != null && !map.isEmpty()) {
context.putAll(map);
}
try {
return (T) runner.execute(formula, context, null, true, false);
} finally {
context.clear();
}
}
public static void main(String[] args) throws Exception {
String expression = "A+(B-C)*D/E";
Map<String, Object> map = new HashMap<>();
map.put("A", 5);
map.put("B", 4);
map.put("C", 3);
map.put("D", 2);
map.put("E", 2);
Object calculate = FormulaCalculateUtil.calculate(expression, map);
System.out.println("计算结果:" + calculate.toString());
}
}
这个工具类是阿里大佬写的,依赖中有很多其他功能,这里只是使用了其中的公式计算