减少查询数据库的频率,多在内存中计算

在一次请求中,网络IO占据了大多数的时间,代码在内存中的时间是很快的。有时候需要多次查询数据库,这时候可以将一个大的集合查询到,具体使用的时候在进行筛选。尤其避免了在for循环中多次调用数据库查询的情况。举例
错误示范

1
2
3
4
5
List<Pojo> collection = new ArrayList();
for (Pojo pojo : collection) {
Object o = repository.findById(pojo.getId());
xxx
}

正确示范

1
2
3
4
5
6
7
List<Pojo> collection = new ArrayList();
// 并不一定是全量查询,尽量缩小范围
List allData = repository.findAll();
for (Pojo pojo : collection) {
Optional data = allData.stream().filter(e -> Objects.equals(e.getId(), pojo.getId())).findFirst();
xxx
}

数据需要精确计算的场景下(如金额,率值,数据分析等)使用java.math.BigDecimal

推荐入参都使用Sring类型的,可以准确的获取预期值,尤其是浮点数,可能无法准确表示一个值,这与二进制有关,而我们使用的是十进制。例如0.1,当构造函数使用double时,传入的值不会正好等于0.1
出参也同样推荐BigDecimal,使用toPlainString()准确的表示。使用toString()可能会出现科学计数法的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
/** 默认除法精度 */
private static final int DEFAULT_DIV_SCALE = 8;

public static BigDecimal div(String v1, String v2) {
return div(v1, v2, DEFAULT_DIV_SCALE);
}

public static BigDecimal div(String v1, String v2, int scale) {
if (NumberUtil.isNumber(v1) && NumberUtil.isNumber(v2) && new BigDecimal(v2).compareTo(BigDecimal.ZERO) > 0) {
return new BigDecimal(v1).divide(new BigDecimal(v2), scale, RoundingMode.HALF_UP);
}
return BigDecimal.ZERO;
}

使用枚举判断时,无需使用equals比较,直接使用==

直接使用==也避免了null的判断,枚举的equals实现也是直接==判断的,所以equals等同于==

1
2
3
4
5
6
/**
* Returns true if the specified object is equal to this enum constant.
*/
public final boolean equals(Object other) {
return this==other;
}

善用工具类

判断集合是否有元素
通常为list != null && list.size() > 0。可替换为!CollectionUtils.isEmpty(list)更加清晰
判断两个对象的值是否相等
需要判断不为空并且使用equals区比较。可替换为Objects.equals(a, b)
判断两个集合是否相等
许多工具类都提供了API,但并不好用,自己DIY的,也挺好使

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 判断集合是否相等
* 1.个数必须相等
* 2.元素必须相等
* 3.元素顺序可以不相等
*
* @param list1 集合1
* @param list2 集合2
* @return true表示元素相等
*/
public static <T>boolean isEqualList(List<T> list1, List<T> list2) {
if (list1 == null || list2 == null) {
return false;
}

if (list1.size() != list2.size()) {
return false;
}

Set<T> set1 = new HashSet<>(list1);
Set<T> set2 = new HashSet<>(list2);

return set1.containsAll(set2) && set2.containsAll(set1);
}

根据对象属性去重
stream的distinct无法对对象去重(除非每个对象的属性全部相同)。有些时候需要针对于对象的部分属性进行去重,需要使用自定义方法去重

1
2
3
4
private static  <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}

putIfAbsent()方法是如果key不存在则put如map中,并返回null。若key存在,则直接返回key所对应的value值。
seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
最终表达式如果没有该值会返回true得以保留,有该值会返回false跳过不收集
举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Example {
public static void main(String[] args) {
List<A> ls = new ArrayList<>();
ls.add(new A("tom","10", "a"));
ls.add(new A("jack","20", "b"));
ls.add(new A("tom","10", "c"));
List<A> collect = ls.stream().filter(distinctByKey(e -> e.getName() + e.getAge())).collect(Collectors.toList());
System.out.println(collect);
}

private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}

}
@Data
class A {
String name;
String age;
String address;
public A(String name, String age, String address) {
this.age = age;
this.name = name;
this.address = address;
}
}

输出结果:[A(name=tom, age=10, address=a), A(name=jack, age=20, address=b)]

小技巧

-XX:+PrintCommandLineFlags可以输出使用的命令(比如最大堆内存之类的)