Effective Java笔记第二章对所有对象都通用的方法第五节考虑实现Comparable接口

Effective Java笔记第二章对所有对象都通用的方法

第五节考虑实现Comparable接口

1.compareTo方法并没有在Object中声明,相反,他是Comparable接口中唯一的方法。compareTo方法不但允许进行简单的等同性比较,而且允许执行顺序比较,除此之外他与Object的equals方法具有相似的特征,他还是个泛型。

2.一旦实现了Comparable接口,他就可以和许多泛型算法以及依赖于该接口的集合实现进行协作,付出很小的努力就可以获得非常强大的功能。事实上,Java平台类库中的所有值类都实现了Comparable接口。如果你正在编写一个值类(类具有自己特有的"逻辑相等"概念,值类仅仅是一个表示值的类,如Integer或者Date),它具有非常明显的内在排序关系,比如字母排序,按数值顺序或者年代顺序,那你就应该解决考虑实现Comparable这个接口:

public interface Comparable<T> {
    public int compareTo(T o);
}

3.compareTo方法的通用约定与equals方法的相似:
将这个对象与指定的对象进行比较,当该对象小于,等于或者大于指定对象的时候,分别返回一个负整数,零和正整数。如果由于指定对象的类型而无法与该对象进行比较,则抛出ClassCastException异常。

4.在下面的说明中,符号sgn(表达式)表示数学中的signum函数,他根据表达式的值为负值,零和正值,分别返回-1,0和1。
1)实现者必须确保所有的x和y都满足sgn(x.compareTo(y))==-sgn(y.compareTo(x)),这实际也暗示着,当且仅当x.compareTo(y)抛出异常时, y.compareTo(x)也必须抛出异常。
2)实现者还必须确保这个关系是可传递的:x.compareTo(y)>0 && y.compareTo(z)>0暗示着x.compareTo(z) >0。
3)最后,实现者必须确保x.compareTo(y)暗示着所有的z都满足sgn(x.compareTo(z))==sgn(y.compareTo(z))。
4)强烈建议(x.compareTo(y)0)(x.equals(y)),但是这并非绝对必要,一般来说任何实现了Comparable接口的类,若违反了这个条件,都应该明确的予以说明。推荐使用这样的说法"注意:该类具有内在的排序功能,但是与equals不一致".。

5.在跨越不同类的时候,compareTo可以不做比较:如果两个被比较的对象引用不同类的对象,compareTo可以抛出ClassCastException异常。

6.违反compareTo约定的类也会破坏其他依赖于比较关系的类,依赖于比较关系的类包括有序集合类TreeSet和TreeMap,以及工具类Coolections和Arrays,他们内部包含有搜索和排序算法。

7.如果遵守(x.compareTo(y)0)(x.equals(y)),那么由compareTo方法所施加的顺序关系就被认为"与equals一致"。如果违反了这个规则,顺序关系就被认为"与equals不一致",他仍然能够正常工作,但是如果一个有序集合包含了该类的元素,这个集合就可能无法遵守相应集合接口(Collection,Set,Map)的通用约定。这是因为,对于这些接口的通用约定是按照equals方法来定义的,但是有序集合使用了由compareTo方法而不是equals方法所施加的等同性测试。

8.CompareTo方法中的域的比较是顺序的比较,而不是等同性的比较。比较对象引用域可以是通过递归的调用compareTo方法来实现。如果一个域并没有实现Comparable接口,或者你需要使用一个非标准的排序关系,就可以使用一个显式的Comparable接口,或者编写自己的Comparator,或者使用已有的Comparator。下面我们举个例子:

public class ComparatorDemo {

    private final String s;

    public ComparatorDemo(String s) {
        this.s = s;
    }


    public int compareTo(ComparatorDemo cd) {
        int result = String.CASE_INSENSITIVE_ORDER.compare(s, cd.s);
        return result;
    }

    public static void main(String[] args) {
        ComparatorDemo cd1 = new ComparatorDemo("a");
        ComparatorDemo cd2 = new ComparatorDemo("b");
        System.out.println(cd1.compareTo(cd2));
    }

}

9.比较整数型基本类型的域,可以使用关系操作符 < 和 > 。例如:浮点域用Bouble.compare或者Float.compare,而不用关系操作符,当应用到浮点值时,他们没有遵守compareTo的通用约定。对于数组域,则要把这些指导原则应用到每个元素上。

10.如果一个类有多个关键域,那么,按什么样的顺序来比较这些域是非常关键的。你必须从最关键的域开始,逐步进行到所有的重要域。如果某个域的比较产生了非零的结果(零代表相等),则整个比较操作结束,并返回该结果。如果最关键的域是相等的,则进一步比较次关键的域,以此类推。如果所有的域都是相等的,则对象就是相等的,并返回零。下面我们举个例子:

