Effective java笔记-对于所有对象都通用的方法

对于所有对象都通用的方法

第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));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值