Effective java筆記-對於所有物件都通用的方法
阿新 • • 發佈:2019-01-22
對於所有物件都通用的方法
第8條 覆蓋equals時請遵守通用約定
滿足以下任何一個條件,可以不覆蓋equals:
類的每個例項本質上都是唯一的。(如Thread)
不關心是否提供了“邏輯相等”的測試功能(如Random)
超類已經覆蓋了equals,從超類繼承過來的行為對於子類也是合適的。(如Set)
類是私有的或包級私有的,可以確定他的equals方法永遠不會被呼叫
當類有自己的邏輯相等的概念且父類沒有實現期望的功能時要覆蓋equals,且覆蓋之後還可以使這個“類的值”可以作為key,一般這種類都是一些“值類”,如String,Integer。不過有一種“值類”不需要覆蓋equals,就是例項受控的類,這種類只會存在一個物件,所以物件相等和值相等就是一回事了。 覆蓋equals方法要遵守一些規範(equals方法實現了等價關係): 自反性,對稱性,傳遞性,一致性(非null x,y只要equals的比較操作在物件中所用的資訊沒有被修改,則多次呼叫x。equals(y)返回值時一致的),x.equals(null)必需返回false(x非null)
對稱性例子:
// Broken - violates symmetry! - Pages 36-37
package org.effectivejava.examples.chapter03.item08;
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
if (s == null)
throw new NullPointerException();
this .s = s;
}
// Broken - violates symmetry!
@Override
public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
if (o instanceof String) // One-way interoperability!
return s.equalsIgnoreCase((String) o);
return false;
}
// This version is correct.
// @Override public boolean equals(Object o) {
// return o instanceof CaseInsensitiveString &&
// ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
// }
public static void main(String[] args) {
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
System.out.println(cis.equals(s) + " " + s.equals(cis));
}
}
傳遞性例子:
// Simple immutable two-dimensional integer point class - Page 37
package org.effectivejava.examples.chapter03.item08;
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point) o;
return p.x == x && p.y == y;
}
// See Item 9
@Override
public int hashCode() {
return 31 * x + y;
}
}
上面程式碼定義了一個Point類,它們的邏輯相等條件是x,y相等,現在再定義一個ColorPoint,繼承自Point:
public enum Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
// Attempting to add a value component to Point - Pages 37 - 38
package org.effectivejava.examples.chapter03.item08;
public class ColorPoint extends Point {
private final Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
// Broken - violates symmetry!
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}
public static void main(String[] args) {
// First equals function violates symmetry
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);
System.out.println(p.equals(cp) + " " + cp.equals(p));
}
}
你會發現這樣違反了對稱性,改成這樣,又會違反傳遞性
// Broken - violates transitivity!
@Override public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
If o is a normal Point, do a color-blind comparison
if (!(o instanceof ColorPoint))
return o.equals(this);
o is a ColorPoint; do a full comparison
return super.equals(o) && ((ColorPoint)o).color == color;
}
// Second equals function violates transitivity
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
System.out.printf("%s %s %s%n", p1.equals(p2), p2.equals(p3),
p1.equals(p3));
你可能這樣實現Point的equals方法:
// Broken - violates Liskov substitution principle - Pages 39-40
// @Override public boolean equals(Object o) {
// if (o == null || o.getClass() != getClass())
// return false;
// Point p = (Point) o;
// return p.x == x && p.y == y;
// }
這樣看起來還可以,但結果是不可接受的,假設我們通過不新增值元件的方式擴充套件了Point
// Trivial subclass of Point - doesn't add a value component - Page 39
package org.effectivejava.examples.chapter03.item08;
import java.util.concurrent.atomic.AtomicInteger;
public class CounterPoint extends Point {
private static final AtomicInteger counter = new AtomicInteger();
public CounterPoint(int x, int y) {
super(x, y);
counter.incrementAndGet();
}
public int numberCreated() {
return counter.get();
}
}
假設又有一個方法是用來判斷某個Point是否是在單位圓中的整值點:
// Test program that uses CounterPoint as Point
package org.effectivejava.examples.chapter03.item08;
import java.util.HashSet;
import java.util.Set;
public class CounterPointTest {
// Initialize UnitCircle to contain all Points on the unit circle
private static final Set<Point> unitCircle;
static {
unitCircle = new HashSet<Point>();
unitCircle.add(new Point(1, 0));
unitCircle.add(new Point(0, 1));
unitCircle.add(new Point(-1, 0));
unitCircle.add(new Point(0, -1));
}
public static boolean onUnitCircle(Point p) {
return unitCircle.contains(p);
}
//里氏替換法則認為一個型別的任何重要屬性也將適用於其子型別,因此為該方法定義的方法也應該在子型別上執行地很好,可是:
public static void main(String[] args) {
Point p1 = new Point(1, 0);
Point p2 = new CounterPoint(1, 0);
// Prints true
System.out.println(onUnitCircle(p1));
// Should print true, but doesn't if Point uses getClass-based equals
System.out.println(onUnitCircle(p2));
}
}
到這裡就說明了Point不能使用基於getClass地equals方法,那這樣ColorPoint又怎麼辦呢,也就是說如何即擴充套件不可例項化的(?)類,又增加值元件(ColorPoint增加了值元件,CounterPointTest沒有增加),有一個權宜之計:使用組合,以下是最終的完整解決方案:
// Simple immutable two-dimensional integer point class - Page 37
package org.effectivejava.examples.chapter03.item08.composition;
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point) o;
return p.x == x && p.y == y;
}
// See Item 9
@Override
public int hashCode() {
return 31 * x + y;
}
}
package org.effectivejava.examples.chapter03.item08.composition;
public enum Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
// Adds a value component without violating the equals contract - Page 40
package org.effectivejava.examples.chapter03.item08.composition;
public class ColorPoint {
private final Point point;
private final Color color;
public ColorPoint(int x, int y, Color color) {
if (color == null)
throw new NullPointerException();
point = new Point(x, y);
this.color = color;
}
/**
* Returns the point-view of this color point.
*/
public Point asPoint() {
return point;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint) o;
return cp.point.equals(point) && cp.color.equals(color);
}
@Override
public int hashCode() {
return point.hashCode() * 33 + color.hashCode();
}
}
一致性:不要使equals方法依賴於不可靠的資源。java.net.URL就依賴於URL中主機IP地址的比較,這違反了一致性,不過因為相容性的需要無法改變這個行為。
覆蓋equals時總是覆蓋hashCode方法;
不要將equals宣告中的Object物件替換為其他型別
第九條 覆蓋equals時總是覆蓋hashCode
覆蓋equals時如果不覆蓋hashCode就會導致該類無法結合所有基於雜湊的集合一起工作。
Object規範:
1.equals基於的比較資訊不變則hashCode也不能變
2.x.equals(y)==true,則x.hashCode()==y.hashCode()
3.x.equals(y)==false,x.hashCode()和y.hashCode()不一定不同,但要知道盡可能使得兩個不相等(equals返回false)的物件的hashCode不同可提高散列表效能
package org.effectivejava.examples.chapter03.item09;
// Shows the need for overriding hashcode when you override equals - Pages 45-46
import java.util.HashMap;
import java.util.Map;
public final class PhoneNumber {
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber(int areaCode, int prefix, int lineNumber) {
rangeCheck(areaCode, 999, "area code");
rangeCheck(prefix, 999, "prefix");
rangeCheck(lineNumber, 9999, "line number");
this.areaCode = (short) areaCode;
this.prefix = (short) prefix;
this.lineNumber = (short) lineNumber;
}
private static void rangeCheck(int arg, int max, String name) {
if (arg < 0 || arg > max)
throw new IllegalArgumentException(name + ": " + arg);
}
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber) o;
return pn.lineNumber == lineNumber && pn.prefix == prefix
&& pn.areaCode == areaCode;
}
// Broken - no hashCode method!
// A decent hashCode method - Page 48
// @Override public int hashCode() {
// int result = 17;
// result = 31 * result + areaCode;
// result = 31 * result + prefix;
// result = 31 * result + lineNumber;
// return result;
// }
// Lazily initialized, cached hashCode - Page 49
// private volatile int hashCode; // (See Item 71)
//
// @Override public int hashCode() {
// int result = hashCode;
// if (result == 0) {
// result = 17;
// result = 31 * result + areaCode;
// result = 31 * result + prefix;
// result = 31 * result + lineNumber;
// hashCode = result;
// }
// return result;
// }
public static void main(String[] args) {
Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");
System.out.println(m.get(new PhoneNumber(707, 867, 5309)));
}
}
在雜湊碼的計算過程中,可以把冗餘域排除在外(冗餘域就是可以通過其他域的值計算出來的),必需排除equals比較計算中沒有用到的域,否則會違反上面第二條。
第十條 始終要覆蓋toString
比較簡單,直接給個例子好了:
// Adding a toString method to PhoneNumber - page 52
package org.effectivejava.examples.chapter03.item10;
import java.util.HashMap;
import java.util.Map;
public final class PhoneNumber {
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber(int areaCode, int prefix, int lineNumber) {
rangeCheck(areaCode, 999, "area code");
rangeCheck(prefix, 999, "prefix");
rangeCheck(lineNumber, 9999, "line number");
this.areaCode = (short) areaCode;
this.prefix = (short) prefix;
this.lineNumber = (short) lineNumber;
}
private static void rangeCheck(int arg, int max, String name) {
if (arg < 0 || arg > max)
throw new IllegalArgumentException(name + ": " + arg);
}
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber) o;
return pn.lineNumber == lineNumber && pn.prefix == prefix
&& pn.areaCode == areaCode;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumber;
return result;
}
/**
* Returns the string representation of this phone number. The string
* consists of fourteen characters whose format is "(XXX) YYY-ZZZZ", where
* XXX is the area code, YYY is the prefix, and ZZZZ is the line number.
* (Each of the capital letters represents a single decimal digit.)
*
* If any of the three parts of this phone number is too small to fill up
* its field, the field is padded with leading zeros. For example, if the
* value of the line number is 123, the last four characters of the string
* representation will be "0123".
*
* Note that there is a single space separating the closing parenthesis
* after the area code from the first digit of the prefix.
*/
@Override
public String toString() {
return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber);
}
public static void main(String[] args) {
Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");
System.out.println(m);
}
}
第十一條 謹慎地覆蓋clone
Cloneable介面的目的是作為物件的一個mixin介面(見18條),表明這樣的物件允許克隆。
既然Cloneable並沒有包含任何方法,那他到底有什麼用呢:他會改變Object中受保護的clone方法,如果一個類實現了Cloneable,Object的clone方法就返回該物件的逐域拷貝,否則就會丟擲CloneNotSupportedException異常,這是介面的一種極端非典型用法,不值得效仿。
還有Object的clone方法是protected的,那我們要怎麼用,只要子類繼承,並將子類中的clone改為public就可。(我們一般會在子類的clone中呼叫super。clone,後面會說)
Colne方法的通用約定是非常弱的:
一般:x.clone()!=x為true;x.clone().getClass()==x.getClass() 為true;x。clone().equals(x)為true;這些不是絕對的要求。
還有要求這個過程中沒有呼叫構造器。
這個約定存在幾個問題:
1.不呼叫構造器太強硬了
2.x.clone().getClass()通常應該等於x.getClass()又太軟弱了(??)
在實踐中,程式設計師會假設:如果它們擴充套件了一個類,並且從子類中呼叫了super.clone(),返回的物件將是該子類的例項。超類能夠提供這種功能的唯一途徑是,返回一個通過呼叫super.clone而得到的物件。如果clone方法返回一個由構造器建立的物件,他就得到有錯誤的類。如果所有的超類都遵守這條規則,那麼呼叫super.clone最終會呼叫Object的clone方法,從而創建出正確類的例項。(書上這裡沒怎麼看懂,但可以知道的是,要遵守規則:返回的物件是通過super.clone建立的,而不是自己呼叫構造器建立的)
例子:
// Adding a clone method to PhoneNumber - page 55
package org.effectivejava.examples.chapter03.item11;
import java.util.HashMap;
import java.util.Map;
public final class PhoneNumber implements Cloneable {
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber(int areaCode, int prefix, int lineNumber) {
rangeCheck(areaCode, 999, "area code");
rangeCheck(prefix, 999, "prefix");
rangeCheck(lineNumber, 9999, "line number");
this.areaCode = (short) areaCode;
this.prefix = (short) prefix;
this.lineNumber = (short) lineNumber;
}
private static void rangeCheck(int arg, int max, String name) {
if (arg < 0 || arg > max)
throw new IllegalArgumentException(name + ": " + arg);
}
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber) o;
return pn.lineNumber == lineNumber && pn.prefix == prefix
&& pn.areaCode == areaCode;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumber;
return result;
}
/**
* Returns the string representation of this phone number. The string
* consists of fourteen characters whose format is "(XXX) YYY-ZZZZ", where
* XXX is the area code, YYY is the prefix, and ZZZZ is the line number.
* (Each of the capital letters represents a single decimal digit.)
*
* If any of the three parts of this phone number is too small to fill up
* its field, the field is padded with leading zeros. For example, if the
* value of the line number is 123, the last four characters of the string
* representation will be "0123".
*
* Note that there is a single space separating the closing parenthesis
* after the area code from the first digit of the prefix.
*/
@Override
public String toString() {
return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber);
}
@Override
public PhoneNumber clone() {
try {
return (PhoneNumber) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // Can't happen
}
}
public static void main(String[] args) {
PhoneNumber pn = new PhoneNumber(707, 867, 5309);
Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
m.put(pn, "Jenny");
System.out.println(m.get(pn.clone()));
}
}
物件中有可變的物件時clone,例子:
// A cloneable version of Stack - Pages 56-57
package org.effectivejava.examples.chapter03.item11;
import java.util.Arrays;
public class Stack implements Cloneable {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
public boolean isEmpty() {
return size == 0;
}
@Override
public Stack clone() {
try {
Stack result = (Stack) super.clone();
result.elements = elements.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
// Ensure space for at least one more element.
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
// To see that clone works, call with several command line arguments
public static void main(String[] args) {
Stack stack = new Stack();
for (String arg : args)
stack.push(arg);
Stack copy = stack.clone();
while (!stack.isEmpty())
System.out.print(stack.pop() + " ");
System.out.println();
while (!copy.isEmpty())
System.out.print(copy.pop() + " ");
}
}
實際上,clone方法就是另一個構造器;你必須確保它不會傷害到原始的物件,並確保正確地建立被克隆物件中的約束條件(invariant)
如果elements域是final的,上述方案就不能正常工作,因為clone方法是被禁止給elements域賦新值。這是個根本的問題:clone架構與引用可變物件的final域的正常用法是不相相容的,除非在原始物件和克隆物件之間安全地共享此可變物件。
有時候這樣還要這樣:
public class HashTable implements Cloneable{
private Entry[] buckets = ...;
private static class Entry{
final Object key;
Object value;
Entry next;
Entry(Object key,Object value,Entrt next){
this.key = key;
this.value=value;
this.next = next;
}
protected Object deepCopy() {
return new Entry(hash, key, value,
(next==null ? null : next,deepCopy()));
}
}
public HashTable clone(){
try{
HasbTable result = (HasbTable)super.clone();
result.buckets = new Entry[buckets.length];
for(int i=0;i<buckets.length;i++)
if(buckets[i]!=null)
result.buckets[i]=buckets[i].deepCopy();
return result;
}catch(。。。){
。。。
}
}
}
不過看了這段程式碼,到不知道為什麼上面那個類不用deepCopy呢???
和構造器一樣,clone方法不應該在構造地過程中,呼叫新物件中任何非final的方法。如果clone呼叫了一個被覆蓋的方法,那麼在該翻噶發所在的子類有機會修正他在克隆物件的狀態前,該方法就會被執行,這樣可能導致克隆物件和原始物件之間的不一致。
Object的clone方法被宣告為可丟擲CloneNotSupportedException,公有的clone應該省略這個宣告(為了好用),如果專門為了繼承而設計的類覆蓋了clone方法,這個clone方法就應該宣告為protected和可丟擲CloneNotSupportedException,並且不實現Cloneable。
實現拷貝還可以用拷貝構造器或拷貝工廠:
拷貝構造器只是一個構造器,它唯一的引數型別是包含該構造器的類,如public Yum(Yum yum);
拷貝工廠是類似於拷貝構造器的靜態工廠,如:public static Yum newInstance(Yum yum);
拷貝構造器和拷貝工廠比clone方法有更多優勢
而且使用拷貝構造器或者拷貝工廠來代替clone方法時,並沒有放棄介面的功能特性,更進一步,拷貝構造器或者拷貝工廠可以帶一個引數,引數型別是通過該類實現的介面。例如所有通用集合實現都提供了一個拷貝構造器,它的引數型別為Collection或Map。基於介面的拷貝構造器或拷貝工廠(更準確地叫法是轉換構造器和轉換工廠),允許客戶選擇拷貝地實現型別,而不是強迫客戶接受原始地實現型別。例如:假設你有一個HashSet,並且希望把它拷貝成一個TreeSet可以使用轉換構造器:new TreeSet(s).
由於clone方法具有那麼多缺點,有些專家級的程式設計師乾脆從來不去覆蓋clone方法,也從來不去呼叫它,除非拷貝陣列。
第十二條 考慮實現Comparable介面
下面的程式依賴於String實現了Comparable介面,它去掉了命令列引數列表中的重複引數,並按字母順序打印出來:
public class WordList {
public static void main(String[] args) {
Set<String> s = new TreeSet<String>();
Collections.addAll(s, args);
System.out.println(s);
}
}
java平臺類庫中所有值類(value classes)都實現了Comparable介面
規範:
1.sgn(x.compareTo(y))==-sgn(y.compareTo(x))為true
2.(x.compareTo(y)>0&&y.compareTo(z)>0)=>x.compareTo(z)>0
3.x.compareTo(y)==0=>所有z滿足sgn(x.compareTo(z))==sgn(y.compareTo(z))
4.強烈建議:(x.compareTo(y)==0)==(x.equals(y)),不過這並非絕對必要,一般來說若違反這條規定,應當說明,推薦這樣的說法:“注意:該類具有內在的排序功能,但是與equals不一致(inconsistent with equals)“
針對equals的的權宜之計也同樣適用於compareTo方法。如果你想為一個實現了Compareable介面的類增加值元件,請不要擴充套件這個類,而是要編寫一個不相關的類,其中包含第一個類的一個例項。然後提供一個檢視(view)方法返回這個例項。這樣既可以讓你自由地在第二個類上實現compareTo方法,同時也允許它的客戶端在必要的時候,把第二個類的例項視同第一個類的例項
關於第四條建議:
如果一個類的equals和compareTo不一致,如果有一個有序集合包含這個類的元素,那這個集合就無法遵守相關集合介面(Collection,Set,Map)的通用約定,因為對於這些介面的約定是根據equals來定義的,而有序集合使用了compareTo的等同性測試,舉個例子:
BigDecimal的equals和compareTo不一致,如果你將new BigDecimal("1.0")和new BigDecimal("1.00")到HashSet中,這個HashSet將包含兩個元素,但如果將它們add入TreeSet中,TreeSet將只包含一個元素。
一種compareTo實現方式:
// Making PhoneNumber comparable - Pages 65-66
package org.effectivejava.examples.chapter03.item12;
import java.util.NavigableSet;
import java.util.Random;
import java.util.TreeSet;
public final class PhoneNumber implements Cloneable, Comparable<PhoneNumber> {
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber(int areaCode, int prefix, int lineNumber) {
rangeCheck(areaCode, 999, "area code");
rangeCheck(prefix, 999, "prefix");
rangeCheck(lineNumber, 9999, "line number");
this.areaCode = (short) areaCode;
this.prefix = (short) prefix;
this.lineNumber = (short) lineNumber;
}
private static void rangeCheck(int arg, int max, String name) {
if (arg < 0 || arg > max)
throw new IllegalArgumentException(name + ": " + arg);
}
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber) o;
return pn.lineNumber == lineNumber && pn.prefix == prefix
&& pn.areaCode == areaCode;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumber;
return result;
}
/**
* Returns the string representation of this phone number. The string
* consists of fourteen characters whose format is "(XXX) YYY-ZZZZ", where
* XXX is the area code, YYY is the prefix, and ZZZZ is the line number.
* (Each of the capital letters represents a single decimal digit.)
*
* If any of the three parts of this phone number is too small to fill up
* its field, the field is padded with leading zeros. For example, if the
* value of the line number is 123, the last four characters of the string
* representation will be "0123".
*
* Note that there is a single space separating the closing parenthesis
* after the area code from the first digit of the prefix.
*/
@Override
public String toString() {
return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber);
}
@Override
public PhoneNumber clone() {
try {
return (PhoneNumber) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // Can't happen
}
}
// Works fine, but can be made faster
// public int compareTo(PhoneNumber pn) {
// // Compare area codes
// if (areaCode < pn.areaCode)
// return -1;
// if (areaCode > pn.areaCode)
// return 1;
//
// // Area codes are equal, compare prefixes
// if (prefix < pn.prefix)
// return -1;
// if (prefix > pn.prefix)
// return 1;
//
// // Area codes and prefixes are equal, compare line numbers
// if (lineNumber < pn.lineNumber)
// return -1;
// if (lineNumber > pn.lineNumber)
// return 1;
//
// return 0; // All fields are equal
// }
public int compareTo(PhoneNumber pn) {
// Compare area codes
int areaCodeDiff = areaCode - pn.areaCode;
if (areaCodeDiff != 0)
return areaCodeDiff;
// Area codes are equal, compare prefixes
int prefixDiff = prefix - pn.prefix;
if (prefixDiff != 0)
return prefixDiff;
// Area codes and prefixes are equal, compare line numbers
return lineNumber - pn.lineNumber;
}
public static void main(String[] args) {
NavigableSet<PhoneNumber> s = new TreeSet<PhoneNumber>();
for (int i = 0; i < 10; i++)
s.add(randomPhoneNumber());
System.out.println(s);
}
private static final Random rnd = new Random();
private static PhoneNumber randomPhoneNumber() {
return new PhoneNumber((short) rnd.nextInt(1000),
(short) rnd.nextInt(1000), (short) rnd.nextInt(10000));
}
}