public class PhoneNumber implements 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);
        }
    }

    //覆写equals方法
    @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.areaCode == areaCode && pn.prefix == prefix;
    }

    @Override
    public int hashCode() {
        int result=17;
        result=31*result+areaCode;
        result=31*result+prefix;
        result=31*result+lineNumber;
        return result;
    }

    /**
     * 返回此电话号码的字符串表示形式。
     * 字符串由14个字符组成,格式为"(XXX) YYY-ZZZZ",其中XXX为区号,YYY为前缀,ZZZZ为行号(每个大写字母代表一个十进制数字)
     *
     * 如果有一些行号太短比如说是三位数,那么我们在开头用0来填充。例如,如果行号的值是123,则字符串表示形式的最后四个字符将是"0123"。
     *
     * 注意,区号后的右括号与前缀的第一个数字之间用一个空格隔开。
     * @return
     */
    @Override
    public String toString() {
        return String.format("(%03d) %03d-%04d",areaCode,prefix,lineNumber);
    }

    @Override
    public int compareTo(PhoneNumber pn) {
        //判断areaCode
        if(areaCode<pn.areaCode){
            return -1;
        }
        if(areaCode>pn.areaCode){
            return 1;
        }
        //areaCode相同时判断prefix
        if(prefix<pn.prefix){
            return -1;
        }
        if(prefix>pn.prefix){
            return 1;
        }
        //prefix相同时判断lineNumber
        if(lineNumber<pn.lineNumber){
            return -1;
        }
        if(lineNumber>pn.lineNumber){
            return 1;
        }
        return 0;
    }
    
    public static void main(String[] args) {
        PhoneNumber pn1 = new PhoneNumber(707, 867, 5309);
        PhoneNumber pn2 = new PhoneNumber(701, 867, 5309);
        System.out.println(pn1.compareTo(pn2));
    }

}

compareTo方法的约定并没有指定返回值的大小,而只是指定了返回值的符号。你可以利用这一点来简化代码,或许可以提高他的运行性速度。

@Override
    public int compareTo(PhoneNumber pn) {
        //判断areaCode
        int areaCodeDiff = areaCode - pn.areaCode;
        if(areaCodeDiff!=0){
            return areaCodeDiff;
        }
        //areaCode相同时判断prefix
        int prefixDiff = prefix - pn.prefix;
        if(prefixDiff!=0){
            return prefixDiff;
        }
        //prefix相同时判断lineNumber
        int lineNumberDiff = lineNumber - pn.lineNumber;
        if(lineNumberDiff!=0){
            return lineNumberDiff;
        }
        return 0;
    }

这种方法用起来要非常小心,除非你确信相关的域不会为负值,或者更一般的情况:最小和最大的可能域值之差小于或等于INTEGER.MAX_VALUE(2³¹-1),否则就不要使用这种方法。这种技巧有时不能正常工作的原因在于,一个有符号的32位的整数还没有大到足以表达任意两个32位的整数的差。如果i是一个很大的正整数(int类型),而j是一个很大的负整数(int类型),那么(i-j)将会溢出,并返回一个负值。这样就使得compareTo方法将对某些参数返回错误的结果,违反了compareTo约定的第一条和第二条。

11.我们无法在使用新的值组件扩展可实例化的类时,同时保持compareTo约定,除非愿意放弃面向对象的抽象优势。针对equals的权宜之计适用于compareTo方法。如果你想为一个实现了Comparable接口的类增加值组件,请不要扩展这个类,而是要编写一个不相关的类,其中包含第一个类的一个实例。然后提供一个"视图"方法返回这个实例。这样既可以自由的在第二个类上实现compareTo方法,同时允许他的客户端在必要的时候,把第二个类的实例视为第一个类的实例。
原类

public class ComparableDemo implements Comparable<ComparableDemo> {

    private final int x;
    private final int y;


    public ComparableDemo(int x, int y) {
        this.x = x;
        this.y = y;
    }


    @Override
    public int compareTo(ComparableDemo cd) {
        if (x < cd.x) {
            return -1;
        }
        if (x > cd.x) {
            return 1;
        }
        if (y < cd.y) {
            return -1;
        }
        if (y > cd.y) {
            return 1;
        }
        return 0;
    }

    public static void main(String[] args) {
        ComparableDemo cd=new ComparableDemo(1,2);
        ComparableDemo cd1=new ComparableDemo(12,2);
        System.out.println(cd.compareTo(cd1));
    }

}

扩展类

public class ComparableDemoSon {

    private final ComparableDemo cd;
    private final int z;

    public ComparableDemoSon(int x, int y, int z) {
        cd = new ComparableDemo(x, y);
        this.z = z;
    }

    //创建公有视图
    public ComparableDemo returnComparableDemo() {
        return cd;
    }

    /**
     * 先调用ComparableDemo类的compareTo方法,如果返回0的话,才会继续往下走,判断扩展的z是否相同。
     * @param cs
     * @return
     */
    public int compareTo(ComparableDemoSon cs) {
        int i = cd.compareTo(cs.cd);
        if (i != 0) {
            return i;
        }
        if (z < cs.z) {
            return -1;
        }
        if (z > cs.z) {
            return 1;
        }
        return 0;
    }

    public static void main(String[] args) {
        ComparableDemoSon cs = new ComparableDemoSon(1, 2, 3);
        ComparableDemoSon cs1 = new ComparableDemoSon(12, 2, 3);
        System.out.println(cs.compareTo(cs1));
    }

}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值