这两个声明之间是否存在语义差异,还是仅仅是语法糖?
class C<T extends C>
vs class C<T extends C<T>>
背景:我最近使用C<T extends C>
方法回答了关于泛型的question,并且同行提供了基于C<T extends C<T>>
的类似答案。最后,两种备选方案都提供了相同的结果(在提出的问题的背景下)。我仍然对这两种结构之间的区别感到好奇。
是否存在语义差异?如果是这样,每种方法的含义和后果是什么?
答案 0 :(得分:9)
当然 - 通常这些“自我类型”用于约束子类型以完全返回自己的类型。考虑以下内容:
public interface Operation {
// This bit isn't very relevant
int operate(int a, int b);
}
public abstract class AbstractOperation<T extends AbstractOperation<T>> {
// Lets assume we might need to copy operations for some reason
public T copy() {
// Some clever logic that you don't want to copy and paste everywhere
}
}
酷 - 我们有一个父类,它有一个有用的运算符,可以特定于子类。例如,如果我们创建AddOperation
,它的通用参数是什么?由于“递归”泛型定义,这只能是AddOperation给我们:
public class AddOperation extends AbstractOperation<AddOperation> {
// Methods etc.
}
因此copy()
方法保证返回AddOperation
。现在让我们想象一下我们是愚蠢的,恶意的,或创造性的,或者其他什么,并尝试定义这个类:
public class SubtractOperation extends AbstractOperation<AddOperation> {
// Methods etc.
// Because of the generic parameters, copy() will return an AddOperation
}
这将被编译器拒绝,因为泛型类型不在其范围内。这非常重要 - 这意味着在父类中,即使我们不知道具体类型是什么(并且它甚至可能是在编译时不存在的类),copy()
方法将返回该子类的实例。
如果您只使用C<T extends C>
,那么SubtractOperation
这个奇怪的定义将是合法的,您将失去对T
所属内容的保证case - 因此减法操作可以将自身复制到添加操作中。
这不是为了保护您的类层次结构免受恶意子类的影响,更重要的是它为编译器提供了对所涉及类型的更强保证。如果您在任意copy
上从另一个类altogther调用Operation
,则其中一个格式会保证结果属于同一类,而另一个格式需要强制转换(并且可能不是正确的强制转换,就像上面的SubtractOperation一样。)
例如:
// This prelude is just to show that you don't even need to know the specific
// subclass for the type-safety argument to be relevant
Set<? extends AbstractOperation> operations = ...;
for (AbstractOperation<?> op : operations) {
duplicate(op);
}
private <T extends AbstractOperation<T>> Collection<T> duplicate(T operation) {
T opCopy = operation.copy();
Collection<T> coll = new HashSet<T>();
coll.add(operation);
coll.add(opCopy);
// Yeah OK, it's ignored after this, but the point was about type-safety! :)
return coll;
}
duplicate
到T
第一行的赋值对于你提出的两个边界中较弱的一个不是类型安全的,所以代码不会编译。 即使你明智地定义了所有子类。
答案 1 :(得分:4)
假设您有一个名为Animal的课程
class Animal<T extends Animal>
如果T是Dog,那就意味着Dog应该是Animal&lt;的子类。狗&gt;,动物&lt; Cat&gt;,Animal&lt;驴&GT;任何东西。
class Animal<T extends Animal<T>>
在这种情况下,如果T是Dog,它必须是Animal&lt;的子类。狗&GT;没有别的,即你必须有一个班级
class Dog extends Animal<Dog>
你不能拥有
class Dog extends Animal<Cat>
即使你有,也不能将Dog用作Animal的类型参数,你必须拥有
class Cat extends Animal<Cat>
在许多情况下,两者之间存在很大差异,特别是如果您应用了一些约束。
答案 2 :(得分:0)
是的,存在语义上的差异。这是一个最小的插图:
class C<T extends C> {
}
class D<T extends D<T>> {
}
class Test {
public static void main(String[] args) {
new C<C>(); // compiles
new D<D>(); // doesn't compile.
}
}
错误很明显:
绑定不匹配:类型
D
不能替代<T extends D<T>>
类型的有界参数D<T>