Java教程

【Effective Java 14】考虑实现 Comparable 接口

本文主要是介绍【Effective Java 14】考虑实现 Comparable 接口,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1. 什么时候应该让类实现 Comparable 接口

Comparable 接口是一个泛型接口,代码如下:

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

类实现 Comparable 接口,就表明它的实例具有内在的排序关系,比如按照字母顺序、按数值顺序或者按年代顺序,那你就应该考虑实现 Comparable 接口:

一般来说,值类适合实现 Comparable 接口。

2. 使用 Comparable 接口的约定

将一个对象与另一个对象进行比较时。当对象小于、等于或大于指定对象的时候,分别返回一个负整数、零或正整数。如果由于指定对象的类型而无法与该对象进行比较,则抛出 ClassCastException 异常。

实现 Comparable 接口的类,在实现时应该满足以下约定(注意,在下面的说明中,符号 sgn(...) 表示数学中的 signum 函数,它根据表达式的值为负、零、正,分别返回-1,0,1):

  • 实现者必须确保所有的 x 和 y 都满足 sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) (交换律)
  • 实现者必须确保比较关系可以传递:(x.compareTo(y) > 0 && y.compareTo(z) > 0)x.comareTo(z) > 0;(传递律)
  • 实现着必须确保 x.compareTo(y) == 0 则对于所有的 z 都满足 sgn(x.compareTo(z)) == sgn(y.compareTo(z))
  • 强烈建议对于值类,满足(x.compareTo(y) == 0) == (x.equals(y))。但也并非绝对必要,如果违反这一条件,都应该在文档中明确予以说明。

注意,Comparable 接口不能跨越不同类型进行比较,在比较不同类型的对象时,可以在 compareTo 函数中抛出 ClassCastException 异常来拒绝执行。

违反 Comparable 接口约定会破坏其他依赖于比较关系的类或方法,如 TreeSetTreeMap、工具类 CollectionsArrays,因为它们内部都用与排序相关的算法。

3. 使用 Comparable 接口的建议

  • compareTo 方法中使用关系操作符><去比较基本类型是十分繁琐的,或者使用->0判断。对于基本类型在 compareTo 函 数中进行比较应该使用静态方法代替,如Integer.compare(a, b)Float.compare(a, b)Double.compare(a, b) 等等。
// 太繁琐
public int compare(Integer A, Integer B) {
    if A > B {
        return 1;
    } else if A < B {
        return -1;
    } else {
        return 0;
    }
}

// 有溢出风险
public int compare(Integer A, Integer B) {
	return A - B;
}

// 最佳
public int compare(Integer A, Integer B) {
	return Integer.compare(A, B);
}
  • 如果一个类有多个关键域,按照什么样的顺序来比较这些域是十分关键的。你必须从最关键的域开始,逐步进行到所有重要的域。

4. 使用 Comparator 接口

  • Comparator 接口配置了一组比较构造器方法,使得比较器的构造工作变得更加流畅。之后,按照 Comparable 接口的要求,这些比较器可以用来实现一个 compareTo 方法。如下:
private static class PhoneNumber implements Comparable<PhoneNumber>{
    private final short areaCode;
    private final short prefix;
    private final short lineNum;

    public PhoneNumber(short areaCode, short prefix, short lineNum) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNum = lineNum;
    }

    // 使用 Comparator 可以轻松实现对关键域按优先级进行比较
    private static final Comparator<PhoneNumber> COMPARATOR =
            Comparator
                    .comparingInt((ToIntFunction<PhoneNumber>) value -> value.areaCode)
                    .thenComparingInt(value -> value.prefix)
                    .thenComparingInt(value -> value.lineNum);

    @Override
    public int compareTo(PhoneNumber o) {
        return COMPARATOR.compare(this, o);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PhoneNumber that = (PhoneNumber) o;
        return areaCode == that.areaCode && prefix == that.prefix && lineNum == that.lineNum;
    }

    @Override
    public int hashCode() {
        return Objects.hash(areaCode, prefix, lineNum);
    }
}

5. 总结

  • 对于排序敏感类,都应该让该类实现 Comparable 接口
  • compareTo 函数中避免使用 > 或 < 操作符,不要通过减法运算去进行判断,有可能溢出
  • 如果 compareTo 函数编写起来比较复杂,比如关键域较多的情况,应该单独实现一个静态Comparator 比较构造器,并通过它简介实现 comparaTo 接口。
这篇关于【Effective Java 14】考虑实现 Comparable 接口的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!