天天看點

Java習慣用法

1.實作equals()

class Person {
    String name;
    int birthYear;
    byte[] raw; 
    public boolean equals(Object obj) {
        if (!obj instanceof Person)
        return false;
        Person other = (Person)obj;
        return name.equals(other.name)&& birthYear == other.birthYear&& Arrays.equals(raw, other.raw);
    }
    public int hashCode() { ... }
}
           

參數必須是Object類型,不能是外圍類。foo.equals(null) 必須傳回false,不能抛NullPointerException。

(注意,null instanceof 任意類 總是傳回false,是以上面的代碼可以運作。)

基本類型域(比如 int)的比較使用 == ,基本類型數組域的比較使用Arrays.equals()。

覆寫equals()時,記得要相應地覆寫 hashCode(),與 equals() 保持一緻。

參考: java.lang.Object.equals(Object)。

2.實作hashCode()

class Person {
    String a;
    Object b;
    byte c;
    int[] d;
    public int hashCode() {
        return a.hashCode() + b.hashCode() + c + Arrays.hashCode(d);
    }
    public boolean equals(Object o) { ... }
}
           

當x和y兩個對象具有x.equals(y) == true ,你必須要確定x.hashCode() == y.hashCode()。

根據逆反命題,如果x.hashCode() != y.hashCode(),那麼x.equals(y) == false 必定成立。

你不需要保證,當x.equals(y) == false時,x.hashCode() != y.hashCode()。但是,如果你可以盡可能地使它成立的話,這會提高哈希表的性能。

hashCode()最簡單的合法實作就是簡單地return 0;雖然這個實作是正确的,但是這會導緻HashMap這些資料結構運作得很慢。

參考:java.lang.Object.hashCode()。

3.實作compareTo()

class Person implements Comparable<Person> {
    String firstName;
    String lastName;
    int birthdate;
    //Compare by firstName, break ties by lastName, finally break ties by birthdate
    public int compareTo(Person other) {
        if (firstName.compareTo(other.firstName) != )
            return firstName.compareTo(other.firstName);
        else if (lastName.compareTo(other.lastName) != )
            return lastName.compareTo(other.lastName);
        else if (birthdate < other.birthdate)
            return -;
        else if (birthdate > other.birthdate)
            return ;
        else
            return ;
    }
}
           

總是實作泛型版本 Comparable 而不是實作原始類型 Comparable 。因為這樣可以節省代碼量和減少不必要的麻煩。隻關心傳回結果的正負号(負/零/正),它們的大小不重要。Comparator.compare()的實作與這個類似。

參考:java.lang.Comparable。

4.實作clone()

class Values implements Cloneable {
    String abc;
    int[] bars;
    Date hired;
    public Values clone() {
        try {
            Values result = (Values)super.clone();
            result.bars = result.bars.clone();
            result.hired = result.hired.clone();
            return result;
        } catch (CloneNotSupportedException e) { // Impossible
            throw new AssertionError(e);
        }
    }
}
           

使用 super.clone() 讓Object類負責建立新的對象。

基本類型域都已經被正确地複制了。同樣,我們不需要去克隆String和BigInteger等不可變類型。

手動對所有的非基本類型域(對象和數組)進行深度複制(deep copy)。

實作了Cloneable的類,clone()方法永遠不要抛CloneNotSupportedException。是以,需要捕獲這個異常并忽略它,或者使用不受檢異常(unchecked exception)包裝它。不使用Object.clone()方法而是手動地實作clone()方法是可以的也是合法的。

參考:java.lang.Object.clone()、java.lang.Cloneable()。

5.使用StringBuilder或StringBuffer

// join(["a", "b", "c"]) -> "a and b and c"
String join(List<String> strs) {
    StringBuilder sb = new StringBuilder();
    boolean first = true;
    for (String s : strs) {
        if (first) first = false;
        else sb.append(" and ");
        sb.append(s);
    }
    return sb.toString();
}
           

