阿里JAVA编码规范手册 下载本文

阿里巴巴 Java开发手册

(五)集合处理

1.【强制】关于 hashCode和 equals的处理,遵循如下规则: 1) 只要重写 equals,就必须重写 hashCode。

2) 因为 Set存储的是不重复的对象,依据 hashCode和 equals进行判断,所以 Set存储的 对象必须重写这两个方法。

3) 如果自定义对象做为 Map的键,那么必须重写 hashCode和 equals。

说明:String重写了 hashCode和 equals方法,所以我们可以非常愉快地使用 String对象 作为 key来使用。

2.【强制】ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException

异常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList ; 说明:subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是

ArrayList 的一个视图,对于 SubList子列表的所有操作最终会反映到原列表上。

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

4.【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全 一样的数组,大小就是 list.size()。

说明:使用 toArray带参方法,入参分配的数组空间不够大时,toArray方法内部将重新分配 内存空间,并返回新数组地址;如果数组元素大于实际所需,下标为[ list.size() ]的数组 元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素 个数一致。 正例:

List list = new ArrayList(2); list.add(\ list.add(\

String[] array = new String[list.size()]; array = list.toArray(array);

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

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

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

String[] str = new String[] { \ List list = Arrays.asList(str);

——禁止用于商业用途,违者必究—— 11 / 37

阿里巴巴 Java开发手册

第一种情况:list.add(\运行时异常。

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

6.【强制】泛型通配符来接收返回的数据,此写法的泛型集合不能使用 add方 法,而不能使用 get方法,做为接口调用赋值时易出错。

说明:扩展说一下 PECS(Producer Extends Consumer Super)原则:1)频繁往外读取内容 的,适合用上界 Extends。2)经常往里插入的,适合用下界 Super。

7.【强制】不要在 foreach循环里进行元素的 remove/add操作。remove元素请使用 Iterator

方式,如果并发操作,需要对 Iterator对象加锁。 反例:

List a = new ArrayList(); a.add(\ a.add(\

for (String temp : a) {

if (\

a.remove(temp); } }

说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的 结果吗? 正例:

Iterator it = a.iterator(); while (it.hasNext()) {

String temp = it.next(); if (删除元素的条件) {

it.remove(); } }

8.【强制】 在 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() {

@Override

public int compare(Student o1, Student o2) {

return o1.getId() > o2.getId() ? 1 : -1; } }

——禁止用于商业用途,违者必究—— 12 / 37

阿里巴巴 Java开发手册

9.【推荐】集合初始化时,尽量指定集合初始值大小。 说明:ArrayList尽量使用 ArrayList(int initialCapacity) 初始化。

10.【推荐】使用 entrySet遍历 Map类集合 KV,而不是 keySet方式进行遍历。

说明:keySet其实是遍历了 2次,一次是转为 Iterator对象,另一次是从 hashMap中取出

key所对应的 value。而 entrySet只是遍历了一次就把 key和 value都放到了 entry中,效

率更高。如果是 JDK8,使用 Map.foreach方法。

正例:values()返回的是 V值集合,是一个 list集合对象;keySet()返回的是 K值集合,是 一个 Set集合对象;entrySet()返回的是 K-V值组合集合。

11.【推荐】高度注意 Map类集合 K/V能不能存储 null值的情况,如下表格:

集合类 Hashtable

ConcurrentHashMap TreeMap HashMap

Key

不允许为 null 不允许为 null 不允许为 null 允许为 null

Value

不允许为 null 不允许为 null

Super Dictionary AbstractMap

AbstractMap AbstractMap

说明 线程安全 分段锁技术 线程不安全 线程不安全

允许为 null 允许为 null

反例: 由于 HashMap的干扰,很多人认为 ConcurrentHashMap是可以置入 null值,注意存储

null值时会抛出 NPE异常。

12.【参考】合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和 不稳定性(unorder)带来的负面影响。

说明:有序性是指遍历的结果是按某种比较规则依次排列的。稳定性指集合每次遍历的元素次 序是一定的。如:ArrayList是 order/unsort;HashMap是 unorder/unsort;TreeSet是

order/sort。

13.【参考】利用 Set元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List的

contains方法进行遍历、对比、去重操作。

(六)并发处理

1.【强制】获取单例对象需要保证线程安全,其中的方法也要保证线程安全。 说明:资源驱动类、工具类、单例工厂类都需要注意。 2.【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。 正例:

public class TimerTaskThread extends Thread { public TimerTaskThread() {

super.setName(\... }

——禁止用于商业用途,违者必究——

13 / 37

阿里巴巴 Java开发手册

3.【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

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

4.【强制】线程池不允许使用 Executors去创建,而是通过 ThreadPoolExecutor的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors返回的线程池对象的弊端如下: 1)FixedThreadPool和 SingleThreadPool:

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

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

5.【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为 static变量,如果定义为

static,必须加锁,或者使用 DateUtils工具类。

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

private static final ThreadLocal df = new ThreadLocal() {

@Override

protected DateFormat initialValue() {

return new SimpleDateFormat(\ } };

说明:如果是 JDK8的应用,可以使用 Instant代替 Date,LocalDateTime代替 Calendar,

DateTimeFormatter代替Simpledateformatter,官方给出的解释:simplebeautifulstrong immutable thread-safe。

6.【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能 锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。 7.【强制】对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造 成死锁。

说明:线程一需要对表 A、B、C依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序 也必须是 A、B、C,否则可能出现死锁。

8.【强制】并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加 锁,要么在数据库层使用乐观锁,使用 version作为更新依据。

说明:如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次 数不得小于 3次。

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

——禁止用于商业用途,违者必究—— 14 / 37