1. 程式人生 > 其它 >編寫高效優雅java程式碼【轉】

編寫高效優雅java程式碼【轉】

面向物件

構造器引數太多怎麼辦

Java類設計過程中,如果類的構造器或者靜態工廠中具有多個引數,並且其中有大量的可選引數時,我們應該怎麼辦?

Telescoping Constructor模式(重疊構造器)

我們首先想到的方法肯定是傳統的構造器

/**
 * Created by itbird on 2017/3/23
 */

public class Person {
    private String name;
    private String sex;
    private int year;

    public Person(String name, String sex, int year) {
        this.name = name;
        this.sex = sex;
        this.year = year;
    }
}

但是設計以及使用過程中我們發現以下幾點問題:

  • 屬性引數逐漸變多時屬性引數逐漸變多時,由於要對之前的程式碼做相容,所以不可以直接在現有構造器後面追加屬性,只能不斷新增構造器
/**
 * Created by itbird on 2017/3/23
 */

public class Person {
    private String name;
    private String sex;
    private int year;
    private String city;

    public Person(String name, String sex, int year) {
        this.name = name;
        this.sex = sex;
        this.year = year;
    }

    public Person(String name, String sex, int year, String city) {
        this.name = name;
        this.sex = sex;
        this.year = year;
        this.city = city;
    }
}
  • 屬性引數中有大量的可選引數
/**
 * Created by itbird on 2017/3/23
 */

public class Person {
    private String name;
    private String sex;
    private int year;
    private String city;
    private String state;
    private boolean isFemale;
    private boolean isEmployed;
    private boolean isHomewOwner;

    public Person(String name, String sex, int year) {
        this.name = name;
        this.sex = sex;
        this.year = year;
    }

    public Person(String name, String sex, int year, String city) {
        this.name = name;
        this.sex = sex;
        this.year = year;
        this.city = city;
    }

    public Person(String name, String sex, int year, String city, String newState,
                  boolean newIsFemale, boolean newIsEmployed, boolean newIsHomeOwner) {
        this.name = name;
        this.sex = sex;
        this.year = year;
        this.city = city;
        this.state = newState;
        this.isFemale = newIsFemale;
        this.isEmployed = newIsEmployed;
        this.isHomewOwner = newIsHomeOwner;
    }
}

顯而易見,這樣寫的類構造器雖然無可厚非,但是當有許多引數的時候,客戶端程式碼會很難編寫,並且難以閱讀。如果讀者想知道那些值是什麼意思,必須很仔細的數著這些引數來探個究竟。

JavaBeans模式

在這種模式下,呼叫一個無參構造器來建立物件,然後呼叫setter方法來設定每個必要的引數,以及每個相關的可選引數。

/**
 * Created by itbird on 2017/3/23
 */

public class Person {
    private String name;
    private String sex;
    private int year;
    private String city;
    private String state;
    private boolean isFemale;
    private boolean isEmployed;
    private boolean isHomewOwner;

    public Person() {
    }
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public boolean isFemale() {
        return isFemale;
    }

    public void setFemale(boolean female) {
        isFemale = female;
    }

    public boolean isEmployed() {
        return isEmployed;
    }

    public void setEmployed(boolean employed) {
        isEmployed = employed;
    }

    public boolean isHomewOwner() {
        return isHomewOwner;
    }

    public void setHomewOwner(boolean homewOwner) {
        isHomewOwner = homewOwner;
    }
}

這種模式彌補重疊構造器模式的不足。說的明白一點,就是建立例項很容易,這樣產生的程式碼讀起來也很容易:

Person person = new Person();
person.setCity("重慶");
person.setYear(12);
person.setSex("男");
person.setName("itbird");

遺憾的是,JavaBeans模式自身有著很嚴重的缺點。因為構造過程被分到幾個呼叫中,在構造過程中JavaBean可能處於非一致的狀態。JavaBeans模式阻止了把類做成不可變的可能,這就需要程式設計師付出額外的努力來確保他的執行緒安全。

Builder模式

5個或者5個以上的成員變數 引數不多,但是在未來,引數會增加

public class Person {
    private String name;
    private String sex;
    private int year;
    private String city;
    private String state;
    private boolean isFemale;
    private boolean isEmployed;
    private boolean isHomewOwner;

    public Person() {
    }

    public static class PersonBuilder {
        // 必要引數
        private String name;
        // 可選引數
        private String sex;
        private int year;
        private String city;
        private String state;
        private boolean isFemale;
        private boolean isEmployed;
        private boolean isHomewOwner;

