学习一下阿里的Java开发手册,记录一些对自己有用的技术点

编程规约

命名规约

  1. 中括号是数组类型的一部分,数组定义如下:String[] args;
    反例:请勿使用 String args[]的方式来定义
  2. POJO 类中的任何布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错
    误。
    反例:定义为基本数据类型 boolean isSuccess;的属性,它的方法也是 isSuccess(),RPC
    框架在反向解析的时候,“以为”对应的属性名称是 success,导致属性获取不到,进而抛出
    异常。

常量定义

  1. 常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包
    内共享常量、类内共享常量。
    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规约

  1. 所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。
    说明:对于 Integer var=?在-128 至 127 之间的赋值,Integer 对象是在 IntegerCache.cache
    产生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之
    外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方
    法进行判断。
  2. 使用索引访问用 String 的 split 方法得到的数组时,需做最后一个分隔符后有无内
    容的检查,否则会有抛 IndexOutOfBoundsException 的风险。

    1
    2
    3
    String str = "a,b,c,,"; String[] ary = str.split(",");
    //预期大于 3,结果是 3
    System.out.println(ary.length);
  3. 循环体内,字符串的联接方式,使用 StringBuilder 的 append 方法进行扩展。
    反例:

    1
    2
    3
    4
    String str = "start";
    for(int i=0; i<100; i++){
    str = str + "hello";
    }

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

集合处理

  1. ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出 ClassCastException
    异常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList ;
    说明:subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是 ArrayList
    的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。
  2. 在 subList 场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、
    删除均产生 ConcurrentModificationException 异常。
  3. 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全
    一样的数组,大小就是 list.size()。
    反例:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它
    类型数组将出现 ClassCastException 错误。
    正例:

    1
    2
    3
    4
    5
    List<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,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素
    个数一致。

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

    1
    2
    String[] str = new String[] { "a", "b" };
    List list = Arrays.asList(str);

    第一种情况:list.add(“c”); 运行时异常。
    第二种情况:str[0]= “gujin”; 那么 list.get(0)也会随之修改。

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

    1
    2
    3
    4
    5
    6
    7
    8
    List<String> a = new ArrayList<String>();
    a.add("1");
    a.add("2");
    for (String temp : a) {
    if("1".equals(temp)){
    a.remove(temp);
    }
    }

    说明:这个例子的执行结果会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结
    果吗?
    正例:

    1
    2
    3
    4
    5
    6
    7
    Iterator<String> it = a.iterator();
    while(it.hasNext()){
    String temp = it.next();
    if(删除元素的条件){
    it.remove();
    }
    }
  6. 集合初始化时,尽量指定集合初始值大小。
    说明:ArrayList 尽量使用 ArrayList(int initialCapacity) 初始化。

  7. 使用 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 值组合集合。
  8. 利用 Set 元素唯一的特性,可以快速对另一个集合进行去重操作,避免使用 List 的
    contains 方法进行遍历去重操作。

并发处理

  1. SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为
    static,必须加锁,或者使用 DateUtils 工具类。
    1
    2
    3
    4
    5
    6
    private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){
    @Override
    protected DateFormat initialValue() {
    return new SimpleDateFormat("yyyy-MM-dd");
    }
    }

说明:如果是JDK8的应用,可以使用instant代替Date,Localdatetime代替Calendar,Datetimeformatter代替SiimpleDateForMatter,官方给出的解释:simple beautiful strong
immutable thread-safe。

  1. 避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed
    导致的性能下降。
    说明:Random 实例包括 java.util.Random 的实例或者 Math.random()实例。
    正例:在 JDK7 之后,可以直接使用 API ThreadLocalRandom,在 JDK7 之前,可以做到每个线
    程一个实例