《阿里巴巴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的字段,數據類型必須絕對一致;當多表關聯查詢時,保證被關聯的字段需要有索引。

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