1.需要 Map 的主键和取值时,应该迭代 entrySet()
当循环中只需要 Map 的主键时,迭代 keySet() 是正确的。但是,当需要主键和取值时,迭代 entrySet() 才是更高效的做法,比先迭代 keySet() 后再去 get 取值性能更佳。
反例
for (String key : map.keySet()) {
if (map.get(key).equals(arField) && !key.equals(infoId)) {
}
}
正例
for (Map.Entry<String,String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (value.equals(arField) && !key.equals(infoId)) {
}
}
2.集合初始化尽量指定大小
java 的集合类用起来十分方便,但是看源码可知,集合也是有大小限制的。每次扩容的时间复杂度很有可能是 O(n) ,所以尽量指定可预知的集合大小,能减少集合的扩容次数。
比如ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等,以StringBuilder为例:
StringBuilder() // 默认分配16个字符的空间
StringBuilder(int size) // 默认分配size个字符的空间
StringBuilder(String str) // 默认分配16个字符+str.length()个字符空间
可以通过类(这里指的不仅仅是上面的StringBuilder)的构造函数来设定它的初始化容量,这样可以明显地提升性能。比如StringBuilder吧,length表示当前的StringBuilder能保持的字符数量。因为当StringBuilder达到最大容量的时候,它会将自身容量增加到当前的2倍再加2,无论何时只要StringBuilder达到它的最大容量,它就不得不创建一个新的字符数组然后将旧的字符数组内容拷贝到新字符数组中----这是十分耗费性能的一个操作。试想,如果能预估到字符数组中大概要存放5000个字符而不指定长度,最接近5000的2次幂是4096,每次扩容加的2不管,那么:
- 在4096 的基础上,再申请8194个大小的字符数组,加起来相当于一次申请了12290个大小的字符数组,如果一开始能指定5000个大小的字符数组,就节省了一倍以上的空间
- 把原来的4096个字符拷贝到新的的字符数组中去
这样,既浪费内存空间又降低代码运行效率。所以,给底层以数组实现的集合、工具类设置一个合理的初始化容量是错不了的,这会带来立竿见影的效果。但是,注意,像HashMap这种是以数组+链表实现的集合,别把初始大小和你估计的大小设置得一样,因为一个table上只连接一个对象的可能性几乎为0。初始大小建议设置为2的N次幂,如果能估计到有2000个元素,设置成new HashMap(128)、new HashMap(256)都可以。
3.频繁调用 Collection.contains 方法请使用 Set
在 java 集合类库中,List 的 contains 方法普遍时间复杂度是 O(n) ,如果在代码中需要频繁调用 contains 方法查找数据,可以先将 list 转换成 HashSet 实现,将 O(n) 的时间复杂度降为 O(1) 。
反例
List<String> systemFieldList = Arrays.asList(workFlowFieldArrs);
for (Field field : fields) {
if (!systemFieldList.contains(field.getName())) {
continue;
}
}
反例
List<String> list = areaArFieldMap.get(areaKey);
if (list.contains(arField))
throw new ArBillException(msg);
} else {
list.add(arField);
}
4.长整型常量后添加大写 L
在使用长整型常量值时,后面需要添加 L ,必须是大写的 L ,不能是小写的 l ,小写 l 容易跟数字 1 混淆而造成误解。
5.不要使用魔法值
当你编写一段代码时,使用魔法值可能看起来很明确,但在调试时它们却不显得那么明确了。这就是为什么需要把魔法值定义为可读取常量的原因。但是,-1、0 和 1 不被视为魔法值。
反例
Predicate p5 = builder.equal(root.get("isUsed"), "Y");
反例
list = arSysDataSourceRepository.findByItemType("0");
6.删除多余代码
- 删除未使用的私有方法和字段
- 删除未使用的局部变量
- 删除未使用的方法参数
7.使用 String.valueOf(value) 代替 ""+value
当要把其它对象或类型转化为字符串时,使用 String.valueOf(value) 比 ""+value 的效率更高。
String s = "" + i;
String s1 = String.valueOf(i);
8.过时代码添加 @Deprecated 注解
当一段代码过时,但为了兼容又无法直接删除,不希望以后有人再使用它时,可以添加 @Deprecated 注解进行标记。在文档注释中添加 @deprecated 来进行解释,并提供可替代方案
9.禁止使用构造方法 BigDecimal(double)
- BigDecimal(double) 存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。
BigDecimal doubleVal = new BigDecimal(0.7);//0.6999999999999999555910790149937383830547332763671875
BigDecimal stringVal = new BigDecimal("0.7");//0.7
- 不要用equals方法来比较BigDecimal对象
- BigDecimal.ZERO;
- BigDecial是immutable的,就像String一样,它的所有操作都会生成一个新的对象,所以
amount.add( thisAmount );
是错误的;而应该是:
amount = amount.add( thisAmount );
10.返回空数组和空集合而不是 null
返回 null ,需要调用方强制检测 null ,否则就会抛出空指针异常。返回空数组或空集合,有效地避免了调用方因为未检测 null 而抛出空指针异常,还可以删除调用方检测 null 的语句使代码更简洁。
11.枚举的属性字段必须是私有不可变
枚举通常被当做常量使用,如果枚举中存在公共属性字段或设置字段方法,那么这些枚举常量的属性很容易被修改。理想情况下,枚举中的属性字段是私有的,并在私有构造函数中赋值,没有对应的 Setter 方法,最好加上 final 修饰符。
反例
/**
* 结算方式
*/
@AllArgsConstructor
@NoArgsConstructor
@Getter
public enum SETTLEMENTTYPE {
CASH("CASH", "现金"),
BUSINESS("BUSINESS", "公务卡"),
PAY("PAY", "转账"),
CHECK("CHECK", "支票");
private String code;
private String describe;
}
反例
@AllArgsConstructor
@NoArgsConstructor
@Getter
public enum TimeInfoIdEnums {
startDate("开始日期", "START_DATE"),
departDate("开始日期", "DEPART_DATE"),
endDate("开始日期", "END_DATE"),
arrivalDate("开始日期", "ARRIVAL_DATE");
private String name;
private String infoId;
}
12.尽量减少对变量的重复计算
明确一个概念,对方法的调用,即使方法中只有一句语句,也是有消耗的,包括创建栈帧、调用方法时保护现场、调用方法完毕时恢复现场等。所以例如下面的操作:
反例
for (int i = 0; i < arSysSetupArray.size(); i++) {
}
正例
for (int i = 0, len = expenseList.size(); i < len; i++) {
}
反例
if (ArStringUtil.isNotEmpty(bgItemMap.get("bgItemCur"))) {
balance.setBgItemCur(new BigDecimal(bgItemMap.get("bgItemCur").toString()));
}
正例
Object bgItemCur = bgItemMap.get("bgItemCur");
if (ArStringUtil.isNotEmpty(bgItemCur) {
balance.setBgItemCur(new BigDecimal(bgItemCur.toString()));
}
13.循环内不要不断创建对象引用
例如:
for (int i = 1; i <= count; i++)
{
Object obj = new Object();
}
这种做法会导致内存中有count份Object对象引用存在,count很大的话,就耗费内存了,建议为改为:
Object obj = null;
for (int i = 0; i <= count; i++)
{
obj = new Object();
}
这样的话,内存中只有一份Object对象引用,每次new Object()的时候,Object对象引用指向不同的Object罢了,但是内存中只有一份,这样就大大节省了内存空间了。
14.尽量采用懒加载的策略,即在需要的时候才创建
例如:
String str = "aaa";
if (i == 1)
{
list.add(str);
}
建议替换为:
if (i == 1)
{
String str = "aaa";
list.add(str);
}
14.不要创建一些不使用的对象,不要导入一些不使用的类
15.JPA将对象从持久状态转化为游离状态
for (ArSysSetup arSysSetup : initArSysSetupList) {
ArSysSetup newArSysSetup = new ArSysSetup();
BeanUtils.copyProperties(arSysSetup, newArSysSetup);
newArSysSetup.setId(null);
}
List<ArBillBalance> relationBalanceList = arBillBalanceRepository.findByBillId(RelateBillId);
Session session = entityManager.unwrap(org.hibernate.Session.class);
//计算对应的余额
for (ArBillBalance arBillBalance : relationBalanceList) {
session.evict(arBillBalance);
BigDecimal usedMoney = arBillBalance.getUsedMoney();
...
usedMoney = usedMoney.subtract(useRecord.getAmt());
arBillBalance.setUsedMoney(usedMoney);
}
@PersistenceContext //注入的是实体管理器,执行持久化操作
EntityManager entityManager;
Session session = entityManager.unwrap(org.hibernate.Session.class);
session.evict(obj);