如果你建立了一個
Point
類別:
public class Point {
public Integer x;
public Integer y;
public Point(Integer x, Integer y) {
this.x = x;
this.y = y;
}
}
並且用它來產生了一些 Point
實例並收集起來,然後在某個時候,打算顯示一下目前收集了哪個點:
List<Point> points = Arrays.asList(new Point(1, 1), new Point(2, 2));
out.println(points);
執行結果只會顯示[guavademo.Point@139a55, guavademo.Point@1db9742]這種資訊,真的是沒什麼用,這就是為什麼你要定義 toString
方法。現代 IDE 有些會支援直接產生 toString
,例如可以用 NetBeans 的 Insert Code 來產生如下的 toString
:
public class Point {
...
@Override
public String toString() {
return "Point{" + "x=" + x + ", y=" + y + '}';
}
}
IDE 幫你產生是不錯,不過要自己寫這些就不怎麼高興了,而且讀來也不怎麼好讀,改用 String.format
會好一些些:
public class Point {
...
@Override
public String toString() {
return String.format("Point{x=%d, y=%d}", x, y);
}
}
不過自己做字串格式化終究還是蠻麻煩的,你可以改用 Guava 的 Objects.toStringHelper
試試:
import com.google.common.base.Objects;
public class Point {
...
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("x", x)
.add("y", y)
.toString();
}
}
除了產生 toString
的幫手之外,Guava 在比較物件時也提供了 Objects.equal
,這東西與 JDK7 的 Objects.equals
是相同作用的,如果你使用 JDK6 或之前的版本,則可以試試 Guava 的。怎麼用呢?因為 Objects.equal
蠻簡單的,單純解釋它沒意思,重點還是在於怎麼寫出正確的 equals
比較重要,那藉這個機會重新闡述一下 物件相等性(上) 這篇文章裏頭的一些東西好了,將裏頭第二個 Point
類別的定義改寫如下:
import com.google.common.base.Objects;
public class Point {
public Integer x;
public Integer y;
public Point(Integer x, Integer y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("x", x)
.add("y", y)
.toString();
}
@Override
public boolean equals(Object that) {
if(that instanceof Point) {
Point p = (Point) that;
return x.equals(p.x) && y.equals(p.y);
}
return false;
}
}
不過,這個 equals
並不安全,如果 x
或 y
是 null
的話,那麼就會噴出 NullPointerException
了,自行加些 x
與 y
是否為 null
的檢查是可以,不過我知道有 Objects.equal
可以協助,為什麼不拿來用?
...
public class Point {
...
@Override
public boolean equals(Object that) {
if(that instanceof Point) {
Point p = (Point) that;
return Objects.equal(x, p.x) && Objects.equal(y, p.y);
}
return false;
}
}
Object.equal
的原始碼很簡單,會幫你判斷參考與 null
:
...
public static boolean equal(@Nullable Object a, @Nullable Object b) {
return a == b || (a != null && a.equals(b));
}
...
你可以使用目前的 Point
做相等性測試看看,像是 new Point(1, 1).equals(new Point(1, 1))
,結果應該會是 true
,那如果是以下的程式碼呢?
Point p1 = new Point(1, 1);
Point p2 = new Point(1, 1);
Set<Point> pSet = new HashSet<>();
pSet.add(p1);
out.println(pSet.contains(p2)); // 可能顯示 false
如果上例結果顯示 false
,並不用訝異,因為你在重新定義 equals
時,並沒有重新定義 hashCode
。在許多場合,例如將物件加入群集 (Collection)時,會同時利用 equals
與 hashCode
來判斷是否加入的是(實質上)相同的物件。在 Object 的 hashCode() 說明 指出:
- 在同一個應用程式執行期間,對同一物件呼叫
hashCode
方法,必須回傳相同的整數結果。 - 如果兩個物件使用
equals(Object)
測試結果為相等, 則這兩個物件呼叫hashCode
時,必須獲得相同的整數結果。 - 如果兩個物件使用
equals(Object)
測試結果為不相等, 則這兩個物件呼叫hashCode
時,可以獲得不同的整數結果。
HashSet
為例,會先使用 hashCode
得出該將物件放至哪個雜湊桶(Hash buckets)中,如果雜湊桶有物件,再進一步使用 equals
確定實質相等性,從而確定 Set
中不會有重複的物件。上例中說可能會顯示false
,是因為若湊巧物件 hashCode
算出在同一個雜湊桶,再進一步用 equals
就有可能出現 true
。在重新定義
equals
時,最好重新一併重新定義 hashCode
。只是 hashCode
該怎麼算呢?算出來的雜湊碼最好是儘量別重複,以免引起雜湊碰撞(Hash collision),過多的雜湊碰撞可能會有效能問題,甚至增加 hash collision dos 的可能性。IDE 產生的
hashCode
通常比較簡單,例如 物件相等性(上) 中的 hashCode
實作,是舊版 NetBeans IDE 自動產生的程式碼:
...
@Override
public int hashCode() {
return 41 * (41 + x) + y;
}
...
如果使用 JDK7,那麼可以用 Objects.hash
來協助產生,如果是 JDK6 或先前版本,則可以使用 Guava 的 Objects.hashCode
:
...
@Override
public int hashCode() {
return Objects.hashCode(x, y);
}
...
實際上目前版本的 Guava 只是用了 JDK5 就有的 Arrays
上 hashCode
方法而已:
...
public static int hashCode(@Nullable Object... objects) {
return Arrays.hashCode(objects);
}
...
所以實際上,你應該看看 Arrays
的 hashCode
上各個重載方法,瞭解它產生的 hashCode
是不是符合你的需求,就算你不使用 Guava,也不是在 JDK7 以上的版本,也知道可否使用 Arrays
的 hashCode
為你產生適當的雜湊碼。
再次做剛剛的測試就會得到 true
了:
Point p1 = new Point(1, 1);
Point p2 = new Point(1, 1);
Set<Point> pSet = new HashSet<>();
pSet.add(p1);
out.println(pSet.contains(p2)); // true
如果你沒瞭解過 equals
與 hashCode
撰寫時,需要注意哪些事項,建議你繼續看看 物件相等性(上) 這篇文章,你也可以試著用 Guava 來簡化該篇文章的範例。當然,你也可以讓 IDE 結合 Guava 來產生
equals
、hashCode
與 toString
,如果你使用 IntelliJ IDEA,可以參考一下 IntelliJ IDEA: Generate equals, hashCode and toString with Google Guava。
新版的 NetBeans 本身如果在 JDK7 平台上,產生的 equals
與 hashCode
已經運用了 JDK7 的 Objects.equals
與 Objects.hashCode
,你可以看看是不是你想要的。