可比性是否应该与另一种类型进行比较?

2022-09-03 02:38:59

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

class Base {}

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

接受类型的集合似乎是一种常见的模式(有关许多示例,请参阅集合),其中 .TT 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
    }
}

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

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

只接受不是更安全吗?或者是否有一些用例可以验证通配符?T extends Comparable<T>


答案 1

这是一个非常好的问题。首先,让我们从使用以下方法的原因开始:Collections

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

事实上,为什么不只是

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

其原因是PECS原则:生产者扩展,消费者超级。有什么作用?它从列表中读取元素,然后通过将其值传递给函数来比较它们。由于它读取元素,因此该列表充当生产者,因此第一部分 - 生产者扩展。这个是显而易见的,那么消费者超级部分呢?binarySearchcompareTo

Consumer Super基本上意味着,如果你只打算将值传递给某个函数,你并不真正关心它是否接受你的对象的确切类型或它的某些超类。因此,声明的意思是:只要任何东西都可以传递给列表元素的方法,我就可以查找任何内容。binarySearchcompareTo

在排序的情况下,它不是那么明显,因为元素只是相互比较。但即便如此,如果实际实现(并且整个比较)并且只是扩展而不以任何方式触及比较呢?然后,您将无法对 列表进行排序,因为它们没有分别实现 和。每次子类时,您都必须重新实现整个接口!BaseComparable<Base>ABBaseABComparable<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);
    }
};

现在,他们希望使用 的实例作为此二进制搜索的键。他们只能因为零件而做到这一点。请注意,此类不知道键是 an 还是 a,因此它不可能实现 。A? super TABComparable<A/B>

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


答案 2

约束

    T extends Comparable<? super T>

应理解为

    T extends S, S extends Comparable<S>

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

如果 ,则一定是 .X <: Comparable<Y>X <: Y <: Comparable<Y>

我们甚至可以从合同中“证明”。。由于必须定义相反的情况,因此 必须为 true。按照相同的参数,我们有.然后传递性将导致 。compareTo()y.compareTo(x)Y <: Comparable<A>, X<:AA <: Comparable<Y>A=Y


推荐