在Java編程中,有些知識 並不能僅通過語言規範或者標準API文檔就能學到的。在本文中,我會盡量收集一些最常用的習慣用法,特別是很難猜到的用法。(Joshua Bloch的《Effective Java》對這個話題給出了更詳盡的論述,可以從這本書裡學習更多的用法。)
我把本文的所有代碼都放在公用場所裡。你可以根據自己的喜好去複製和修改任意的程式碼片段,不需要任何的憑證。 目錄 實現: equals() hashCode() compareTo() clone() 應用: StringBuilder/StringBuffer Random.nextInt(int) Iterator.remove() StringBuilder.reverse() Thread/Runnable try-finally 輸入/輸出: 從輸入資料流裡讀取位元組資料 從輸入資料流裡讀取塊資料 從檔案裡讀取文本 向檔案裡寫文本 預防性檢測: 數值 對象 數組索引 數組區間 數組: 填充元素 複製一個範圍內的數組元素 調整數組大小 封裝 個位元組封裝成一個int 分解成4個位元組
實現equals()
class Person { String name; int birthYear; byte[] raw; public boolean equals(Object obj) { if(!obj instanceofPerson) 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)。
實現hashCode()
class Person { String a; Object b; bytec; 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()。
實現compareTo()
class Person implementsComparable<Person> { String firstName; String lastName; intbirthdate; // Compare by firstName, break ties by lastName, finally break ties by birthdate public int compareTo(Person other) { if(firstName.compareTo(other.firstName) != 0) returnfirstName.compareTo(other.firstName); else if (lastName.compareTo(other.lastName) != 0) returnlastName.compareTo(other.lastName); else if (birthdate < other.birthdate) return-1; else if (birthdate > other.birthdate) return1; else return0; }}
總是實現泛型版本 Comparable 而不是實現原始類型 Comparable 。因為這樣可以節省代碼量和減少不必要的麻煩。 只關心返回結果的加號或減號(負/零/正),它們的大小不重要。 Comparator.compare()的實現與這個類似。 參考:java.lang.Comparable。
實現clone()
class Values implementsCloneable { String abc; doublefoo; int[] bars; Date hired; public Values clone() { try{ Values result = (Values)super.clone(); result.bars = result.bars.clone(); result.hired = result.hired.clone(); returnresult; }catch(CloneNotSupportedException e) { // Impossible thrownew AssertionError(e); } }} 使用 super.clone() 讓Object類負責建立新的對象。 基本類型域都已經被正確地複製了。同樣,我們不需要去複製String和BigInteger等不可變類型。 手動對所有的非基本類型域(對象和數組)進行深度複製(deep copy)。 實現了Cloneable的類,clone()方法永遠不要拋CloneNotSupportedException。因此,需要捕獲這個異常並忽略它,或者使用不受檢異常(unchecked exception)封裝它。 不使用Object.clone()方法而是手動地實現clone()方法是可以的也是合法的。 參考:java.lang.Object.clone()、java.lang.Cloneable()。
使用StringBuilder或StringBuffer
// join(["a", "b", "c"]) -> "a and b and c"String join(List<String> strs) { StringBuilder sb = newStringBuilder(); booleanfirst = true; for(String s : strs) { if(first) first = false; elsesb.append(" and "); sb.append(s); } returnsb.toString();} 不要像這樣使用重複的字串串連:s += item ,因為它的時間效率是O(n^2)。 使用StringBuilder或者StringBuffer時,可以使用append()方法添加文本和使用toString()方法去擷取串連起來的整個文本。 優先使用StringBuilder,因為它更快。StringBuffer的所有方法都是同步的,而你通常不需要同步的方法。 參考java.lang.StringBuilder、java.lang.StringBuffer。
產生一個範圍內的隨機整數
Random rand = newRandom(); // Between 1 and 6, inclusiveintdiceRoll() { return rand.nextInt(6) + 1;} 總是使用Java API方法去產生一個整數範圍內的隨機數。 不要試圖去使用 Math.abs(rand.nextInt()) % n 這些不確定的用法,因為它的結果是有偏差的。此外,它的結果值有可能是負數,比如當rand.nextInt() == Integer.MIN_VALUE時就會如此。 參考:java.util.Random.nextInt(int)。
使用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()。
返轉字串
String reverse(String s) { return new StringBuilder(s).reverse().toString();} 這個方法可能應該加入Java標準庫。 參考:java.lang.StringBuilder.reverse()。
啟動一條線程
下面的三個例子使用了不同的方式完成了同樣的事情。
實現Runnnable的方式:
void startAThread0() { new Thread(newMyRunnable()).start();} class MyRunnable implementsRunnable { public void run() { ... }}
繼承Thread的方式:
void startAThread1() { new MyThread().start();} class MyThread extendsThread { public void run() { ... }}
匿名繼承Thread的方式:
void startAThread2() { new Thread() { publicvoid run() { ... } }.start();} 不要直接調用run()方法。總是調用Thread.start()方法,這個方法會建立一條新的線程並使建立的線程調用run()。 參考:java.lang.Thread, java.lang.Runnable。
使用try-finally
I/O流例子:
void writeStuff() throwsIOException { OutputStream out = newFileOutputStream(...); try{ out.write(...); }finally{ out.close(); }}
鎖例子:
void doWithLock(Lock lock) { lock.acquire(); try{ ... }finally{ lock.release(); }}