        public PersonBuilder(String name) {
            this.name = name;
        }

        public PersonBuilder setSex(String sex) {
            this.sex = sex;
            return this;
        }

        public PersonBuilder setYear(int year) {
            this.year = year;
            return this;
        }

        public PersonBuilder setCity(String city) {
            this.city = city;
            return this;
        }

        public PersonBuilder setState(String state) {
            this.state = state;
            return this;
        }

        public PersonBuilder setFemale(boolean female) {
            isFemale = female;
            return this;
        }

        public PersonBuilder setEmployed(boolean employed) {
            isEmployed = employed;
            return this;
        }

        public PersonBuilder setHomewOwner(boolean homewOwner) {
            isHomewOwner = homewOwner;
            return this;
        }

        public Person build() {
            Person person = new Person();
            person.name = name;
            person.sex = sex;
            person.city = city;
            person.isEmployed = isEmployed;
            person.isFemale = isFemale;
            person.isHomewOwner = isHomewOwner;
            person.state = state;
            person.year = year;
            return person;
        }
    }
}

呼叫的例項:

Person person = new Person.PersonBuilder("itbird")
                   .setCity("重慶").setYear(15).build();

顯然,使用Builder模式解決了上訴的難題,達到了“以不變(Builder)應萬變(引數)”的目的。

不需要例項化的類應該構造器私有

​ 一些工具類提供的都是靜態方法,這些類是不應該提供具體的例項的。可以參考JDK中的Arrays。

不要建立不必要的物件

  • 反例

該語句每次被執行的時候都建立一個新的String例項,但這些建立物件的動作全部都是不必要的。傳遞給String構造器的引數(”stringette”)本身就是一個String例項,功能方面等同於構造器建立的物件。如果這種用法實在一個迴圈中,或者在一個被頻繁呼叫的方法中,就會創建出成千上萬不必要的String例項。

String s = new String("stringette");
  • 正例

這個版本只用了一個String例項,而不是每一次執行程式碼都建立一個新的例項。

String s = "stringette";

避免使用終結方法

終結方法(finalizer)通常是不可預測的,也是很危險的,一般情況下是不必要的。使用終結方法會導致行為不穩定,降低效能以及可移植性問題。

  • 反例
Foo foo = new Foo(...);
try{
    //Do what must be done with foo
    ...
}finally{
    foo.terminate();        //Explicit termination method
}

使用終結方法的好處,它們有兩種合法用途:

第一種用途是,當物件的所有者忘記呼叫前面建議的顯示終止方法的時,終結方法可以充當“安全網(safety net)”。遲一點釋放關鍵資源總比永遠不釋放要好。但是如果終結方法發現資源還未被終止,則應該在日誌中記錄一條警告,因為這是客戶端的一個BUG,應當被修復。

第二種合理用途與物件的本地對等體(native peer)有關。本地對等體是一個本地物件(native object),普通物件通過本地方法(native method)委託給一個本地物件。因為本地對等體不是一個普通物件,所以垃圾回收器不會知道它,當它的Java對等體被回收的時候,它不會被回收。在本地對等體不擁有關鍵資源的前提下,終結方法正是執行這項任務最合適的工具。如果本地對等體擁有必須被及時終止的資源,那麼該類就應該具有一個顯示的終止方法,如前所述。終止方法應該完成所有必要的工作以便釋放關鍵資源。終止方法可以是本地的,或者呼叫本地方法。

使類和成員的可訪問性最小化

編寫程式和設計架構,最重要的目標之一就是模組之間的解耦。使類和成員的可訪問性最小化無疑是有效的途徑之一。

為什麼要使類和成員的可訪問性最小化

可以有效的解除系統中各個模組的耦合度、實現每個模組的獨立開發、使得系統更加的可維護,更加的健壯。

如何最小化類和介面的可訪問性?

能將類和介面做成包級私有就一定要做成包級私有的。

如果一個類或者介面,只被另外的一個類應用,那麼最好將這個類或者介面做成其內部的私有類或者介面。

如何最小化一個了類中的成員的可訪問性?

首先設計出該類需要暴露出來的api,然後將剩下的成員的設計成private型別。然後再其他類需要訪問某些private型別的成員時,在刪掉private,使其變成包級私有。如果你發現你需要經常這樣做,那麼就請你重新設計一下這個類的api。

對於protected型別的成員,作用域是整個系統,所以,能用包訪問型別的成員的話就儘量不要使用保護行的成員。

