条件判断
@NoArgsConstructor(access = AccessLevel.PRIVATE) // Object对象判断 // 不推荐使用 hutool 的,因为 jdk 不能自动识别,会产生异味 import cn.hutool.core.util.ObjectUtil; ObjectUtil.isNotNull // 推荐使用 java.util 提供的工具类 Objects.nonNull // String对象判断 // 不推荐使用 hutool 的,因为 jdk 不能自动识别,会产生异味 import cn.hutool.core.util.StrUtil; StrUtil.isNotBlank // 推荐使用 apache 的 import org.apache.commons.lang3.StringUtils; StringUtils.isNotBlank // google guava import com.google.common.base.Strings; Strings.isNullOrEmpty(name); // Collection对象判断 // 不推荐使用 hutool 的,因为 jdk 不能自动识别,会产生异味 import cn.hutool.core.collection.CollectionUtil; CollectionUtil.isNotEmpty // 推荐使用 apache 的 import org.apache.commons.collections.CollectionUtils; CollectionUtils.isNotEmpty // equals判断 Objects.equals() StringUtils.equals()
# 对象拷贝
BeanConverter
这个是 idea 的一个插件,可以帮你生成一个 Convert类。
Mapstruct
官网:MapStruct – Java bean mappings, the easy way!
学习文档:MapStruct 1.5.5.Final Reference Guide
简介
-
不同类型之间可以相互转换,但是如果 String 转 Integer,你的值是 "aa",那就会报类型转换异常
-
不同名字可以通过 @Mapping(source = "tel", target = "telNumber") 做映射
-
可以设置默认值 @Mapping(source = "tel", target = "telNumber", defaultValue = "我的默认值"),
-
@Mapping(target = "creationDate", expression = "java(new java.util.Date())")
-
也可以给目标设置常量,@Mapping(target = "telNumber", constant = "我的常量"),不可有 source,
-
其他数据类型也可以正常转换,比如 List,
-
格式转换,@Mapping(source = "price", target = "price", numberFormat = "0.00"),目前只发现转换成 String 的时候可以成功,
-
日期格式转换也是,只有转成 String 才会成功 @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")。
-
把 A 的属性赋值给 B,如果 B 本来就有值,会覆盖掉
-
忽略 @Mapping(target = "modificationTime", ignore = true)
-
source 的空值 "" 和 null 也会赋值给 target,
-
在类上加上 @Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS),可以忽略 null,
-
list to string:@Mapping(target = "list", expression = "java(com.alibaba.fastjson.JSON.toJSONString(user.getList()))"),
-
List 的转换是建立在单个的基础之上的,即 @Mappings 写在单个上,List 只是去调用单个的映射方式。
@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) public interface ShopInfoConvert { /** * Is not empty boolean. * * @param value the value * @return the boolean */ @Condition static boolean isNotEmpty(String value) { return StringUtils.isNotBlank(value); } /** * To do. * * @param entity the entity * @param request the request */ @Mappings({ @Mapping(target = "createBy", expression = "java(UserDetailUtil.getUserId())"), @Mapping(target = "createDate", expression = "java(new java.util.Date())"), // @Mapping(target = "updateBy", expression = "java(UserDetailUtil.getUserId())"), @Mapping(target = "updateDate", expression = "java(new java.util.Date())"), }) void toDo(@MappingTarget MallShopInfo entity, SaveRequest request); }
多源映射
@Mapper public interface AddressMapper { @Mapping(target = "description", source = "person.description") @Mapping(target = "houseNumber", source = "address.houseNo") DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address); }
源参直接映射
可以把参数列表中的参数值直接做映射。
@Mapper public interface AddressMapper { @Mapping(target = "description", source = "person.description") @Mapping(target = "houseNumber", source = "hn") AddressDto personAddressDto(Person person, Integer hn); }
用 . 指代同名参数
生成的代码可以直接地映射每个属性从 CustomerDto.record 给 Customer不需. The same goes for Customer.account.
@Mapper public interface CustomerMapper { @Mapping( target = "name", source = "record.name" ) @Mapping( target = ".", source = "record" ) @Mapping( target = ".", source = "account" ) Customer customerDtoToCustomer(CustomerDto customerDto); }
子类映射
如果Fruit是抽象类或接口,则会出现编译错误。
@Mapper public interface FruitMapper { @SubclassMapping( source = AppleDto.class, target = Apple.class ) @SubclassMapping( source = BananaDto.class, target = Banana.class ) Fruit map( FruitDto source ); }
String 不为 null 且不为空时转换
@Condition default boolean isNotEmpty(String value) { return StringUtils.isNotBlank(value); }
不同服务模块都需要加上插件
假如你定义了一个父转换器在 A模块,然后依赖和插件都放在 A模块里面了;现在定义了一个子转换器在 B模块,那么也需要假如此插件,否则不会生成实现类。
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <source>1.8</source> <target>1.8</target> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${mapstruct.version}</version> </path> </annotationProcessorPaths> <compilerArgs> <compilerArg>-Amapstruct.defaultComponentModel=spring</compilerArg> <compilerArg>-Amapstruct.suppressGeneratorTimestamp=true</compilerArg> <compilerArg>-Amapstruct.suppressGeneratorVersionInfoComment=true</compilerArg> </compilerArgs> </configuration> </plugin> </plugins> </build>
映射 List 时的注意点
-
List 的转换是建立在单个的基础上的。
-
List 的转换建立在单个的基础上,若这个单个需要自定义,那么必须使用 A toA(B b); 这种;不可使用 toA(@MappingTarget A a, B b); 这种形式,否则 mapstruct 会自动帮你建立一个默认的 A toA(B b);实现。
@Mappings({ @Mapping(target = "fansAddressId", source = "id"), @Mapping(target = "provinceId", source = "provinceNo"), @Mapping(target = "cityId", source = "cityNo"), @Mapping(target = "districtId", source = "areaNo"), @Mapping(target = "district", source = "area"), @Mapping(target = "mobile", source = "phone"), }) UserAddressVO toVo(RnUserAddressVO rnAddress); void toVo(@MappingTarget List<UserAddressVO> voList, List<RnUserAddressVO> rnAddressList);
实践1
依赖
<properties> <mapstruct.version>1.5.2.Final</mapstruct.version> <lombok.version>1.16.22</lombok.version> </properties> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${mapstruct.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${mapstruct.version}</version> </path> </annotationProcessorPaths> <compilerArgs> <compilerArg>-Amapstruct.defaultComponentModel=spring</compilerArg> <compilerArg>-Amapstruct.suppressGeneratorTimestamp=true</compilerArg> <compilerArg>-Amapstruct.suppressGeneratorVersionInfoComment=true</compilerArg> </compilerArgs> </configuration> </plugin> </plugins> </build>
对象类
getter、setter。
public class User { private Integer id ; private String userName; private Date birthday; String tel; } public class UserVo { private String id; private String userName; /** * 类型不同 */ private Date birthday; private String telNumber; }
转换mapper
import com.chw.pojo.User; import com.chw.pojo.UserVo; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; // 将转换器注入到ioc容器 @Mapper // @Mapper(componentModel = "spring") public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); @Mappings({ @Mapping(source = "tel", target = "telNumber"), @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd") }) UserVo convertToVo(User user); @Mappings({ @Mapping(source = "tel", target = "telNumber"), @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm") }) void mapper1(User user, @MappingTarget UserVo userVo); }
测试
@Resource private UserMapper userMapper; @Test public void userToUserVO() { User user = new User(1, "aa", new DateTime(), "123456"); UserVo userVo = userMapper.convertToVo(user); // 或者 UserMapper.INSTANCE.convertToVo(user); System.out.println(userVo); }
实践2 - qualifier
import com.chw.pojo.User; import com.chw.pojo.UserVo; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.apache.commons.lang3.StringUtils; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; import org.mapstruct.NullValueCheckStrategy; import org.mapstruct.NullValuePropertyMappingStrategy; import org.mapstruct.Qualifier; @Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) public interface UserMapper { @Mappings({ @Mapping(source = "tel", target = "telNumber"), @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm") }) UserVo convertToVo(User user); @Mappings({ @Mapping(source = "tel", target = "telNumber", defaultValue = "我的默认值"), @Mapping(source = "price", target = "price", numberFormat = "0.00"), @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd"), @Mapping(target = "userName", qualifiedBy = EmptyStringToNull.class) }) void mapper1(User user, @MappingTarget UserVo userVo); @Qualifier @java.lang.annotation.Target(ElementType.METHOD) @Retention(RetentionPolicy.CLASS) @interface EmptyStringToNull { } @EmptyStringToNull default String emptyStringToNull(String s) { return StringUtils.isNotBlank(s) ? s : null; } }
实践3 - 父类继承
// 父类。父类定义了 isNotEmpty,那么子类都能用上 public interface BaseConvert<D, E> { E toEntity(D dto); D toDto(E entity); List<E> toEntity(List<D> dtoList); List<D> toDto(List<E> entityList); @Condition static boolean isNotEmpty(String value) { return StringUtils.isNotBlank(value); } } // 子类 @Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) public interface VipInfoConvert extends BaseConvert<VipInfo, WscVipInfo> { void toWscVipInfo(VipInfo vipInfo, @MappingTarget WscVipInfo wscVipInfo); void toVipInfo(WscVipInfo wscVipInfo, @MappingTarget VipInfo vipInfo); @Mappings({@Mapping(target = "userId", ignore = true), @Mapping(source = "userId", target = "custNo"), @Mapping(source = "mobilePhone", target = "mobile"), @Mapping(source = "shopStoreName", target = "nickName"), @Mapping(source = "channelCode", target = "orgType"), @Mapping(source = "otOpenid", target = "thirdOpenId"), @Mapping(source = "uniqueNo", target = "thirdCustNo")}) void toVipInfo(VipLoginData vipLoginData, @MappingTarget VipInfo vipInfo); @Mappings({@Mapping(target = "userId", ignore = true), @Mapping(source = "wscVipInfo.familyNo", target = "familyNo"), @Mapping(source = "vipLoginData.userId", target = "custNo"), @Mapping(source = "vipLoginData.mobilePhone", target = "mobile"), @Mapping(source = "vipLoginData.shopStoreName", target = "nickName"), @Mapping(source = "vipLoginData.shopNo", target = "shopNo"), @Mapping(source = "vipLoginData.merchantCode", target = "merchantCode"), @Mapping(source = "vipLoginData.shopStoreCode", target = "shopStoreCode"), @Mapping(source = "vipLoginData.channelCode", target = "orgType"), @Mapping(source = "vipLoginData.vipIdCard", target = "vipIdcard"), @Mapping(source = "vipLoginData.sex", target = "sex"), @Mapping(source = "vipLoginData.trueName", target = "trueName"), @Mapping(source = "vipLoginData.otOpenid", target = "thirdOpenId"), @Mapping(source = "vipLoginData.uniqueNo", target = "thirdCustNo")}) void toVipInfo(@MappingTarget VipInfo vipInfo, WscVipInfo wscVipInfo, VipLoginData vipLoginData); }
数值计算
代码review
https://www.cnblogs.com/candlia/p/11920108.html
其他工具
非自主抛异常
@SneakyThrows
普通Exception类,也就是我们常说的受检异常或者Checked Exception会强制要求抛出它的方法声明throws,调用者必须显示的去处理这个异常。设计的目的是为了提醒开发者处理一些场景中必然可能存在的异常情况。比如网络异常造成IOException。
但是现实大部分情况下的异常,我们都是一路往外抛了事。所以渐渐的java程序员处理Exception的常见手段就是外面包一层RuntimeException,接着往上丢
而Lombok的@SneakyThrows就是为了消除这样的模板代码。
使用注解后不需要担心Exception的处理
非空判断取值
sysTagInfo.setShopNo(Optional.ofNullable(shopNo) .filter(item -> StringUtils.isNotBlank(item.trim())) .map(String::trim).orElse(getShopNo()));
入参出参序列化工具
当需要对入参出参做动作时,一定要想到序列化工具,因为你自己去判断对象内的值是否有 null 是一件很麻烦的事情,那么你就要想到,我对象传给前端变成 json字符串的时候,是怎么样一个过程,类似于 jackson 这种工具能不能帮我们自动完成 null 的去除呢。
jackson
一般入参出参都是用的 json字符串来转换的,jackson 是一个很强大的自动转换工具。
出参序列化忽略null
当你传给前端的对象中有空值时,前端拿到然后显示的就会是 null,这是很不美观的。那么我们就可以让 Jackson 在将对象序列化为 json字符串的时候忽略 null。
// bootstrap.properties 中加上 spring.jackson.default-property-inclusion=non_null // 或者对指定属性加上注解 @JsonInclude(JsonInclude.Include.NON_NULL)
hutool工具类
中文文档:Hutool参考文档
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.5</version> </dependency>
克隆
JDK 中的 Cloneable接口只是一个空接口,并没有定义成员,它存在的意义仅仅是指明一个类的实例化对象支持位复制(就是对象克隆),如果不实现这个类,调用对象的 clone()方法就会抛出 CloneNotSupportedException异常。并且因为 clone()方法在 Object对象中,所以返回值也是 Object对象,因此克隆后我们需要自己强转下类型。
实现接口重写克隆方法
优点就是 Java 可以多实现,所以不会有限制;缺点就是需要自己重写,还要自己处理异常。
private static class Cat implements Cloneable<Cat>{ @Override public Cat clone() { try { return (Cat) super.clone(); } catch (CloneNotSupportedException e) { throw new CloneRuntimeException(e); } } }
继承父类直接可以克隆
优点就是不需要自己重写了,缺点就是 Java 不能多继承。
private static class Dog extends CloneSupport<Dog>{ // ... }
深克隆
以下三种方法都可进行深克隆。
ObjectUtil.clone(obj); ObjectUtil.cloneByStream(obj); ObjectUtil.cloneIfPossible(obj);
URL编码解码
URLEncoder.encode(encrypt, StandardCharsets.UTF_8.name());
SESSION学习
// 会获取这个 request 中的 session,如果为空则会为此 request 创建新 session // 所以推荐使用 request.getSession(false),然后记得做非空判断,他不会自动创建 request.getSession()
Google guava 工具类
简介
Guava 是对 JavaAPI 的补充,
实践1
依赖
<guava.version>31.0.1-jre</guava.version> <!-- https://mvnrepository.com/artifact/com.google.guava/guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>${guava.version}</version> </dependency>
使用
Strings
import com.google.common.base.Strings; // 如果值为 null,则转成 "" String entity = Strings.nullToEmpty(name); // 判断是否为 null或空字符串 Strings.isNullOrEmpty(name);
新集合工具类
不可变集合 ImmutableList
-
当对象被不可信的库调用时,不可变形式是安全的;
-
不可变对象被多个线程调用时,不存在竞态条件问题
-
不可变集合不需要考虑变化,因此可以节省时间和空间。所有不可变的集合都比它们的可变形式有更好的内存利用率(分析和测试细节);
-
不可变对象因为有固定不变,可以作为常量来安全使用。
ImmutableList<String> iList = ImmutableList.of("a", "b", "c");
Lists
List<Integer> list1 = Lists.newArrayList(1, 2, 3, 4, 5); // 根据长度分区 List<List<Integer>> partition = Lists.partition(list1, 2);
key可重复Map
// 这个 Multimap,重复的 Multimap<String,Integer> mapM = ArrayListMultimap.create(); mapM.put("test",1); mapM.put("test",2);
交集、并集、差集
Set 的
Set<Integer> set1 = Sets.newHashSet(1, 3, 5, 7, 9); Set<Integer> set2 = Sets.newHashSet(5, 7, 9, 6, 8, 10); // 交集 SetView<Integer> setIntersection = Sets.intersection(set1, set2); setIntersection.forEach(System.out::println); // 并集 SetView<Integer> setUnion = Sets.union(set1, set2); setUnion.forEach(System.out::println); // 差集 SetView<Integer> setDifference = Sets.difference(set1, set2); setDifference.forEach(System.out::println); // 双方的差集的和 SetView<Integer> symmetricDifference = Sets.symmetricDifference(set1, set2); // 实践 HashSet<Long> oldDictDataNoSet = oldDataList.stream().map(ShopChannelData::getDictDataNo).distinct().collect(Collectors.toCollection(Sets::newHashSet)); HashSet<Long> newDictDataNoSet = Sets.newHashSet(newDictDataNoList); SetView<Long> deleteDictDataNoSet = Sets.difference(oldDictDataNoSet, newDictDataNoSet); SetView<Long> insertDictDataNoSet = Sets.difference(newDictDataNoSet, oldDictDataNoSet);
Map 的
MapDifference 这个对象内就有了交集、差集
Map<Integer, Integer> map1 = Maps.newHashMap(); map1.put(1,1); map1.put(2,2); Map<Integer, Integer> map2 = Maps.newHashMap(); map2.put(2,2); map2.put(3,3); MapDifference<Integer, Integer> mapDifference = Maps.difference(map1, map2);
List 通过 Set 来实现
List<Integer> list1 = Lists.newArrayList(1, 2, 3, 4, 5); List<Integer> list2 = Lists.newArrayList(3, 4, 5, 6, 7, 8); SetView<Integer> intersection = null; // 如果 list1 为空会报错,所以需要做判断 if (CollectionUtils.isNotEmpty(list1) && CollectionUtils.isNotEmpty(list2)) { intersection = Sets.intersection(Sets.newHashSet(list1), Sets.newHashSet(list2)); } List<Integer> intersectionList = Lists.newArrayList(intersection);
缓存 LoadingCache
类型转换
我们常用的类型转换就是得到 String,然后进行解析转换成对象,但是需要加上 try/catch 来预防转换失败。
Hutool 可以实现自定义类型转换,具体类型
Convert类
此类的大部分方法为 toXXX,参数1 为 Object,参数2 为 defaultValue,转换失败时返回这个默认值。
这个类的方法特别多,具体使用到或想去使用的时候可以搜一下。
// Object对象转换成 String Convert.toStr // Object对象转换成 Integer数组 Convert.toIntArray // Object对象转换成 Date对象 Convert.toDate // 半角转全角 Convert.toSBC // 全角转半角 Convert.toDBC
泛型类型转换
通过 convert(TypeReference<T> reference, Object value)方法,自行 new 一个TypeReference对象,可以对嵌套泛型进行类型转换。
// 例如我们想转换一个对象为 List<String>类型 Object[] obj = {"你", "好", "", 1}; List<String> list = Convert.convert(new TypeReference<List<String>>() {}, obj);
JSON处理
// JSON字符串转换成 List // import cn.hutool.json.JSONUtil; JSONUtil.toList(redisResult, MobileCategoryVo.class);
日期时间
各工具类
-
DateUtil 针对日期时间操作提供一系列静态方法
-
DateTime 提供类似于 Joda-Time 中日期时间对象的封装,继承自 Date类,并提供更加丰富的对象方法。
-
DatePattern 提供常用的日期格式化模式,包括 String类型和 FastDateFormat两种类型。
-
DateUnit,时间枚举工具类,可以枚举很多时间。
-
FastDateFormat 提供线程安全的针对Date对象的格式化和日期字符串解析支持。此对象在实际使用中并不需要感知,相关操作已经封装在DateUtil和DateTime的相关方法中。
-
DateBetween 计算两个时间间隔的类,除了通过构造新对象使用外,相关操作也已封装在DateUtil和DateTime的相关方法中。
-
TimeInterval 一个简单的计时器类,常用于计算某段代码的执行时间,提供包括毫秒、秒、分、时、天、周等各种单位的花费时长计算,对象的静态构造已封装在DateUtil中。 Splitter 和 Joinner