《阿里巴巴Java开发手册-EasyCoding》精简整理

在这里插入图片描述

一、编程规约

1.区分JavaBean、POJO、Entity

原文:(P8)

领域模型命名规约如下:

  • 数据对象: xxxDO,xx为数据表名。
  • 数据传输对象:xxxDTO,xxx为业务领域相关的名称。
  • 展示对象: xxxVO,xxx一般为网页命名。
  • POJO是DO/DTO/BO/VO的统称,禁止命名成xxxPOJO。

1.1 JavaBean

JavaBean: 符合一定规范编写的Java类。

  • 所有属性私有化;
  • 有一个公共的午餐构造器;
  • 有getter/setter方法;
  • 可序列化,实现Serializable接口。

1.2 POJ

POJO: 就是普通的JavaBean,为了避免和EJB混淆所创造的简称,包括DO/DTO/BO/VO。

  • DO: 数据对象,对应一个表的所有字段;
  • DTO: 数据传输对象,对应一个表的所有字段;
  • BO: 业务对象,封装了一定的业务逻辑,内部可能包含多个对象,常位于业务层;
  • VO: 表现对象,表现层对象,主要用于前端界面的展示,类似ModuleAndView。

1.3 Entity

Entity: 实体类,对应表中的部分数据,类似DTO。

2. 创建枚举类

原文:(p10)

如果变量值仅在一个范围内变化,则用enum类型来定义。

2.1 Enum(value)

/**
 * 当枚举类只有一个属性value的时候
 */
public enum SeasonEnum {
    // 枚举值
    SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
    
    // 属性
    private Integer value;
    
    // 构造函数
    SeasonEnum(Integer value) {
        this.value = value;
    }
    
    public Integer value() {
        return value;
    }
}

2.2 Enum(value, name)

/**
 * 当枚举类有两个属性value、name的时候
 */
public enum SeasonEnum {
    // 枚举值
    SPRING(1, "spring"),
	SUMMER(2, "summer"),
    AUTUMN(3, "autumn"),
    WINTER(4, "winter");
    
    // 属性
    private Integer value;
    private String name;
    
    // 静态map容器,用于存放value和SeasonEnum的对应关系
    private static Map<Integer, SeasonEnum> valueMap = new HashMap();
    // 初始化
    static {
        Arrays.asStream(SeasonEnum.values()).forEach(e -> valueMap.put(e.value, e));
    }
    
    // 构造函数
    SeasonEnum(Integer value, String name) {
        this.value = value;
        this.name = name;
    }
    
    // 获取枚举类
    public SeasonEnum of(Integer value) {
        return valueMap.get(value);
    }
    
    // 获取value
    public Integer value() {
        return value;
    }
    
    // 获取name
    public Integer name() {
        return name;
    }
}

3. equals判断

原文:(p18)

推荐使用java.util.Objects.equals(Object obj, Object obj)(JDK7引入的工具类)。

4. 包装类比较

原文:(p18)

所有整形包装类对象之间值的比较,全部使用equals方法。

说明:

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

5. float、double不能直接做等值判断

原文:(p19)

浮点数之间的等职判断,基本数据类型不能用==进行比较,包装数据类型不能用equals方法进行判断。

举例:

public static void main(String[] args) {
    float a = 1.0f - 0.9f;
    float b = 0.9f - 0.8f;
    if (a == b) {
        // 预期进入此代码块,但是a == b的结果为false
    }
    
    Float x = Float.valueOf(a);
    Float y = Float.valueOf(b);
    if (x.equals(y)) {
        // 预期进入此代码块,但是x.equals(y)的结果为false
    }
    
    // 结果全部为false
    System.out.println("result1: " + ((0.05 + 0.01) == 0.06));
    System.out.println("result2: " + ((1.0 - 0.42) == 0.58));
    System.out.println("result3: " + ((4.015 * 100) == 401.5));
    System.out.println("result4: " + ((123.3 / 100) == 1.233));
    
}

解决方案:

数字类型操作尽量使用BigDecimal类型。

6. 不能使用BigDecimal(double)

原文:(p21)

禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象。

举例:

public static void main(String[] args) {
    System.out.println(new BigDecimal(0.13));
    // 输出结果为:0.13000000000000000444089209850062616169452667236328125
}

解决方案:

采用如下两种方式:

  • new BigDecimal(Double.toString(0.13))
  • BigDecimal.valueOf(0.13)

7. foreach循环中不可remove/add

原文:(p31)

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

举例:

public static void main(String[] args) {
    List<String> list = new ArrayList();
    list.add("1");
    list.add("2");
    for (String item : list) {
        if ("1".equals(item)) {
            // 会抛出ConcurrentModificationException
            list.remove(item);
        }
    }
}

解决方案:

public static void main(String[] args) {
    List<String> list = new ArrayList();
    list.add("1");
    list.add("2");
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
        if ("1".equals(iterator.next())) {
            iterator.remove();
        }
    }
}

8. 使用entrySet()遍历Map集合

原文:(p33)

使用entrySet()遍历Map类集合K/V,而不是用keySet()方式遍历。

说明:

keySet 其实遍历了2次,一次是转为Iterator对象,另一次是从hashMap中取出key所对应的value。
如果是JDK8,使用Map.forEach方法。