不能為了測試而將包中的類或者成員變為public型別的,最多隻能設定成包級私有型別。

例項域絕對不能是public型別的.

使可變性最小化

儘量使類不可變,不可變的類比可變的類更加易於設計、實現和使用,而且更不容易出錯,更安全。

常用的手段

  • 不提供任何可以修改物件狀態的方法;
  • 使所有的域都是final的。
  • 使所有的域都是私有的。
  • 使用寫時複製機制。帶來的問題:會導致系統產生大量的物件,而且效能有一定的影響,需要在使用過程中小心權衡。

優先使用複合

繼承容易破壞封裝性,而且會使子類的實現依賴於父類。
複合則是在類中增加一個私有域,引用類的一個例項,這樣的話就避免了依賴類的具體實現。
只有在子類確實是父類的一個子型別時,才比較適合用繼承。

介面優於抽象類

java是個單繼承的,但是類允許實現多個介面。
所以當發生業務變化時,新增介面,並且需要進行業務變化的類現新介面即可。但是抽象類有可能導致不需要變化的類也不得不實現新增的業務方法。
在JDK裡常用的一種設計方法是:定義一個介面,宣告一個抽象的骨架類實現介面,骨架類類實現通用的方法,而實際的業務類可以同時實現介面又繼承骨架類,也可以只實現介面。
如HashSet實現了implements Set介面 但是又extends 類AbstractSet,而AbstractSet本身也實現了Set介面。其他如Map,List都是這樣的設計的。


方法

可變引數要謹慎使用

可變引數是允許傳0個引數的
如果是引數個數在1~多個之間的時候,要做單獨的業務控制。

//可能很多 0~很多
   static int sum(int... args) {
       int sum = 0;
       for (int arg : args)
           sum += arg;
       return sum;
   }
   
   //要求引數的個數,是1~多個
   //
   static int sum1(int... args) {
       if(args.length==0) {
           //做點異常處理
       }
       if(args[0]==100) {

       }
       for(int i=1;i<args.length;i++) {
           int sum = 0;
           sum += args[i];
           return sum; 
       }
   }

   static int sum2(int flag, int... args) {
       if(flag==100) {

       }
       int sum = 0;
       for (int arg : args)
           sum += arg;
       return sum;
       return Collections.EMPTY_LIST;
   }
   

優先使用標準的異常

要儘量追求程式碼的重用,同時減少類載入的數目,提高類裝載的效能。

NullPointerException 在引數值不能為null的情況下引數值為null 丟擲空指標異常

IndexOutOfBoundsException 下標引數值越界 丟擲索引越界異常

ConcurrentModificationException 在禁止併發修改的情況下,檢測到物件的併發修改 丟擲

UnsupportedOperationException 物件不支援使用者請求的方法 丟擲


讓程式碼效能更高

需要 Map 的主鍵和取值時,應該迭代 entrySet()

當迴圈中只需要 Map 的主鍵時,迭代 keySet() 是正確的。但是,當需要主鍵和取值時,迭代 entrySet() 才是更高效的做法,比先迭代 keySet() 後再去 get 取值效能更佳。

反例

Map<String, String> map = ...;
for (String key : map.keySet()) {
    String value = map.get(key);
    ...
}

正例

Map<String, String> map = ...;
for (Map.Entry<String, String> entry : map.entrySet()) {
    String key = entry.getKey();
    String value = entry.getValue();
    ...
}

應該使用Collection.isEmpty()檢測空

使用 Collection.size() 來檢測空邏輯上沒有問題,但是使用 Collection.isEmpty()使得程式碼更易讀,並且可以獲得更好的效能。任何 Collection.isEmpty() 實現的時間複雜度都是 O(1) ,但是某些 Collection.size() 實現的時間複雜度可能是 O(n) 。

反例

if (collection.size() == 0) {
    ...
}

正例

if (collection.isEmpty()) {
    ...
}

如果需要還需要檢測 null ,可採用

CollectionUtils.isEmpty(collection)和CollectionUtils.isNotEmpty(collection)。

不要把集合物件傳給自己

此外,由於某些方法要求引數在執行期間保持不變,因此將集合傳遞給自身可能會導致異常行為。

反例

List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
if (list.containsAll(list)) { // 無意義,總是返回true
    ...
}
list.removeAll(list); // 效能差, 直接使用clear()

集合初始化儘量指定大小

java 的集合類用起來十分方便,但是看原始碼可知,集合也是有大小限制的。每次擴容的時間複雜度很有可能是 O(n) ,所以儘量指定可預知的集合大小,能減少集合的擴容次數。

