第七篇 alibaba Java开发手册笔记
编程规约
命名规约
- 中括号是数组类型的一部分,数组定义如下:String[] args;
反例:请勿使用 String args[]的方式来定义 - POJO 类中的任何布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错
误。
反例:定义为基本数据类型 boolean isSuccess;的属性,它的方法也是 isSuccess(),RPC
框架在反向解析的时候,“以为”对应的属性名称是 success,导致属性获取不到,进而抛出
异常。
常量定义
- 常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包
内共享常量、类内共享常量。
1) 跨应用共享常量:放置在二方库中,通常是 client.jar 中的 const 目录下。
2) 应用内共享常量:放置在一方库的 modules 中的 const 目录下。
反例:易懂变量也要统一定义成应用内共享常量,两位攻城师在两个类中分别定义了表示
“是”的变量:
类 A 中:public static final String YES = “yes”;
类 B 中:public static final String YES = “y”;
A.YES.equals(B.YES),预期是 true,但实际返回为 false,导致产生线上问题。
3) 子工程内部共享常量:即在当前子工程的 const 目录下。
4) 包内共享常量:即在当前包下单独的 const 目录下。
5) 类内共享常量:直接在类内部 private static final 定义。
OOP规约
- 所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。
说明:对于 Integer var=?在-128 至 127 之间的赋值,Integer 对象是在 IntegerCache.cache
产生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之
外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方
法进行判断。 使用索引访问用 String 的 split 方法得到的数组时,需做最后一个分隔符后有无内
容的检查,否则会有抛 IndexOutOfBoundsException 的风险。123String str = "a,b,c,,"; String[] ary = str.split(",");//预期大于 3,结果是 3System.out.println(ary.length);循环体内,字符串的联接方式,使用 StringBuilder 的 append 方法进行扩展。
反例:1234String str = "start";for(int i=0; i<100; i++){str = str + "hello";}说明:反编译出的字节码文件显示每次循环都会 new 出一个 StringBuilder 对象,然后进行
append 操作,最后通过 toString 方法返回 String 对象,造成内存资源浪费。
集合处理
- ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出 ClassCastException
异常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList ;
说明:subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是 ArrayList
的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。 - 在 subList 场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、
删除均产生 ConcurrentModificationException 异常。 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全
一样的数组,大小就是 list.size()。
反例:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它
类型数组将出现 ClassCastException 错误。
正例:12345List<String> list = new ArrayList<String>(2);list.add("guan");list.add("bao");String[] array = new String[list.size()];array = list.toArray(array);说明:使用 toArray 带参方法,入参分配的数组空间不够大时,toArray 方法内部将重新分配
内存空间,并返回新数组地址;如果数组元素大于实际所需,下标为[ list.size() ]的数组
元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素
个数一致。使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,
它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
说明:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList
体现的是适配器模式,只是转换接口,后台的数据仍是数组。12String[] str = new String[] { "a", "b" };List list = Arrays.asList(str);第一种情况:list.add(“c”); 运行时异常。
第二种情况:str[0]= “gujin”; 那么 list.get(0)也会随之修改。不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator
方式,如果并发操作,需要对 Iterator 对象加锁。
反例:12345678List<String> a = new ArrayList<String>();a.add("1");a.add("2");for (String temp : a) {if("1".equals(temp)){a.remove(temp);}}说明:这个例子的执行结果会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结
果吗?
正例:1234567Iterator<String> it = a.iterator();while(it.hasNext()){String temp = it.next();if(删除元素的条件){it.remove();}}集合初始化时,尽量指定集合初始值大小。
说明:ArrayList 尽量使用 ArrayList(int initialCapacity) 初始化。- 使用 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 值组合集合。 - 利用 Set 元素唯一的特性,可以快速对另一个集合进行去重操作,避免使用 List 的
contains 方法进行遍历去重操作。
并发处理
- SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为
static,必须加锁,或者使用 DateUtils 工具类。123456private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){@Overrideprotected DateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd");}}
说明:如果是JDK8的应用,可以使用instant代替Date,Localdatetime代替Calendar,Datetimeformatter代替SiimpleDateForMatter,官方给出的解释:simple beautiful strong
immutable thread-safe。
- 避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed
导致的性能下降。
说明:Random 实例包括 java.util.Random 的实例或者 Math.random()实例。
正例:在 JDK7 之后,可以直接使用 API ThreadLocalRandom,在 JDK7 之前,可以做到每个线
程一个实例