Comparable 应该与另一种类型进行比较吗?
Should Comparable ever compare to another type?
我想知道以下是否有有效的用例:
class Base {}
class A implements Comparable<Base> {
//...
}
接受类型为 T
的集合似乎是一种常见的模式(请参阅 Collections 了解一些示例),其中 T extends Comparable<? super T>
.
但与基础 class 相比,在技术上似乎无法履行 compareTo()
的契约,因为无法确保另一个 class 不会扩展基础与矛盾的比较。考虑以下示例:
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
}
}
我们有两个 classes 扩展 Base
使用不遵循通用规则的比较(如果有通用规则,它几乎肯定会在 Base
中实现).然而,以下损坏的排序将编译:
Collections.sort(Arrays.asList(new A(0), new B(1)));
只接受T extends Comparable<T>
不是更安全吗?或者是否有一些用例可以验证通配符?
这是个很好的问题。首先,让我们从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
函数来比较它们。由于它读取元素,因此列表充当生产者,因此第一部分 — Producer Extends。这个很明显,那么Consumer Super部分呢?
Consumer Super 基本上意味着如果您只是将值传递给某个函数,您并不真正关心它是否接受对象的确切类型或它的某些 superclass。所以 binarySearch
声明的意思是:只要任何东西都可以传递给列表元素的 compareTo
方法,我就可以寻找任何东西。
在排序的情况下并不那么明显,因为元素只是相互比较。但即便如此,如果 Base
实际上实现了 Comparable<Base>
(并进行了整个比较)并且 A
和 B
只是扩展了 Base
而没有以任何方式触及比较?那么你将无法对 A
和 B
的列表进行排序,因为它们没有分别实现 Comparable<A>
和 Comparable<B>
。每次 subclass!
时都必须重新实现整个界面
另一个例子:如果有人想对包含某些 class 的实例的列表进行二分搜索,而这些实例甚至没有扩展您的 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
部分。请注意,此 class 不知道密钥是 A
还是 B
,因此它不可能实现 Comparable<A/B>
.
至于你的例子,我想这只是一个设计不佳的例子。不幸的是,我看不到在不违反 PECS 原则 and/or 限制已经有限的 Java 泛型功能的情况下防止此类事情发生的方法。
约束条件
T extends Comparable<? super T>
应该理解为
T extends S, S extends Comparable<S>
A Comparable
类型总是 self-comparing。
如果X <: Comparable<Y>
,则一定是X <: Y <: Comparable<Y>
。
我们甚至可以 "prove" 来自 compareTo()
的合同。由于必须定义反向y.compareTo(x)
,因此Y <: Comparable<A>, X<:A
必须为真。按照同样的论证,我们有 A <: Comparable<Y>
。那么传递性会导致A=Y
。
我想知道以下是否有有效的用例:
class Base {}
class A implements Comparable<Base> {
//...
}
接受类型为 T
的集合似乎是一种常见的模式(请参阅 Collections 了解一些示例),其中 T extends Comparable<? super T>
.
但与基础 class 相比,在技术上似乎无法履行 compareTo()
的契约,因为无法确保另一个 class 不会扩展基础与矛盾的比较。考虑以下示例:
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
}
}
我们有两个 classes 扩展 Base
使用不遵循通用规则的比较(如果有通用规则,它几乎肯定会在 Base
中实现).然而,以下损坏的排序将编译:
Collections.sort(Arrays.asList(new A(0), new B(1)));
只接受T extends Comparable<T>
不是更安全吗?或者是否有一些用例可以验证通配符?
这是个很好的问题。首先,让我们从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
函数来比较它们。由于它读取元素,因此列表充当生产者,因此第一部分 — Producer Extends。这个很明显,那么Consumer Super部分呢?
Consumer Super 基本上意味着如果您只是将值传递给某个函数,您并不真正关心它是否接受对象的确切类型或它的某些 superclass。所以 binarySearch
声明的意思是:只要任何东西都可以传递给列表元素的 compareTo
方法,我就可以寻找任何东西。
在排序的情况下并不那么明显,因为元素只是相互比较。但即便如此,如果 Base
实际上实现了 Comparable<Base>
(并进行了整个比较)并且 A
和 B
只是扩展了 Base
而没有以任何方式触及比较?那么你将无法对 A
和 B
的列表进行排序,因为它们没有分别实现 Comparable<A>
和 Comparable<B>
。每次 subclass!
另一个例子:如果有人想对包含某些 class 的实例的列表进行二分搜索,而这些实例甚至没有扩展您的 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
部分。请注意,此 class 不知道密钥是 A
还是 B
,因此它不可能实现 Comparable<A/B>
.
至于你的例子,我想这只是一个设计不佳的例子。不幸的是,我看不到在不违反 PECS 原则 and/or 限制已经有限的 Java 泛型功能的情况下防止此类事情发生的方法。
约束条件
T extends Comparable<? super T>
应该理解为
T extends S, S extends Comparable<S>
A Comparable
类型总是 self-comparing。
如果X <: Comparable<Y>
,则一定是X <: Y <: Comparable<Y>
。
我们甚至可以 "prove" 来自 compareTo()
的合同。由于必须定义反向y.compareTo(x)
,因此Y <: Comparable<A>, X<:A
必须为真。按照同样的论证,我们有 A <: Comparable<Y>
。那么传递性会导致A=Y
。