【问题标题】:Generics: T extends MyClass vs. T extends MyClass<T>泛型:T 扩展 MyClass 与 T 扩展 MyClass<T>
【发布时间】:2011-06-29 15:22:27
【问题描述】:

这两个声明之间是否存在语义差异,或者只是语法糖?

class C&lt;T extends C&gt; 与 class C&lt;T extends C&lt;T&gt;&gt;

背景:我最近使用C&lt;T extends C&gt; 方法回答了一个关于泛型的question,同行提供了基于C&lt;T extends C&lt;T&gt;&gt; 的类似答案。最后,两种选择都提供了相同的结果(在所问问题的背景下)。我一直对这两种结构之间的区别感到好奇。

有语义上的区别吗?如果是这样,每种方法的含义和后果是什么?

【问题讨论】:

  • 我知道这不是你要问的,但第一个 sn-p 确实包含一个原始类型,这意味着它不是“正确的”通用代码 - C 应该用参数化某物。我认为如果不是为了向后兼容,这将是一个硬编译器错误。
  • 我知道这也不是(真的)你的问题,但没有 语义 区别,因为程序在两种情况下的行为方式相同。

标签: java generics


【解决方案1】:

当然 - 通常这些“自我类型”用于约束子类型以完全返回它们自己的类型。考虑以下内容:

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&lt;T extends C&gt;,那么SubtractOperation 的这个奇怪定义将是合法的,并且你失去了T 在这种情况下是什么的保证 - 因此减法运算可以将自身复制到添加操作中。

这并不是为了保护您的类层次结构免受恶意子类的侵害,更多的是它为编译器提供了对所涉及类型的更强有力的保证。如果您在任意Operation 上从另一个类调用copy,则您的一种形式保证结果将属于同一类,而另一种则需要强制转换(并且可能不是正确的演员表,就像上面的 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 的第一行的赋值不会是类型安全的,因为您建议的两个边界较弱,因此代码无法编译。 即使你合理地定义了所有的子类。

【讨论】:

  • 您没有在代码中的其他任何地方引用Operation - 您是否想要在其中的某个地方使用implements Operation?
【解决方案2】:

假设你有一个名为 Animal 的类

class Animal<T extends Animal>

如果 T 是 Dog,则意味着 Dog 应该是 Animal、Animal、Animal 任何东西的子类。

class Animal<T extends Animal<T>>

在这种情况下,如果 T 是 Dog,它必须是 Animal 的子类,没有别的,即你必须有一个类

class Dog extends Animal<Dog>

你不能拥有

class Dog extends Animal<Cat>

即使你有,你也不能使用 Dog 作为 Animal 的类型参数,你必须有

class Cat extends Animal<Cat>

在许多情况下,两者之间存在很大差异,尤其是当您应用了一些约束时。

【讨论】:

  • 是的,只要class Cat extends Animal&lt;Cat&gt;class Dog extends Animal&lt;Cat&gt;,您就可以在两种情况下都使用class Dog extends Animal&lt;Cat&gt;。
  • 只要 Cat 扩展 Animal 你的意思是
  • 没有。我的意思是只要 Cat 扩展 Animal,Dog 就可以扩展 Animal。通用模板只是表明 T 应该扩展 Animal 并且 Cat 可以 扩展 Animal。
  • 但是你不能直接将那只狗与动物一起使用。即你不能有类 Animal> ,其中 T 稍后被指定为 Dog。因为它必须完全匹配。
【解决方案3】:

是的,存在语义差异。这是一个最小的插图:

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 类型不是D&lt;T&gt; 类型的绑定参数&lt;T extends D&lt;T&gt;&gt; 的有效替代品

【讨论】:

    猜你喜欢
    • 2021-12-18
    • 1970-01-01
    • 2023-02-02
    • 1970-01-01
    • 2011-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多