Java重构备忘录
以下是平时积累的一些Java重构内容(会持续更新):
简化bool型条件赋值
原来
boolean flag = false;
if (condition1 && condition2)
flag = true;
else
flag = false;
现在
flag = condition1 && condition2;
Optional如果内容为空就抛异常
原来
if (!opt.isPresent()) {
throw new xxxException(...);
}
现在
var xxx = opt.orElseThrow(() -> throw new IllegalStateException("xxx"));
Optional不要用于控制流
Optionals should not be created for flow control within a method. This is called out as an anti-pattern by the developers who built Optional.
避免使用Optional.ofNullable(System.getProperty("property")).orElse(defaultValue)
应该使用apache common的ObjectUtils.defaultIfNull(T object, T defaultValue)
或者Guava的MoreObjects.firstNonNull(T first, T second)
或者JDK 9的Objects.requireNonNullElse(T obj, T defaultObj) Returns the first argument if it is non-null and otherwise returns the non-null second argument.
对于Map<String, Collecton>,不要使用Map#containsKey
推荐
Collection<Item> list = theMap.get(key);
if (list == null) {
list = new ArrayList<Item>();
theMap.put(key, list );
}
list.add(item);
key存在情况只有一次key access,不存在情况有两次key access。
不要使用Stream作为方法入参,而要使用传统的Collection
因为当在Stream上调用过一次terminal操作后,它就关闭了。如果同一个方法后面还需要操作这个Stream,就会抛出IllegalStateException: stream has already been operated upon or closed。
所以最好传入Collection。另一个解决方法是使用Suppler见下例:
Supplier<Stream<String>> streamSupplier = () -> Stream.of("a", "b", "c");
System.out.println(streamSupplier.get().findAny().get());
System.out.println(streamSupplier.get().findFirst().get());
使用Arrays#copyOf复制数组,浅复制
int[] a = {1, 2, 3, 4, 5};
int newLength = 10;
int[] b = Arrays.copyOf(a, newLength);
for (int i = 0; i < b.length; i++) {
System.out.printf("%d ", b[i]); // 1 2 3 4 5 0 0 0 0 0
}
将一个数组复制到另一个数组
不要手动复制:
char[] targets = ...;
char[] results = ...;
for (int i = 0; i < targets.length; i++) {
results[i] = targets[i];
}
应该用API:System.arraycopy
/*
The components at positions srcPos through srcPos+length-1 in the source array are
copied into positions destPos through destPos+length-1, respectively, of the
destination array.
*/
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
System.arraycopy(targets, 0, results, 0, targets.length);
类暴露外部属性,不要暴露内部属性,除非必要
我关心的是某人的体检是否合格,而合格标准有若干项指标综合评定,那么就只暴露isHealth()方法
public class PhysicalExamination {
private Person p;
...
private double eyeSight() {...}
private double bloodIndicator() {...}
...
public boolean isHealthy() {
return (eyeSight() > 1.0 && bloodIndicator() < 40) ? true : false;
}
}
防止同时往db写
serialize all db writes through a single thread. This forces all updates/inserts to the db to go through a single-thread executor, which prevents concurrent db access and the resulting lock exception.
一种Utils类的设计
- Extract all methods from original class into a util class and put as
staticmethods. - Have each of above method accept a target object on which the
staticmethods will manipulate.
命名规范:Utils类的包名和类名
重要的是在选择后要保持始终一致。我喜欢:
- package: com.coldair.util
- class name: AbcUtils.java
顺便说一下,JDK中几个只有static方法的工具类命名也是如此:Collections, Collectors, Executors
java.util.Date 转换成 java.time.LocalDate / java.time.LocalDateTime
自1.8Date提供toInstant()方法,但是Instant要先绑定到某个时区最后转换成LocalDate:
Date dateToConvert = ...
dateToConvert.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
或者
Date dateToConvert = ...
Instant.ofEpochMilli(dateToConvert.getTime()).atZone(ZoneId.systemDefault()).toLocalDate();
在Controller层可以用FormDataBodyPart,但若传给Application层,应该先转化成自定义对象。这个对象至少要有以下属性:
String name;
InputStream inputStream;
让高层函数读起来像一系列注释
先看一段代码:
@PostMapping
public User addUser(UserInputDTO userInputDTO){
User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
return userService.addUser(user);
}
最好需要考虑操作的语义。比如操作不要暴露实现,都是在讲如何在同一个方法中,做一组相同层次的语义操作,而不是暴露具体的实现。
重构成:
@PostMapping
public User addUser(UserInputDTO userInputDTO){
User user = convertFor(userInputDTO); // 没有暴露实现
return userService.addUser(user); // 没有暴露实现
}
private User convertFor(UserInputDTO userInputDTO){
User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
return user;
}
使用泛型进行抽象接口定义
上面例子中的convertFor可以定义到一个泛型接口中:
public interface DTOConvert<S,T> {
T convert(S s);
}
实现类:
public class UserInputDTOConvert implements DTOConvert {
@Override
public User convert(UserInputDTO userInputDTO) {
User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
return user;
}
}
这只是一个概念上的例子,实际上更好的做法是用mapper框架。
不要直接用对象== null或!= null判断null
用java.util.Objects的两个工具方法:
public static boolean isNull(Object obj)
public static boolean nonNull(Object obj)
此外它们也常被用在Predicate的test方法中用作filter: filter(Objects::isNull)
Bad Smell
方法调用链很长,而且有一个入参(集合类居多)从最外层那个方法就被传入,一直深入传到了内层的某个被调方法才真正被使用。
糟糕在哪里:跟踪过程漫长,传入的时机不是最优的。本质是违反德米特里原则。
Flyway
只用于schema setup,不应该用于数据行的增删改。
使用卫语句的场合
如果if block最后是一个return/continue/break,不要接着用else,而是直接接着加原else的语句。
while (currNode != null) {
if (val < currNode.val) {
if (currNode.leftNode == null) {
currNode.leftNode = new TreeNode(val);
return;
}
currNode = currNode.leftNode; // 无需else
} else {
if (currNode.rightNode == null) {
currNode.rightNode = new TreeNode(val);
return;
}
currNode = currNode.rightNode; // 无需else
}
}

浙公网安备 33010602011771号