如何避免Java泛型扩展Comparable接口中未经检查的强制转换?
为什么在这个通用接口中需要不安全的cast (T)
?如果T
媲美本身,即实现ExtendedComparable<super of T>
这意味着还ExtendedComparable<T>
,那么为什么类型擦除需要ExtendedComparable<T>
定投至T?如何避免Java泛型扩展Comparable接口中未经检查的强制转换?
/* @param <T> T must be comparable to itself or any of its superclass
* (comparables are consumers, thus acc. to the PECS principle
* = producer-extends,consumer-super we use the bounded wildcard type "super")
*/
public interface ExtendedComparable<T extends ExtendedComparable<? super T>> {
Comparator<? super T> getComparator();
default boolean greaterThen(T toCompare) {
return getComparator().compare((T) this, toCompare) > 0;
}
}
因为没有保证this
实际上是T
类的一个实例,甚至扩展。
例如考虑这个问题:T0 extends ExtendComparable<T0>
和T0为T0的超:
public class T0 implements ExtendComparable<T0> {...}
public class T1 implements ExtendComparable<T0> {...}
在T0
,因为它与结合的符合规定的罚款。在这种情况下,this
是T0
的一个实例,所以你很好;铸造(T)this
(因此(T0)this
)是有道理的。
随着T1
因为结合应用到T0
没有T1
声明也正确,T
是取代T0
。然而this
是T1
和T1
是不是超级也不是T0
一个孩子。是的,两个都执行 ExtendedCompatible<T0>
,但你不能在兄弟姐妹之间施放。例如Integer和Double扩展数字,但(Integer) new Double(0.0)
失败。 铸造(T)
也转换为(T0)
失败。
您正在做的假设是T
将被设置为与已声明的类相同,并且当前无法强制这些语义。我希望这会在未来的Java语言版本中的某个时候发生变化,但也许为什么Java语言“特别工作组”会避免这样做是有原因的。
还有就是要完全避免投的方式,但更好,当你做出一个ExtendedCompatible
抽象类,而非接口。
你可以声明T
类型的值将被保护的构造通过扩展类设置的最后一个字段而这又必须通过this
作为其值:
public abstract class ExtendedCompatible<T extends ExtendedCompatible<? super T>> {
private final T thiz;
protected ExtendedCompatible(final T thiz) {
if (this != thiz) throw new IllegalArgumentException("you must pass yourself");
this.thiz = thiz;
}
...
public class MyExtendedCompatible extends ExtendedCompatible<MyExtendedCompatible> {
public MyExtendedCompatible() {
super(this);
}
}
你付出的代价是额外的内存对自身有愚蠢的引用的消费以及将this
传递给父构造函数的附加代码/ CPU负担。
另一个办法是宣布一个抽象的方法来获得T
(本):
// Parent abstract class:
protected abstract T getThiz();
// Child class... for each class:
protected MyChildClass getThiz() { return this; }
that'a IMO,希望能非常详细和很好的答案。堆栈溢出有时仅授予最高评分用户的声誉,但这是一个非常好的选择。 +1 – Eugene
谢谢。那是对的。我们打算把这个接口用于许多枚举。但是,不幸的是,提议的解决方法不适合枚举,因为它们不支持继承,因此不能从抽象类派生。第二个解决方法也不太可行,因为接口中的每个方法都需要检查getThiz()!= this(在Java 9中,至少所有这些相同的检查都可以重构为私有接口方法),并且每个枚举都会需要实现getThiz()。 – ms34449
@ ms34449枚举么?我认为你可以在每个常量中重写方法,在你的声明中包含一个主体。编辑啊!你的意思是跨越枚举而不是跨越常量。 –
感谢。瓦伦丁是对的。即使这两种类型都实现了相同的界面,但这并不是也不应该在它们之间进行强制转换。是的,Java中没有任何机制强制将T传入与正在声明的类相同的类。
这应该是对瓦伦丁回答的回应 - 你应该接受他的可能性 – Eugene
这篇文章可以为您的帮助:https://stackoverflow.com/a/25783345/4867374 –