Java规范笔记---读《阿里巴巴Java开发手册》

以下规范摘自《阿里巴巴Java开发手册》,基本摘取了自己平时需要注意或者容易出错的部分,以提现自己。

命名风格

  1. 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类 命名以它要测试的类的名称开始,以 Test 结尾。
  2. POJO 类中布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错误。
  3. 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。

    例:应用工具类包名为com.alibaba.open.util、类名为MessageUtils(此规则参考 spring 的框架结构)

  4. 【推荐】如果使用到了设计模式,建议在类名中体现出具体模式。

    例:BeanFactory,OrderBuilder

  5. 【推荐】接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码简洁,加上有效的Javadoc注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。
  6. JDK8 中接口允许有默认实现,那么这个 default 方法,是对所有实现类都有价值的默 认实现。
  7. 接口实现类命名规则:
    1. 一般使用在接口名称后面加Impl表示实现类
    2. 如果是形容能力的接口名称,取对应的形容词做接口名(通常是–able 的形式)。例:AbstractTranslator 实现 Translatable。
  8. 枚举类名建议带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开。

    枚举其实就是特殊的常量类,且构造方法被默认强制是私有。

  9. Service/DAO层方法命名规约
    1. 获取单个对象的方法用get做前缀。
    2. 获取多个对象的方法用list做前缀。
    3. 获取统计值的方法用count做前缀。
    4. 插入的方法用save做前缀。
    5. 删除的方法用remove做前缀。
    6. 修改的方法用update做前缀。
  10. 领域模型命名规约
    1. 数据对象:xxxDO,xxx即为数据表名。
    2. 数据传输对象:xxxDTO,xxx为业务领域相关的名称。
    3. 展示对象:xxxVO,xxx一般为网页名称。

常亮定义

  1. 常量的复用层次
    1. 跨应用共享常量:放置在二方库中,通常是client.jar中的constant目录下。
    2. 应用内共享常量:放置在一方库的modules中的constant目录下。
    3. 子工程内部共享常量:即在当前子工程的constant目录下。
    4. 包内共享常量:即在当前包下单独的constant目录下。
    5. 类内共享常量:直接在类内部private static final定义。

代码格式

  1. 如果是大括号内为空,则简洁地写成{}即可,不需要换行,右大括号后还有else等代码则不换行;表示终止的右大括号后必须换行
  2. if/for/while/switch/do 等保留字与括号之间都必须加空格。
  3. 任何二目、三目运算符的左右两边都需要加一个空格。
  4. 缩进采用 4 个空格,禁止使用 tab 字符。
  5. 【推荐】方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义
    之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。

OOP规约

  1. 【强制】所有的覆写方法,必须加@Override 注解。
  2. 【强制】所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。

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

  3. 基本数据类型与包装数据类型

    1. 【强制】所有的POJO类属性必须使用包装数据类型。
    2. 【强制】RPC方法的返回值和参数必须使用包装数据类型。
    3. 【推荐】所有的局部变量使用基本数据类型。
  4. 【强制】定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。
  5. 【强制】构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。
  6. 【强制】POJO 类必须写 toString 方法。

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

  7. 【推荐】 类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法。

  8. 【推荐】 类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法。当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起, 便于阅读。
  9. 【推荐】setter 方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名。在
    getter/setter 方法中,不要增加业务逻辑,增加排查问题的难度。
  10. 【强制】关于 hashCode 和 equals 的处理,遵循如下规则:
    1. 只要重写equals,就必须重写hashCode。
    2. 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的 对象必须重写这两个方法。
    3. 如果自定义对象做为Map的键,那么必须重写hashCode和equals。
      说明: String重写了hashCodeequals方法,所以我们可以使用String对象作为key来使用。
  11. 【强制】ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException 异常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList ; 说明:subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是 ArrayList 的一个视图,对于SubList子列表的所有操作最终会反映到原列表上。
  12. 【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全 一样的数组,大小就是 list.size()。

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

    • 正例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    List<String> list = new ArrayList<String>(2);
    list.add("guan"); list.add("bao"); String[] array = new String[list.size()];
    array = list.toArray(array);
    ```
    * 反例:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它 类型数组将出现 ClassCastException 错误。 13. 【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方 法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
    > 说明:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList 体现的是适配器模式,只是转换接口,后台的数据仍是数组。
    14. remove 元素请使用`Iterator`方式,如果并发操作,需要对 Iterator 对象加锁。
    15. 【推荐】集合初始化时,指定集合初始值大小。 > 说明:HashMap使用HashMap(int initialCapacity) 初始化,
    > 1. 正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loader factor)默认为 0.75,如果暂时无法确定初始值大小,请设置为 16。
    > 2. 反例:HashMap 需要放置 1024 个元素,由于没有设置容量初始大小,随着元素不断增加,容 量 7 次被迫扩大,resize 需要重建 hash 表,严重影响性能。
    16. 【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。如果是 JDK8,使用 Map.foreach 方法。
    17. 【【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用 DateUtils 工具类。 > 正例:注意线程安全,使用 DateUtils。亦推荐如下处理:

    private static final ThreadLocaldf = new ThreadLocal() {

    @Override
    protected DateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd");
    } 
    

    };
    ```

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

  13. 【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。

    说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法。

  14. 【强制】如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次 数不得大于 3 次。
  15. 【强制】多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获 抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。
  16. 【推荐】避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed 导致的性能下降。

    说明:Random 实例包括 java.util.Random 的实例或者 Math.random()的方式。
    正例:在 JDK7 之后,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前,需要编码保 证每个线程持有一个实例。

  17. 【强制】在一个 switch 块内,每个 case 要么通过 break/return 等来终止,要么注释说明程 序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且 放在最后,即使它什么代码也没有。
  18. 强制】在 if/else/for/while/do 语句中必须使用大括号。即使只有一行代码
  19. 【推荐】表达异常的分支时,少用 if-else 方式,这种方式可以改写成:

    1
    2
    3
    4
    if (condition) {
    ... return obj;
    } // 接着写 else 的业务逻辑代码;
    ``` 说明:如果非得使用 if()...else if()...else...方式表达逻辑,请勿超过 3 层。 正例:逻辑上超过 3 层的 if-else 代码可以使用卫语句,或者状态模式来实现。卫语句示例 如下:

    public void today() {

    if (isBusy()) {
        System.out.println(“change time.”); 
        return;
    }
    if (isFree()) {
        System.out.println(“go to travel.”);
        return; 
    }
    System.out.println(“stay at home to learn Alibaba Java Coding Guideline.”);
    return;
    

    }
    ```

  20. 循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、
    获取数据库连接,进行不必要的 try-catch 操作(这个 try-catch 是否可以移至循环体外)。

  21. 【强制】在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。

    说明:不要在方法体内定义:Pattern pattern = Pattern.compile(规则);

  22. 【强制】不能在 finally 块中使用 return,finally 块中的 return 返回后方法结束执行,不 会再执行 try 块中的 return 语句。
我知道是不会有人点的,但万一有人想不开呢!