泛型:T扩展MyClass与T扩展MyClass <t> </t>

时间:2011-06-29 15:22:27

标签: java generics

这两个声明之间是否存在语义差异,还是仅仅是语法糖?

class C<T extends C> vs class C<T extends C<T>>

背景:我最近使用C<T extends C>方法回答了关于泛型的question,并且同行提供了基于C<T extends C<T>>的类似答案。最后,两种备选方案都提供了相同的结果(在提出的问题的背景下)。我仍然对这两种结构之间的区别感到好奇。

是否存在语义差异?如果是这样,每种方法的含义和后果是什么?

3 个答案:

答案 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; 
}

duplicateT第一行的赋值对于你提出的两个边界中较弱的一个不是类型安全的,所以代码不会编译。 即使你明智地定义了所有子类。

答案 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>