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类的设计

  1. Extract all methods from original class into a util class and put as static methods.
  2. Have each of above method accept a target object on which the static methods 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)

此外它们也常被用在Predicatetest方法中用作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
    }
}
posted @ 2021-01-02 14:47  Sam_Jiang  阅读(99)  评论(0)    收藏  举报