Updated at 2020-12-02 10:12

防止下标越界

import com.google.common.collect.Iterables;

User firstOne = Iterables.getFirst(users, null);
User lastOne = Iterables.getLast(users, null);

null缺省

import com.google.common.base.MoreObjects;

Integer age = MoreObjects.firstNonNull(user, DEFAULT_USER).getAge();

在Guava的早期版本中,firstNonNull是声明在com.google.common.base.Objects中的,这容易与java.util.Objects产生冲突,所以不推荐使用过于早版本的Guava

简化if-null-then-null

import org.apache.commons.lang3.math.NumberUtils;

Long a = NumberUtils.createLong(user.getValue());

字符串拼接

import com.google.common.base.Joiner;

StringBuilder msg = Joiner.on("、").appendTo(new StringBuilder("这些用户已被禁用:"), userIds).append("。");

获取唯一元素

import com.google.common.collect.Iterables;

Foo foo = Iterables.getOnlyElement(foos);

foos元素个数不足或是个数过多会抛出NoSuchElementException或是IllegalArgumentException

java.time.*

Java8 time包指南

谨慎使用继承(extends)

继承代表父子耦合,使用继承前想一想,“这里需要用到多态吗?不需要的话用“组合”行不行?”

继承会引发的BUG可以参考 addaddAll 的那个例子

预提供调用方对返回值的常用操作

// before
public UserDto getUser(Long userId) {
    return userDao.get(userId);
}
// after
public Optional<UserDto> getUser(Long userId) {
    return Optional.ofNullable(userDao.get(userId));
}

public UserDto getUserOrElseThrow(Long userId) {
    return getUser().orElseThrow(() -> new UserAbsentException(userId));
}

public UserDto getUserOrElseNew(Long userId) {
    return getUser().orElseThrow(new UserDto());
}

打印日志的场景

  1. “值类型”或者元素是“值类型”的容器,构建/初始化完毕后,打印日志是有必要的。

    比起Javabean,“值类型”对象toString的返回值往往很短,打印它们开销很小,同时他们代表的信息对于调试又很关键(例如List<Long>,它们往往代表着一些实体的外键)

    Map<Long, Integer> extractIds = Maps.newHashMap();
    javabeans.forEach(one -> extractIds.add(one.getId()));
    log.info("extractIds={}", extractIds.toString())
  2. 调用其他模块的API之后

    List<String> itemAttrValues = goodsApi.getAttrValues(itemIds, true, 500);
    log.info("goodsApi.getAttrValues({}, true, 500)={}", itemIds, itemAttrValues);
  3. return null之前

    if (items.size() == 0) {
        log.warn("找不到任何商品,itemName={}", itemName); // warn还是info取决于业务
        return null;
    }
  4. 请求无法继续执行时

    try {
        stockApi.lockStock(id, delta);
    } catch (OutOfStockException e) {
        log.error("库存不足,可能是操作期间C端下单所致");
        throw new SubmitOrderException();
    }

考虑使用一个ApiImpl来实现多个Api

当你的所有ApiImpl里实现的方法没有任何业务逻辑,只有调用Service和数据组装时,可以考虑使用一个ApiImpl来实现多个Api

这样做有助于减少IDE阅读代码时需要打开的窗口数,提高阅读代码的效率

由于改动的仅仅是实现,所以“接口隔离原则”既然是遵守的。

本应该查询到1个但是查到多个时,考虑抛出异常

/**
 * 根据手机号获取用户,不存在时返回null
 */
public User getUserOrElseNull(String telephone) 
        throws MoreThanOneResultsException {
    List<User> users = userDao.select(new Wrapper("telephone", telephone));

    if (users.size() == 0) {
        return null;
    }

    // 预想之外的情况
    if (users.size() > 1) { 
        // 抛出异常交给调用者处理,如果业务上不可能发生,算作BUG;如果是正常情况,那么调用方可以捕获
        throw new MoreThanOneResultsException(users);
    }

    return users.get(0);
}
/**
 * 当本应返回1个满足条件结果的方法内部获取到不止1个结果时,应该抛出这个异常。
 */
public class MoreThanOneResultsException extends RuntimeException {

    private static final long serialVersionUID = 3999892488916264668L;

    private Object firstOne;

    private Collection<?> results;

    public MoreThanOneResultsException(Collection<?> results) {
        super();
        if (results == null || results.size() == 0) {
            throw new IllegalArgumentException("Results cannot be empty.");
        }
        this.results = results;
        this.firstOne = Iterables.getFirst(results, null);
    }

    @SuppressWarnings("unchecked")
    public <T> T orElseFirst() {
        return (T) firstOne;
    }

    @SuppressWarnings("unchecked")
    public <T> Collection<T> getResults() {
        return (Collection<T>) results;
    }

    @Override
    public String getMessage() {
        return "Method obtained more than one result, perhaps caused by data problems. " + results;
    }
}

Java      总结