反例

int[] arr = new int[]{1, 2, 3};
List<Integer> list = new ArrayList<>();
for (int i : arr) {
    list.add(i);
}

正例

int[] arr = new int[]{1, 2, 3};
List<Integer> list = new ArrayList<>(arr.length);
for (int i : arr) {
    list.add(i);
}

字串拼接使用 StringBuilder

一般的字串拼接在編譯期 java 會進行優化,但是在迴圈中字串拼接, java 編譯器無法做到優化,所以需要使用 StringBuilder 進行替換。

反例

String s = "";
for (int i = 0; i < 10; i++) {
    s += i;
}

正例

String a = "a";
String b = "b";
String c = "c";
String s = a + b + c; // 沒問題,java編譯器會進行優化
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) {
    sb.append(i);  // 迴圈中,java編譯器無法進行優化,所以要手動使用StringBuilder
}
```java
# List 的隨機訪問
大家都知道陣列和連結串列的區別:陣列的隨機訪問效率更高。當呼叫方法獲取到 List 後,如果想隨機訪問其中的資料,並不知道該陣列內部實現是連結串列還是陣列,怎麼辦呢?可以判斷它是否實現 RandomAccess 介面。

正例
```java
// 呼叫別人的服務獲取到list
List<Integer> list = otherService.getList();
if (list instanceof RandomAccess) {
    // 內部陣列實現,可以隨機訪問
    System.out.println(list.get(list.size() - 1));
} else {
    // 內部可能是連結串列實現,隨機訪問效率低
}

頻繁呼叫 Collection.contains 方法請使用 Set

在 java 集合類庫中,List 的 contains 方法普遍時間複雜度是 O(n) ,如果在程式碼中需要頻繁呼叫 contains 方法查詢資料,可以先將 list 轉換成 HashSet 實現,將 O(n) 的時間複雜度降為 O(1) 。

反例

ArrayList<Integer> list = otherService.getList();
for (int i = 0; i <= Integer.MAX_VALUE; i++) {
    // 時間複雜度O(n)
    list.contains(i);
}

正例

ArrayList<Integer> list = otherService.getList();
Set<Integer> set = new HashSet(list);
for (int i = 0; i <= Integer.MAX_VALUE; i++) {
    // 時間複雜度O(1)
    set.contains(i);
}

讓程式碼更優雅

長整型常量後新增大寫 L

在使用長整型常量值時,後面需要新增 L ,必須是大寫的 L ,不能是小寫的 l ,小寫 l 容易跟數字 1 混淆而造成誤解。

反例

long value = 1l;
long max = Math.max(1L, 5);

正例

long value = 1L;
long max = Math.max(1L, 5L);

不要使用魔法值

當你編寫一段程式碼時,使用魔法值可能看起來很明確,但在除錯時它們卻不顯得那麼明確了。這就是為什麼需要把魔法值定義為可讀取常量的原因。但是,-1、0 和 1不被視為魔法值。

反例

for (int i = 0; i < 100; i++){
    ...
}
if (a == 100) {
    ...
}

正例

private static final int MAX_COUNT = 100;
for (int i = 0; i < MAX_COUNT; i++){
    ...
}
if (count == MAX_COUNT) {
    ...
}

不要使用集合實現來賦值靜態成員變數

對於集合型別的靜態成員變數,不要使用集合實現來賦值,應該使用靜態程式碼塊賦值。

反例

private static Map<String, Integer> map = new HashMap<String, Integer>() {
    {
        put("a", 1);
        put("b", 2);
    }
};

private static List<String> list = new ArrayList<String>() {
    {
        add("a");
        add("b");
    }
};

正例

private static Map<String, Integer> map = new HashMap<>();
static {
    map.put("a", 1);
    map.put("b", 2);
};

private static List<String> list = new ArrayList<>();
static {
    list.add("a");
    list.add("b");
};

建議使用 try-with-resources 語句

Java 7 中引入了 try-with-resources 語句,該語句能保證將相關資源關閉,優於原來的 try-catch-finally 語句,並且使程式程式碼更安全更簡潔。

反例

private void handle(String fileName) {
    BufferedReader reader = null;
    try {
        String line;
        reader = new BufferedReader(new FileReader(fileName));
        while ((line = reader.readLine()) != null) {
            ...
        }
    } catch (Exception e) {
        ...
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                ...
            }
        }
    }
}

正例

