vlambda博客
学习文章列表

为什么你的Java代码总是有Bug?




如果说在Java开发领域中哪本书必读,非读不可,那一定是《阿里巴巴Java开发手册》。


这本书的每一条建议,都是阿里大佬们的踩了各种坑,深入理解了JDK源码的实战总结,可以说是任何Java选手的实战指南。



今天重读《阿里巴巴Java开发手册》,收获良多。


本文从一个初级程序员,或者刚入门Java的开发者的视角,来盘点一下《阿里巴巴Java开发手册》中,新手程序员最容易踩雷,用错,或者忽视的一些技术细节。


相信读完,作为一个新手程序员应该很有收获,文末有《阿里巴巴Java开发手册》--最新版:嵩山版,提供下载。


正文


  1. 强制】Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。 


正例:"test".equals(object); 

反例:object.equals("test"); 


说明:推荐使用 JDK7 引入的工具类 java.util.Objects#equals(Object a, Object b)


2.【强制】所有整型包装类对象之间值的比较,全部使用 equals 方法比较。


说明:对于 Integer var = ? 在-128 至 127 之间的赋值,Integer 对象是在 IntegerCache.cache 产生, 会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都 会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。


3.关于基本数据类型与包装数据类型的使用标准如下:


1) 【强制所有的 POJO 类属性必须使用包装数据类型。 

2) 【强制RPC 方法的返回值和参数必须使用包装数据类型。

3) 【推荐】所有的局部变量使用基本数据类型。


说明:POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或 者入库检查,都由使用者来保证。


正例数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。


反例:某业务的交易报表上显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC 服务,调 用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线-。所以包装数据类型 的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。


4.强制】定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。


反例:POJO 类的 createTime 默认值为 new Date(),但是这个属性在数据提取时并没有置入具体值,在 更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间。



5.强制】获取当前毫秒数:System.currentTimeMillis(); 而不是 new Date().getTime()。


说明:如果想获取更加精确的纳秒级时间值,使用 System.nanoTime 的方式。在 JDK8 中,针对统计时间 等场景,推荐使用 Instant 类。


6. 强制】不允许在程序任何地方中使用:


1)java.sql.Date。 

2)java.sql.Time。 

3)java.sql.Timestamp。


说明:第 1 个不记录时间,getHours()抛出异常;

第 2 个不记录日期,getYear()抛出异常;

第 3 个在构造 方法 super((time/1000)*1000),在 Timestamp 属性 fastTime 和 nanos 分别存储秒和纳秒信息。反例:java.util.Date.after(Date)进行时间比较时,当入参是 java.sql.Timestamp 时,会触发 JDK BUG(JDK9 已修复),可能导致比较时的意外结果。


7.强制】在使用 java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,一定要使 用含有参数类型为 BinaryOperator,参数名为 mergeFunction 的方法,否则当出现相同 key 值时会抛出 IllegalStateException 异常。


说明:参数 mergeFunction 的作用是当出现 key 重复时,自定义对 value 的处理策略。


正例

1List<Pair<String, Double>> pairArrayList = new ArrayList<>(3);
2pairArrayList.add(new Pair<>("version"12.10));
3pairArrayList.add(new Pair<>("version"12.19));
4pairArrayList.add(new Pair<>("version"6.28));
5Map<String, Double> map = pairArrayList.stream().collect(
6// 生成的 map 集合中只有一个键值对:{version=6.28}
7Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2))


反例

1String[] departments = new String[] {"iERP""iERP""EIBU"};
2// 抛出 IllegalStateException 异常
3Map<Integer, String> map = Arrays.stream(departments)
4 .collect(Collectors.toMap(String::hashCode, str -> str));


8.强制】在使用 java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,一定要注 意当 value 为 null 时会抛 NPE 异常。


说明:在 java.util.HashMap 的 merge 方法里会进行如下的判断


if (value == null || remappingFunction == null)throw new NullPointerException()


反例


1List<Pair<String, Double>> pairArrayList = new ArrayList<>(2);
2pairArrayList.add(new Pair<>("version1"8.3));
3pairArrayList.add(new Pair<>("version2"null));
4Map<String, Double> map = pairArrayList.stream().collect(
5// 抛出 NullPointerException 异常
6Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2))


9.强制】ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出 ClassCastException 异 常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。 


说明:subList()返回的是 ArrayList 的内部类 SubList,并不是 ArrayList 本身,而是 ArrayList 的一个视 图,对于 SubList 的所有操作最终会反映到原列表上。


10.强制】在 subList 场景中,高度注意对父集合元素的增加或删除,均会导致子列表的遍历、 增加、删除产生 ConcurrentModificationException 异常。


11. 强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一 致、长度为 0 的空数组。


反例:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误。


正例:


1List<String> list = new ArrayList<>(2);
2list.add("guan");
3list.add("bao");
4String[] array = list.toArray(new String[0]);


说明:使用 toArray 带参方法,数组空间大小的 length: 

1) 等于 0,动态创建与 size 相同的数组,性能最好。 

2) 大于 0 但小于 size,重新创建大小等于 size 的数组,增加 GC 负担。3) 等于 size,在高并发情况下,数组创建完成之后,size 正在变大的情况下,负面影响与 2 相同。 

4) 大于 size,空间浪费,且在 size 处插入 null 值,存在 NPE 隐患。


12.强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。


说明:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList 体现的是适配 器模式,只是转换接口,后台的数据仍是数组。


String[] str = new String[] { "chen", "yang", "hao" }; List list = Arrays.asList(str)


第一种情况:list.add("yangguanbao"); 运行时异常。

第二种情况:str[0] = "change"; 也会随之修改,反之亦然。


13.强制】在 JDK7 版本及以上,Comparator 实现类要满足如下三个条件,不然 Arrays.sort, Collections.sort 会 报IllegalArgumentException 异常。


说明:三个条件如下 

1) x,y 的比较结果和 y,x 的比较结果相反。

2) x>y,y>z,则 x>z。 

3) x=y,则 x,z 比较结果和 y,z 比较结果相同。


反例:下例中没有处理相等的情况,交换两个对象判断结果并不互反,不符合第一个条件,在实际使用中 可能会出现异常。


new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o1.getId() > o2.getId() ? 1 : -1; }}


END


《阿里巴巴Java开发手册》最新版--嵩山版


链接:

https://pan.baidu.com/s/1O3muUwNuXDXCwTwEzEQ3Rw

提取码:x0b8


为什么你的Java代码总是有Bug?

为什么你的Java代码总是有Bug?

往  期  精  选





"微  信  关  注 、 查  看  往  期  文  章 "



"加  我  聊  聊 "