@TOC
前言
在这里总结java研发过程中一些实用的工具/类/方法/特性,有时合理运用能够提升效率并且节省工作量,使得代码更丝滑更优雅。
一、Mapstruct
MapStruct 是一个代码生成器(可以对比其他的一共12种如BeanUtils和Jmapper;地址zhuanlan.zhihu.com/p/460067093... Java Bean 类型之间映射的实现。生成的映射代码使用纯方法调用,因此快速、类型安全且易于理解。 MapStruct 是一个 Java 注释处理器,适用于Java Bean 类型之间映射,多层应用程序通常需要在不同的对象模型(例如实体和 DTO)之间进行映射。与其他映射框架相比,MapStruct在编译时生成bean映射,这确保了高性能,允许快速的开发人员反馈和彻底的错误检查。遵循约定而不是配置方法,MapStruct使用合理的默认值,但在配置或实现特殊行为时会避开您的方式。当不同层级的类对象之间有公共的属性,需要进行两者间相互转换的时候,可以用mapstruct来构建两者的convert方法进行直接转换,不需要手动设置属性方法,减少工作量。
java
//举例:奖品发放记录插入数据库 从Record到PO的转换
import org.mapstruct.factory.Mappers;
//unmappedTargetPolicy为ERROR 表示匹配策略是 不匹配会报错
@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
//DecoratedWith表示内部可以用一个子类作为装饰器装饰接口内的方法
@DecoratedWith(IssueRecordConverterDecorator.class)
public interface IssueRecordConverter {
//获取对应转化实例
IssueRecordConverter INSTANCE = Mappers.getMapper(IssueRecordConverter.class);
//进行对象映射转换的方法 忽略"id"这个公共基础属性
@Mapping(target = "id", ignore = true)
IssueRecordPO convertToInsert(IssueRecord record);
}
二、Optional
java8的新特性,Java中的Optional是一个容器对象,它可以包含一个非空值,也可以为空。它的主要作用是在编写代码时避免空指针异常。用了之后其实不需要后面加多余的 if(obj!=null) 的判断了
java
//示例:查询后台的所有活动项配置列表
public List<ActivityConfig> queryActivityConfigList(Long areaId, ActivityTypeEnum activityType) {
List<ActivityConfigPO> configPOList = xxxConfig.getAreaAcvitityMap().get(areaId);
//当configPOList不为null时候返回配置list 为null直接走orElse会new一个list返回
return Optional.ofNullable(configPOList)
.map(list -> list.stream()
.map(mapToConfig(areaId, activityType)
.collect(Collectors.toList()))
.orElse(new ArrayList<>());
}
三、Functional
java8的新特性,函数式接口,即只包含一个抽象方法的接口,也称为SAM接口,即Single Abstract Method interfaces,用@FunctionalInterface注解进行标注,常见的函数式接口如Comparator,Runnable,Callable。可以轻量级的将代码封装为数据,使代码简洁,易于理解。
通常在流式计算返回时候内部调用一下。
java
//上面的结果返回list -> list.stream().map(mapToConfig(areaId, activityType).collect(Collectors.toList())) 内部调用了mapToConfig方法
@NotNull
private static Function<ActivityConfigPO, ActivityConfig> mapToConfig(Long areaId, ActivityTypeEnum tag) {
return configPO -> ActivityConfig.builder()
.id(configPO.getId())
.areaId(areaId)
.desc(configPO.getDesc())
.activityType(tag)
.activityId(configPO.getActivityId())
.build();
}
四、Stream
java8新特性,简单返回对象映射转换的时候可以使用。上面示例返回map(list -> list.stream().map(mapToConfig(areaId, activityType).collect(Collectors.toList())));用到了
java
//把list转换成流 取出里面的ActivityConfigPO对象与现有Id比较 支持filter()过滤
public static ActivityConfigPO findItem(List<ActivityConfigPO> list, Long Id) {
return list.stream().filter(item -> item.getId().equals(Id)).findFirst().orElse(null);
}
又或者java的基本数据和引用数据转换,将int[]数组转换成Integer[]数组: ArrayList.asList List.toArray Stream()->函数 boxed()拆装箱
java
import java.util.Arrays;
import java.util.stream.IntStream;
public class ArrayConvertExample {
public static void main(String[] args) {
Integer[] obj = new Integer[] {null, 1, 2, 3};
int[] newObj = Arrays.stream(obj).mapToInt(i -> i == null ? 0 : i.intValue()).toArray();
//Integer[] obj = new Integer[] {1, 2, 3};
//int[] newObj = Arrays.stream(obj).mapToInt(Integer::valueOf).toArray();
int[] obj2 = new int[] {4, 5, 6};
Integer[] newObj2 = Arrays.stream(obj2).boxed().toArray(Integer[]::new);
Integer[] newObj3 = IntStream.of(obj2).boxed().toArray(Integer[]::new);
}
}
注意下Stream.map()、Stream.filter()功能区别 map--映射,filter--匹配过滤 filter()方法用于根据条件过滤流中的元素; map()方法用于对流中的每个元素执行转换操作。 根据具体的需求,选择使用filter()方法还是map()方法。如果你想筛选满足特定条件的元素,使用filter();如果你想对元素进行转换,使用map()。 1.forEach----主要用于改变当前数组里面的值,forEach方法是Stream中的一个方法,它可以让我们对Stream中的元素进行遍历。forEach方法接收一个Consumer类型的参数,这个Consumer类型的参数其实就是一个函数式接口,它的参数为Stream中的元素,没有返回值。 2.filter----主要用于过滤当前数组,找出符合条件的元素,返回一个新的数组,不会改变原数组。filter()方法用于根据指定的条件筛选流中的元素,只保留符合条件的元素。它接受一个Predicate函数式接口作为参数,该接口定义了用于判断元素是否满足条件的方法。 3.map----则可以改变当前循环的值,返回一个新的被改变过值之后的数组,一般用来处理需要修改某一个数组的值。map()方法用于对流中的每个元素执行一个映射操作,将每个元素根据指定的映射规则转换为另一种类型。它接受一个Function函数式接口作为参数,该接口定义了将元素转换为另一种类型的方法。 参考博客blog.csdn.net/Whx123A/art...
五、Builder
以往创建对象和属性Set赋值是分离的,或者通过构造器方式需要有对应的构造函数,整个过程不够直观和连贯。 因此我们可以通过@lombok.Builder注解,进行建造者模式的构建,能够清晰理解对象创建和属性注入需要的内容和具体构建过程。
java
//创建一个活动类
//对应类先加上@lombok.Builder注解
@Data
@lombok.Builder
public class ActivityEntity {
}
//创建一个活动的方法
private xxx builderActivity(ActivityRequest request) {
//......中间过程......
return ActivityEntity.builder()
.id(request.getActivityId()) //活动Id
.name(request.getName()) //活动名称
.status(request.getStatus()) //活动状态
.startTime(request.getStartTime()) //活动开始时间
.endTime(request.getEndTime()) //活动结束时间
.description(request.getDescription()) //活动描述
.build();
}
六、String.format
以前我们写胶水代码时候喜欢通过类似str1+str2的方式拼串,但是这种拼起来的结构看起来比较混乱,阅读起来不直观,容易出错。现在我们可以用String.format划分固定和可变部分进行格式化替换输出:
java
示例:客户端展示输出"满x元减y元优惠券"
//以前的拼串写法
String str = "满"+x+"元减"+y+"元优惠券";
//用String.format格式化输出
private static final String COUPON_DESC = "满%s元减%s元优惠券";
String realCoupon = String.format(COUPON_DESC, x, y);
或者当我们想进行指定类型输出的时候,如汇率与金额的浮点运算结果需要保留特定有效位数:
java
//float x 汇率 float y 金额
log.fine((String.format("ExRate %.2f, 金额$%.2f)", x, y));
七、Logger
以前我们打印日志喜欢用的输出可能是这些:
java
System.out.println("xxxxxx");
System.out.printf("xxxxxx");
try{
//do something
}catch(exception e){
e.printStackTrace("xxxxxx");
}finally{
//do something
}
但是System.out和printStackTrace都是存在线程死锁风险的: 参考www.kdun.cn/ask/9230.ht... System.out属于输出流,多线程争夺共享资源时候可能会发生阻塞导致死锁; printStackTrace占用系统资源,多线程争夺共享资源时候可能会死锁。并且可能会不断向上请求栈,直到异常被JVM捕获,如果一直没处理则也可能会导致死锁。 早期的java自带的log4j也可能死锁,还是推荐使用@slf4j,可以避免死锁问题。 业务常用的方式是@Slf4j带的日志,不会有上面的风险,先定义一个Logger,然后支持不同级别的输出如fine()/info()/error()等。
java
//定义一个logger
private static final Logger LOGGER = LoggerFactory.getLogger(xxx.class);
//使用@Slf4j或者log4j的功能输出
try {
//createOrder(); 创建订单
} catch (Throwable e) {
LOGGER.error("创建订单异常:buildOrderEntity={}, buildOrderEntity, e);
throw new BizException(OrderErrorCode.ORDER_CREATE_ERROR);
}
八、FastJSON
据说是速度最快的JSON序列化工具(可以对比一些其他的比如Gson、java自带的Jackson等;地址blog.csdn.net/qinxun20080...
java
//对象转换
Object obj = new Obj();
String jsonString = JSON.toJSONString(obj);
Object obj = JSON.parseObject(jsonString);
JSONobject jsonObj = JSON.toJSON(obj);
//列表转换
List<Object> objList = new ArrayList<>();
String jsonListString = JSON.toJSONString(objList);
List<Object> objList = JSON.parseArray(jsonListString);
//获取对应内部属性
//Object obj = jsonString.get(key);
//String str = jsonString.getString();
//校验String类型防止异常
if(content instanceof String) return content;
else return JSON.toJSONString(content);
但是要注意有一些坑,比如JSON.toJSONString里面传入String参数会报错,输入的时候先校验一下,if(content instanceof String) 就直接传输String类型的content就行。并且要区分是转成对象用parseObject,转成列表要用parseArray。
九、Validator
通常在Facade层或者Service入参调用真正的内部方法之前先加上一层参数校验,以防止对真正的业务逻辑造成注入干扰。Validator可以手动定义,也可以基于javax.validation包的默认方法校验,或者公司内部的FastValidator工具。 使用示例,调用Service注册方法之前对用户上传信息进行前置校验:
java
//用户上传信息校验
//注入之前通过@Component标识的UserInfoValidator实例
@Autowired
private UserInfoValidator userInfoValidator ;
//真正使用的地方
@Override
public Boolean validUserInfo(Request request) {
return userInfoValidator.validUserInfo(request);
}
//内部的校验函数可以自己写
Boolean validUserInfo(){return xxxx;}
//或者用自动的 validate方法可以是Spring框架重写实现好的
xxx validate = validator.validate(request);
十、Result< T>
对返回的结果进行包装,这个Result泛型类是可以自己定义的,把核心返回值作为泛型类Result的一个子属性,并且可以返回一些其他扩展属性比如自定义的model数据、响应码code和message等,方便定位分析排查执行信息和返回状态。
java
@Override
public Result<xxxxResponse> xxxQuery (Request request) {
return ResultWrapper.wrapper(() -> {
XxxxResponse xxxxResponse = new xxxxResponse();
response.setXXXX(xxxx);
response.setXXXX(xxxx);
response.setXXXX(xxxx);
return xxxxResponse;
});
}
十一、Wrapper
可以理解成一种装饰器模式,通过wrapper方法对返回结果进行包装,支持传入一个函数式接口作为参数,并且返回结果可以用Result< xxxxResponse>去接收。
java
@Slf4j
public class ResultWrapper {
public static <T> Result<T> wrapper(Supplier<T> supplier) {
//do something
}
@Override
public Result<xxxxResponse> xxxQuery (Request request) {
return ResultWrapper.wrapper(() -> {
XxxxResponse xxxxResponse = new xxxxResponse();
response.setXXXX(xxxx);
response.setXXXX(xxxx);
response.setXXXX(xxxx);
return xxxxResponse;
});
}
十二、BeanUtils
很多场景Spring提供的BeanUtils性能不如MapStruct,但是还是介绍一下用法。
java
//@ApiModelProperty是swagger的注解,它的作用是添加和操作属性模块的数据
@ApiModelProperty(value = "Function_call")
private Function_call function_call;
//属性拷贝赋值 第一个参数是被赋值的
BeanUtils.copyProperties(functionDTO.getProperties(), properties);
虽然Assembler/Converter是非常好用的对象,但是当业务复杂时,手写Assembler/Converter是一件耗时且容易出bug的事情,所以业界会有多种Bean Mapping的解决方案,从本质上分为动态和静态映射。 动态映射方案包括比较原始的 BeanUtils.copyProperties、能通过xml配置的Dozer等,其核心是在运行时根据反射动态赋值。动态方案的缺陷在于大量的反射调用,性能比较差,内存占用多,不适合特别高并发的应用场景。 所以在这里我给用Java的同学推荐一个库叫MapStruct(MapStruct官网)。MapStruct通过注解,在编译时静态生成映射代码,其最终编译出来的代码和手写的代码在性能上完全一致,且有强大的注解等能力。如果你的IDE支持,甚至可以在编译后看到编译出来的映射代码,用来做check。在这里我就不细讲MapStruct的用法了,具体细节请见官网。 用了MapStruct之后,会节省大量的成本,让代码变得简洁。 参考博客 blog.csdn.net/Taobaojishu...
十三、 拆装箱
JDK1.5以后java提供了自动拆装箱的机制,但是有时候比如Stream计算时候,还是可能需要手动代码实现拆装箱的。比如Java中的IntStream boxed()方法,可以将int基本类型装箱成Integer。
java
import java.util.*;
import java.util.stream.Stream;
import java.util.stream.IntStream;
public class Demo {
public static void main(String[] args) {
IntStream intStream = IntStream.range(20, 30);
Stream<Integer> s = intStream.boxed();
s.forEach(System.out::println);
}
}
常用的场景是 Java Stream中List、Integer[]、int[] 的相互转换。参考博客 blog.csdn.net/studyday1/a...
java
//int[] => IntStream => Stream<Integer> => Integer[]
Integer[] vinteger = Arrays.stream(vint).boxed().toArray(Integer[]::new);
//Integer[] => Stream<Integer> => IntStream=> int[]
int[] vint = Arrays.stream(vinteger).mapToInt(Integer::valueOf).toArray();
还有一件事,List.toArray() Arrays.asList()方法底层可能也有坑的。 参考一下 blog.csdn.net/h4241778/ar... 比如ArrayList.toArray() 传入String会报出类转换异常, 并且Arrays.asList()转换以后得list的size是不能更改的。
十四、 枚举类Enum
通常会用到可以选择的类型的时候,如果直接用String类型进行基于map映射后转到对应执行,有时候会有误操作增删导致字符串更改造成不匹配的风险,排查起来困难,并且把String定义成常量的实际意义不如定义成枚举更合理。
java
//举例 活动平台设置一个枚举 包括安卓和IOS
public enum AppEnum {
IOS("iOS"),
ANDROID("android");
@Getter
final private String platform;
AppEnum(String platform) {
this.platform = platform;
}
public static AppEnum of(String platform) {
return Arrays.stream(values())
.filter(item -> item.getPlatform().equalsIgnoreCase(platform))
.findFirst()
.orElse(null);
}
}
//具体应用
if (AppEnum.ANDROID.getPlatform().equals(type)) {
//do something
} else if (AppEnum.IOS.getPlatform().equals(type) {
//do something
}
但是注意枚举类在序列化时候可能会出现一些问题,Go语言就不支持枚举,转化之前可以先变成String,枚举类在业务类内部用一下还是比较方便的。
十五、 ()->{} Lambda表达式
可以将Lambda表达式理解为一个匿名函数; Lambda表达式允许将一个函数作为另外一个函数的参数; 我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码作为实参),也可以理解为函数式编程,将一个函数作为参数进行传递。Lambda表达式能够让程序员的编程更加高效。 lambda表达式和方法引用使用前提:函数式接口 1.@FunctionalInterface 语法格式严格要求当前接口有且只能有一个尚未完成的缺省属性为 public abstract 修饰方法。 2.函数式接口一般用于方法的增强,直接作为方法的参数,实现函数式编程。 只有函数式接口的变量或者是函数式接口,才能够赋值为Lambda表达式。这个接口中,可以有默认方法,或者是静态方法。
java
//下面这个例子 mapToConfig需要传入一个configPO
@Override
public ActivityConfig queryActivityConfig(Long areaId, Long configId) {
return Optional.of(xxxConfig.getActivityConfig().getAreaAcvitityMap().get(areaId))
.map(list -> findItem(list, configId))
.map(mapToConfig(areaId, ActivityTypeEnum.XXX))
.orElse(null);
}
@NotNull
private static Function<ActivityConfigPO, ActivityConfig> mapToConfig(Long areaId, ActivityTypeEnum tag) {
//传入之前list里面的ActivityConfigPO对象 转换构建成ActivityConfig输出
return configPO -> ActivityConfig.builder()
.id(configPO.getId())
.areaId(areaId)
.desc(configPO.getDesc())
.activityType(tag)
.activityId(configPO.getActivityId())
.build();
}
其他一些场景,比如实现手动定制排序的时候,可以不同new Comparator()接口了,我们可以直接用lambda表达式,代码量会降低很多。
java
import java.util.*;
class Solution {
public String sort(Integer[] nums) {
Arrays.sort(nums, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
//从大到小的顺序排序
return o2 - o1;
}
});
// Arrays.sort(nums,(a,b)->(b-a));
return Arrays.toString(nums);
}
}
public class Main {
public static void main(String[] args) {
Solution solution = new Solution();
Integer[] arr = {1, 2, 3, 4, 5};
System.out.println(solution.sort(arr));
}
}
其他内容参考博客 blog.csdn.net/qq_45944185...
十六、git发布相关
git发布相关,研发血泪经验总结 1.每次注意修改的是不是Feature分支 2.合并之前先git pull一下最新代码 3.合并master之前,feature分支先本地拉取master合并一下,预先更新包引用并解决冲突 4.按照紧急程度选择新功能放在一个分支还是两个分支,前后端发的时候商量好,先发后端 5.测试和预发公共环境合并commit以后先测一下,跑通了再push和发布,别污染公共环境。
十七、其他补充
forEach 增强for循环 blog.csdn.net/weixin_4599... out 对比break/continue 终止多轮循环 clone方法 深/浅拷贝 @Value("${xxx.xxxxx}") private String xxxxx; 读取prop配置文件里面的内容进行动态赋值 分页查询 子查询 深分页问题 ORM框架 MybatisPlus 或者 基于JPA的Script标签形式
String字符串长度超过javac编译器运行的类型,可以更改类型选择Eclipse编译器。
在Java中,String类型的长度取决于系统内存的限制,因此在理论上没有固定的长度限制。 在Java中,String类型是使用Unicode字符集来存储字符串的,每个字符使用16位表示,因此单个字符串对象的最大长度是65535个字符(即2的16次方减1)。然而,这只是一个理论上的限制,因为实际上在Java中创建String类型的对象时,受限于内存大小和可用的虚拟机堆空间大小。 因此,String类型的长度在Java中受系统内存的限制,而不是固定的字符数限制。 参考 blog.csdn.net/weixin_4257... www.jb51.net/article/273...
总结
在这里总结java研发过程中一些实用的工具/类/方法/特性,有时能提升效率并且节省工作量,使得代码更丝滑更优雅,用的时候回来查一下,结合业务实践真正理解它们的精髓和方便之处,更好地为实际工作服务。