那什麼是try-with-resource呢?簡而言之,當一個外部資源的控制代碼物件(比如FileInputStream物件)實現了AutoCloseable介面,那麼就可以將上面的板式程式碼簡化為如下形式:

private void handle(String fileName) {
    try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
        String line;
        while ((line = reader.readLine()) != null) {
            ...
        }
    } catch (Exception e) {
        ...
    }
}

將外部資源的控制代碼物件的建立放在try關鍵字後面的括號中,當這個try-catch程式碼塊執行完畢後,Java會確保外部資源的close方法被呼叫。程式碼是不是瞬間簡潔許多!

刪除未使用的私有方法和欄位

刪除未使用的私有方法和欄位,使程式碼更簡潔更易維護。若有需要再使用,可以從歷史提交中找回。

反例

public class DoubleDemo1 {
    private int unusedField = 100;
    private void unusedMethod() {
        ...
    }
    public int sum(int a, int b) {
        return a + b;
    }
}

正例

public class DoubleDemo1 {
    public int sum(int a, int b) {
        return a + b;
    }
}

刪除未使用的區域性變數

刪除未使用的區域性變數,使程式碼更簡潔更易維護。

反例

public int sum(int a, int b) {
    int c = 100;
    return a + b;
}

正例

public int sum(int a, int b) {
    return a + b;
}

刪除未使用的方法引數

未使用的方法引數具有誤導性,刪除未使用的方法引數,使程式碼更簡潔更易維護。但是,由於重寫方法是基於父類或介面的方法定義,即便有未使用的方法引數,也是不能刪除的。

反例

public int sum(int a, int b, int c) {
    return a + b;
}

正例

public int sum(int a, int b) {
    return a + b;
}

刪除表示式的多餘括號

對應表示式中的多餘括號,有人認為有助於程式碼閱讀,也有人認為完全沒有必要。對於一個熟悉 Java 語法的人來說,表示式中的多餘括號反而會讓程式碼顯得更繁瑣。

反例

return (x);
return (x + 2);
int x = (y * 3) + 1;
int m = (n * 4 + 2);

正例

return x;
return x + 2;
int x = y * 3 + 1;
int m = n * 4 + 2;

工具類應該遮蔽建構函式

工具類是一堆靜態欄位和函式的集合,不應該被例項化。但是,Java 為每個沒有明確定義建構函式的類添加了一個隱式公有建構函式。所以,為了避免 java “小白”使用有誤,應該顯式定義私有建構函式來遮蔽這個隱式公有建構函式。

反例

public class MathUtils {
    public static final double PI = 3.1415926D;
    public static int sum(int a, int b) {
        return a + b;
    }
}

正例

public class MathUtils {
    public static final double PI = 3.1415926D;
    private MathUtils() {}
    public static int sum(int a, int b) {
        return a + b;
    }
}

刪除多餘的異常捕獲並丟擲

用 catch 語句捕獲異常後,什麼也不進行處理,就讓異常重新丟擲,這跟不捕獲異常的效果一樣,可以刪除這塊程式碼或新增別的處理。

反例

private static String readFile(String fileName) throws IOException {
    try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
        String line;
        StringBuilder builder = new StringBuilder();
        while ((line = reader.readLine()) != null) {
            builder.append(line);
        }
        return builder.toString();
    } catch (Exception e) {
        throw e;
    }
}

正例

private static String readFile(String fileName) throws IOException {
    try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
        String line;
        StringBuilder builder = new StringBuilder();
        while ((line = reader.readLine()) != null) {
            builder.append(line);
        }
        return builder.toString();
    }
}

公有靜態常量應該通過類訪問

雖然通過類的例項訪問公有靜態常量是允許的,但是容易讓人它誤認為每個類的例項都有一個公有靜態常量。所以,公有靜態常量應該直接通過類訪問。

反例

public class User {
    public static final String CONST_NAME = "name";
    ...
}

User user = new User();
String nameKey = user.CONST_NAME;

正例

public class User {
    public static final String CONST_NAME = "name";
    ...
}

String nameKey = User.CONST_NAME;

不要用NullPointerException判斷空

空指標異常應該用程式碼規避(比如檢測不為空),而不是用捕獲異常的方式處理。

反例

public String getUserName(User user) {
    try {
        return user.getName();
    } catch (NullPointerException e) {
        return null;
    }
}

正例

public String getUserName(User user) {
    if (Objects.isNull(user)) {
        return null;
    }
    return user.getName();
}

使用String.valueOf(value)代替””+value
當要把其它物件或型別轉化為字串時,使用 String.valueOf(value) 比””+value 的效率更高。

