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)。