应该比较另一种类型吗?

时间:2016-01-05 02:43:34

标签: java comparable

我想知道以下是否有效用例:

class Base {}

class A implements Comparable<Base> {
    //...
}

接受T类型的集合似乎是一种常见模式(请参阅Collections以获取大量示例),其中T extends Comparable<? super T>

但是在与基类进行比较时,在技术上似乎不可能完成compareTo()的合同,因为没有办法确保另一个类不会通过相互矛盾的比较扩展基数。请考虑以下示例:

class Base {
    final int foo;
    Base(int foo) {
        this.foo = foo;
    }
}

class A extends Base implements Comparable<Base> {
    A(int foo) {
        super(foo);
    }
    public int compareTo(Base that) {
        return Integer.compare(this.foo, that.foo); // sort by foo ascending
    }
}

class B extends Base implements Comparable<Base> {
    B(int foo) {
        super(foo);
    }
    public int compareTo(Base that) {
        return -Integer.compare(this.foo, that.foo); // sort by foo descending
    }
}

我们有两个类使用不遵循通用规则的比较来扩展Base(如果有一个共同的规则,它几乎肯定会在Base中实现)。然而,以下破坏的排序将编译:

Collections.sort(Arrays.asList(new A(0), new B(1)));

仅接受T extends Comparable<T>会更安全吗?或者是否有一些用例来验证通配符?

2 个答案:

答案 0 :(得分:10)

这是一个非常好的问题。首先,让我们从Collections使用

等方法开始
binarySearch(List<? extends Comparable<? super T>> list, T key)

确实,为什么不呢

binarySearch(List<? extends Comparable<T>> list, T key)

原因是PECS原则:Producer Extends,Consumer Super。 binarySearch做了什么? 从列表中读取元素,然后通过值传递给compareTo函数来比较它们。由于它读取元素,因此列表充当生产者,因此第一部分 - 生产者扩展。这个很明显,那么消费者超级部分呢?

Consumer Super基本上意味着如果您只是将值传递给某个函数,那么您是否真的关心它是否接受您的对象的确切类型或它的某些超类。那么binarySearch声明所说的是:只要可以将任何内容传递给列表元素的compareTo方法,我就可以查找任何内容。

在对它进行排序的情况下并不那么明显,因为元素只是相互比较。但即便如此,如果Base实际实现Comparable<Base>(并进行整个比较),AB只是扩展Base而不以任何方式触及比较,该怎么办? ?然后,您将无法对AB的列表进行排序,因为它们不会分别实现Comparable<A>Comparable<B>。每次子类化时,您都必须重新实现整个接口!

另一个例子:如果有人想在包含某些类的实例的列表上进行二进制搜索,甚至不扩展Base会怎么样?

class BaseComparable implements Comparable<Base> {
    private final Base key;
    // other fields

    BaseComparable(Base key, ...) {
        this.key = key;
        // other fields initialization
    }

    @Override
    public int compareTo(Base otherKey) {
        return this.key.compareTo(otherKey);
    }
};

现在他们想要使用A的实例作为此二进制搜索的关键。他们只能因? super T部分而这样做。请注意,此类不知道密钥是A还是B,因此它无法实现Comparable<A/B>

至于你的例子,我想这只是一个糟糕设计的例子。不幸的是,在没有违反PECS原则和/或限制已经有限的Java泛型功能的情况下,我认为无法阻止此类事情。

答案 1 :(得分:0)

约束

    T extends Comparable<? super T>

应理解为

    T extends S, S extends Comparable<S>

Comparable类型始终是自我比较的。

如果X <: Comparable<Y>,则必须是X <: Y <: Comparable<Y>的情况。

我们甚至可以从compareTo()的合同中“证明”这一点。由于必须定义反向y.compareTo(x),因此Y <: Comparable<A>, X<:A必须为真。遵循相同的论点,我们有A <: Comparable<Y>。然后,传递性将导致A=Y