总结java研发过程中一些实用的工具/类/方法/特性

@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研发过程中一些实用的工具/类/方法/特性,有时能提升效率并且节省工作量,使得代码更丝滑更优雅,用的时候回来查一下,结合业务实践真正理解它们的精髓和方便之处,更好地为实际工作服务。

相关推荐
怀旧6661 分钟前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
李老头探索3 分钟前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
芒果披萨8 分钟前
Filter和Listener
java·filter
qq_49244844613 分钟前
Java实现App自动化(Appium Demo)
java
阿华的代码王国21 分钟前
【SpringMVC】——Cookie和Session机制
java·后端·spring·cookie·session·会话
德育处主任Pro1 小时前
『Django』APIView基于类的用法
后端·python·django
找了一圈尾巴1 小时前
前后端交互通用排序策略
java·交互
哎呦没3 小时前
SpringBoot框架下的资产管理自动化
java·spring boot·后端
2401_857600953 小时前
SpringBoot框架的企业资产管理自动化
spring boot·后端·自动化
m0_571957585 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解