不要像這樣使用重複的字元串連接配接:s += item ,因為它的時間效率是O(n^2)。

使用StringBuilder或者StringBuffer時,可以使用append()方法添加文本和使用toString()方法去擷取連接配接起來的整個文本。

優先使用StringBuilder,因為它更快。StringBuffer的所有方法都是同步的,而你通常不需要同步的方法。

參考java.lang.StringBuilder、java.lang.StringBuffer。

6.生成一個範圍内的随機整數

Random rand = newRandom();
// Between  and , inclusive
int diceRoll() {
    return rand.nextInt() + ;
}
           

總是使用Java API方法去生成一個整數範圍内的随機數。不要試圖去使用 Math.abs(rand.nextInt()) % n 這些不确定的用法,因為它的結果是有偏差的。此外,它的結果值有可能是負數,比如當rand.nextInt() == Integer.MIN_VALUE時就會如此。

參考:java.util.Random.nextInt(int)。

7.使用Iterator.remove()

void filter(List<String> list) {
    for (Iterator<String> iter = list.iterator(); iter.hasNext(); ) {
        String item = iter.next();
        if (...)
        iter.remove();
    }
}
           

remove()方法作用在next()方法最近傳回的條目上。每個條目隻能使用一次remove()方法。

參考:java.util.Iterator.remove()。

8.返轉字元串

String reverse(String s) {
    return new StringBuilder(s).reverse().toString();
}
           

參考:java.lang.StringBuilder.reverse()。

9.啟動一條線程

下面的三個例子使用了不同的方式完成了同樣的事情。

實作Runnnable接口的方式:

void startAThread0() {
    new Thread(new MyRunnable()).start();
}
class MyRunnable implements Runnable {
    public void run() {
            ...
    }
}
           

繼承Thread類的方式:

void  startAThread1() {
    new MyThread().start();
}
class MyThread extends Thread {
    public void run() {
    ...
    }
}
           

匿名繼承Thread的方式:

void  startAThread2() {
    new Thread() {
        public void run() {
        ...
        }
    }.start();
}
           

不要直接調用run()方法。總是調用Thread.start()方法,這個方法會建立一條新的線程并使建立的線程調用run()。

參考:java.lang.Thread, java.lang.Runnable。

10.使用try-finally

I/O流例子:

void writeStuff() throws IOException {
    OutputStream out = new FileOutputStream(...);
    try {
        out.write(...);
    } finally {
        out.close();
    }
}
           

鎖例子:

void doWithLock(Lock lock) {
    lock.acquire();
    try {
        ...
    } finally {
        lock.release();
    }
}
           

如果try之前的語句運作失敗并且抛出異常,那麼finally語句塊就不會執行。但無論怎樣,在這個例子裡不用擔心資源的釋放。如果try語句塊裡面的語句抛出異常,那麼程式的運作就會跳到finally語句塊裡執行盡可能多的語句,然後跳出這個方法(除非這個方法還有另一個外圍的finally語句塊)。

11.從輸入流裡讀取位元組資料

InputStream in = (...);
    try {
        while (true) {
            int b = in.read();
            if (b == -)
            break;
            (... process b ...)
        }
    } finally {
        in.close();
    }
           

read()方法要麼傳回下一次從流裡讀取的位元組數(0到255,包括0和255),要麼在達到流的末端時傳回-1。

參考:java.io.InputStream.read()。

12.從輸入流裡讀取塊資料

InputStream in = (...);
    try {
        byte[] buf = new byte[];
        while (true) {
            int n = in.read(buf);
            if (n == -)
            break;
            (... process buf with offset= and length=n ...)
        }
    } finally {
        in.close();
    }
           

要記住的是,read()方法不一定會填滿整個buf,是以你必須在處理邏輯中考慮傳回的長度。

參考: java.io.InputStream.read(byte[])、java.io.InputStream.read(byte[], int, int)。