举例:

public static void main(String[] args) {
    Map<String, String> map = new HashMap();
    map.put("name", "小明");
    map.put("age", "18");
    // entrySet()
    for (Map.Entry<String, String> entry : map.entrySet()) {
        System.out.println("(" + entry.getKey() + ", " + entry.getValue() + ")");
    }
    // JDK 8
    map.forEach((key, value) -> System.out.println("(" + key + ", " + value + ")"));
}

9. 注意Map中不能存null的情况

原文:(p34)

高度注意Map类集合K/V能不能存储null值的情况。

集合类 Key Value Super 说明
Hashtable 不允许为null 不允许为null Dictionary 线程安全
ConcurrentHashMap 不允许为null 不允许为null AbstractMap 锁分段技术
(JDK8:CAS)
TreeMap 不允许为null 允许为null AbstractMap 线程不安全
HashMap 允许为null 允许为null AbstractMap 线程不安全

10. SimpleDateFormat 线程不安全

原文:(p37)

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

举例:

private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
    @Override
    protected DateFormat initialVlue() {
        return new SimpleDateFormat("yyyy-MM-dd");
    }
};

说明:

如果是JDK8 的应用,可以进行如下替换使用:

  • Date -> Instant
  • Calendar -> LocalDateTime
  • SimpleDateFormat -> DateTimeFormatter

11. switch括号中不能为null

原文:(p43)

当switch括号内的变量类型为String并且此变量为外部参数时,必须先进行null判断,否则会抛NPE。

举例:

private static void method(String s) {
    if (Objects.nonNull(s)) {
        // 为null会抛NullPointerException
        switch (s) {
            case "a":
                System.out.println(111);
                break;
            default:
                System.out.println(222);
        }
    }
}

12. 没有说明的注释代码可以直接删掉

原文:(p50)

谨慎注释掉代码,要在上方详细说明,若不是简单地注释掉。如果无用,则删除。

说明:

代码被注释掉有两种可能性。

  • 后续会恢复此段代码逻辑;
  • 永久不用。

前者如果没有备注信息,难以知晓注释动机。后者可解删掉即可。

13. 正则表达式Pattern不要方法内定义

原文:(p52)

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

说明:

不要在方法体内定义Pattern:

Pattern pattern = Pattern.compile("规则");

14. 避免使用BeanUtils.copy()

原文:(p52)

避免用Apache BeanUtils进行属性的copy。

说明:

Apache BeanUtils 性能较差,可以使用其他方案比如是Spring BeanUtils、Cglib BeanCopier,注意均是浅克隆。

15. Meth.random()技巧

原文:(p53)

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

举例:

public static void main(String[] args) {
    // 随机生成[0, 10)之间的整数
    System.out.println(new Random().nextInt(10));
    // 方法调用返回下一个从这个伪随机数生成器的序列中均匀分布的long值。
    // 18~20位
    System.out.println(Long.toString(l).length() + ", " + l);
}

二、日志规范

1. 不要直接使用日志系统

原文:(p62)

应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架 SLF4J 中的API。使用门面模式的日志架构,有利于维护和各个类的日志处理方式统一。

举例:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger logger = LoggerFactory.getLogger(Test.calss);

2. 不要直接System.out.println()、e.printStackTrace()

原文:(p64)

在生产环境(线上)中禁止直接使用 System.out 或 System.err 输出日志,或使用 e.printStackTrace()打印异常堆栈。

说明:

每次Jboss(一个基于Java的应用服务器程序)重启时标准日志输出文件与标准错误输出文件才滚动,如果大量输出送往这两个文件,容易造成文件大小超过操作系统大小限制。

3. 输出日志使用占位符

原文:(p63)

在日志输出时,字符串变量之间的拼接使用占位符的方式。
说明:
因为String字符串的拼接会使用StringBuilder的append()方法,有一定的幸能损耗。使用占位符仅是替换动作,可以有效提升性能。
举例:

logger.debug("Processing trade with id: {} and symbol: {}", id, symbole);

4. 异常日志输出规范

原文:(p64)

异常信息应该包括两类:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。

举例:

logger.error("{}_{}", 各类参数或对象.toString(), e.getMessage(), e);

三、Mysql数据库规范

原文:(p79)

  1. 表名不适用名词复数。

  2. 索引命名。

    • 主键索引 -> pk_字段名
    • 唯一索引 -> uk_字段名
    • 普通索引 -> idx_字段名
  3. 小数类型为decimal,禁止使用float和double。

  4. 如果存储的字符串长度几乎相等,应该使用char定长字符串类型。

  5. varchar是可变长字符串,不预先分配存储空间,长度不要超过5000个字符。如果存储长度大于此值,则应定义字段类型为text,独立出来一张表,用主键来对应,避免影响其他字段的索引效率。

  6. 表必备三个字段:id,create_time,update_time。

  7. 表的命名最好遵循”业务名称_表的作用“原则。

  8. 当表单行数超过500万行或者单表容量超过 2GB 时,才推荐进行分库分表。
    (如果预计三年后的数据量无法达到这个级别,请不要在创建表时就分库分表)

  9. 超过三个表禁止 join。需要join的字段,数据类型必须绝对一致;当多表关联查询时,保证被关联的字段需要有索引。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章