寒假实习,在阅读公司软件研发代码规范文档以后,我把自己在写代码过程中容易忽略的问题做了总结,现发布在我的博客中,不知道大家是否有同样在写代码过程中不规范或者没有注意的地方。

 

1、定义DO / DTO / VO 等POJO 类时,不要设定任何属性默认值。

 

2、序列化类新增属性时,请不要修改serialVersionUID 字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么请修改serialVersionUID 值。说明:注意serialVersionUID 不一致会抛出序列化运行时异常。

 

3、POJO 类必须写toString 方法。使用IDE 的中工具:source > generate toString时,如果继承了另一个POJO 类,注意在前面加一下super.toString。说明:在方法执行抛出异常时,可以直接调用POJO 的toString() 方法打印其属性值,便于排查问题。

 

4、使用索引访问用String 的split 方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛IndexOutOfBoundsException 的风险。说明:String str = "a,b,c,,";String[] ary = str.split(",");//预期大于3,结果是3 System.out.println(ary.length);

 

5、循环体内,字符串的联接方式,使用StringBuilder 的append 方法进行扩展。说明:反编译出的字节码文件显示每次循环都会new 出一个StringBuilder 对象,然后进行append 操作,最后通过toString 方法返回String 对象,造成内存资源浪费。

 

6、任何类、方法、参数、变量,严控访问范围。过宽泛的访问范围,不利于模块解耦。思考:如果是一个private 的方法,想删除就删除,可是一个public 的Service 方法,或者一个public 的成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的视线内,变量作用域太大,如果无限制的到处跑,那么你会担心的。

 

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

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

 

8、在subList 场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均产生ConcurrentModificationException 异常。

 

9、使用工具类Arrays.asList() 把数组转换成集合时,不能使用其修改集合相关的方法,它的add / remove / clear 方法会抛出UnsupportedOperationException 异常。说明:asList 的返回对象是一个Arrays 内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。

String[] str = new String[] { "a", "b" };

List list = Arrays.asList(str);

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

第二种情况:str[0]= "gujin"; 那么list.get(0) 也会随之修改。

 

10、不要在foreach 循环里进行元素的remove / add 操作。remove 元素请使用Iterator方式,如果并发操作,需要对Iterator 对象加锁。

 

11、在JDK 7 版本以上,Comparator 要满足自反性,传递性,对称性,不然Arrays.sort ,Collections.sort 会报IllegalArgumentException 异常。

 

12、使用entrySet 遍历Map 类集合KV ,而不是keySet 方式进行遍历。说明:keySet 其实是遍历了2 次,一次是转为Iterator 对象,另一次是从hashMap 中取出key 所对应的value 。而entrySet 只是遍历了一次就把key 和value 都放到了entry 中,效率更高。如果是JDK 8,使用Map.foreach 方法。

 

13、利用Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用List 的contains 方法进行遍历、对比、去重操作。

 

14、线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

 

15、线程池不允许使用Executors 去创建,而是通过ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors 返回的线程池对象的弊端如下:

1)FixedThreadPool 和SingleThreadPool :允许的请求队列长度为Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致OOM 。

2)CachedThreadPool 和ScheduledThreadPool :允许的创建线程数量为Integer.MAX_VALUE ,可能会创建大量的线程,从而导致OOM 。

 

16、SimpleDateFormat 是线程不安全的类,一般不要定义为static 变量,如果定义为static ,必须加锁,或者使用DateUtils 工具类。

正例:注意线程安全,使用DateUtils 。亦推荐如下处理:

private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {

@ Override

protected DateFormat initialValue() {

return new SimpleDateFormat("yyyy-MM-dd");

}

}

说明:如果是JDK 8 的应用,可以使用Instant 代替Date ,LocalDateTime 代替Calendar ,DateTimeFormatter 代替Simpledateformatter ,官方给出的解释:simple beautiful strongimmutable thread -safe 。

 

17、并发修改同一记录时,避免更新丢失,要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version 作为更新依据。说明:如果每次访问冲突概率小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3 次。

 

18、多线程并行处理定时任务时,Timer 运行多个TimeTask 时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService 则没有这个问题。

 

19、HashMap 在容量不够进行resize 时由于高并发可能出现死链,导致CPU 飙升,在开发过程中注意规避此风险。

 

20、注意Math . random() 这个方法返回是double 类型,注意取值的范围0≤x <1 (能够取到零值,注意除零异常),如果想获取整数类型的随机数,不要将x 放大10 的若干倍然后取整,直接使用Random 对象的nextInt 或者nextLong 方法。

 

21、获取当前毫秒数System . currentTimeMillis(); 而不是new Date() . getTime();说明:如果想获取更加精确的纳秒级时间值,用System . nanoTime() 。在JDK 8 中,针对统计时间等场景,推荐使用Instant 类。

 

22、任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。

 

23、避免重复打印日志,浪费磁盘空间,务必在log4j.xml 中设置additivity = false。

 

24、小数类型为decimal ,禁止使用float 和double 。说明:float 和double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果存储的数据范围超过decimal 的范围,建议将数据拆成整数和小数分开存储。

 

25、合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。

正例:人的年龄用unsigned tinyint(表示范围0-255,人的寿命不会超过255 岁);海龟就必须是smallint ,但如果是太阳的年龄,就必须是int;如果是所有恒星的年龄都加起来,那么就必须使用bigint 。