反例

int i = 1;
String s = "" + i;

正例

int i = 1;
String s = String.valueOf(i);

過時程式碼新增 @Deprecated 註解

當一段程式碼過時,但為了相容又無法直接刪除,不希望以後有人再使用它時,可以新增 @Deprecated 註解進行標記。在文件註釋中新增 @deprecated 來進行解釋,並提供可替代方案

正例

/**
 * 儲存
 *
 * @deprecated 此方法效率較低,請使用{@link newSave()}方法替換它
 */
@Deprecated
public void save(){
    // do something
}

讓程式碼遠離 bug

禁止使用構造方法 BigDecimal(double)

BigDecimal(double) 存在精度損失風險,在精確計算或值比較的場景中可能會導致業務邏輯異常。

反例

BigDecimal value = new BigDecimal(0.1D); // 0.100000000000000005551115...

正例

BigDecimal value = BigDecimal.valueOf(0.1D);; // 0.1

返回空陣列和空集合而不是 null

返回 null ,需要呼叫方強制檢測 null ,否則就會丟擲空指標異常。返回空陣列或空集合,有效地避免了呼叫方因為未檢測 null 而丟擲空指標異常,還可以刪除呼叫方檢測 null 的語句使程式碼更簡潔。

反例

public static Result[] getResults() {
    return null;
}

public static List<Result> getResultList() {
    return null;
}

public static Map<String, Result> getResultMap() {
    return null;
}

public static void main(String[] args) {
    Result[] results = getResults();
    if (results != null) {
        for (Result result : results) {
            ...
        }
    }

    List<Result> resultList = getResultList();
    if (resultList != null) {
        for (Result result : resultList) {
            ...
        }
    }

    Map<String, Result> resultMap = getResultMap();
    if (resultMap != null) {
        for (Map.Entry<String, Result> resultEntry : resultMap) {
            ...
        }
    }
}

正例

public static Result[] getResults() {
    return new Result[0];
}

public static List<Result> getResultList() {
    return Collections.emptyList();
}

public static Map<String, Result> getResultMap() {
    return Collections.emptyMap();
}

public static void main(String[] args) {
    Result[] results = getResults();
    for (Result result : results) {
        ...
    }

    List<Result> resultList = getResultList();
    for (Result result : resultList) {
        ...
    }

    Map<String, Result> resultMap = getResultMap();
    for (Map.Entry<String, Result> resultEntry : resultMap) {
        ...
    }
}

優先使用常量或確定值來呼叫 equals 方法

物件的 equals 方法容易拋空指標異常,應使用常量或確定有值的物件來呼叫 equals 方法。當然,使用 java.util.Objects.equals() 方法是最佳實踐。

反例

public void isFinished(OrderStatus status) {
    return status.equals(OrderStatus.FINISHED); // 可能拋空指標異常
}

正例

public void isFinished(OrderStatus status) {
    return OrderStatus.FINISHED.equals(status);
}

public void isFinished(OrderStatus status) {
    return Objects.equals(status, OrderStatus.FINISHED);
}

列舉的屬性欄位必須是私有不可變

列舉通常被當做常量使用,如果列舉中存在公共屬性欄位或設定欄位方法,那麼這些列舉常量的屬性很容易被修改。理想情況下,列舉中的屬性欄位是私有的,並在私有建構函式中賦值,沒有對應的 Setter 方法,最好加上 final 修飾符。

反例

public enum UserStatus {
    DISABLED(0, "禁用"),
    ENABLED(1, "啟用");

    public int value;
    private String description;

    private UserStatus(int value, String description) {
        this.value = value;
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

正例

public enum UserStatus {
    DISABLED(0, "禁用"),
    ENABLED(1, "啟用");

    private final int value;
    private final String description;

    private UserStatus(int value, String description) {
        this.value = value;
        this.description = description;
    }

    public int getValue() {
        return value;
    }

    public String getDescription() {
        return description;
    }
}

小心String.split(String regex)

字串 String 的 split 方法,傳入的分隔字串是正則表示式!部分關鍵字(比如.| 等)需要轉義

反例

"a.ab.abc".split("."); // 結果為[]
"a|ab|abc".split("|"); // 結果為["a", "|", "a", "b", "|", "a", "b", "c"]

正例

"a.ab.abc".split("\\."); // 結果為["a", "ab", "abc"]
"a|ab|abc".split("\\|"); // 結果為["a", "ab", "abc"]

文章來源