Generics: T extends MyClass vs. T extends MyClass<T>

15
这两种声明方式在语义上有区别吗,还是只是语法糖?

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

背景:我最近回答了一个关于泛型的question,使用了C<T extends C>的方法,同事也提供了一个基于C<T extends C<T>>的类似答案。最终,这两种选择都提供了相同的结果(在所问问题的上下文中)。我仍然好奇这两个构造之间的区别。

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

2
我知道这不是你询问的内容,但第一个代码片段包含原始类型,这意味着它不是“正确”的普通代码 - C 应该使用某种参数化。在我看来,如果不是为了向后兼容性,这将是一个难以处理的编译器错误。 - Andrzej Doyle
我知道这也不是你真正想问的问题,但是由于程序在两种情况下的行为方式相同,因此没有语义上的区别。 - Mathias Schwarz
3个回答

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是什么的保证-因此减法操作可以将自己复制到加法操作中。

这不是为了保护你的类层次结构免受恶意子类的攻击,而是为了让编译器更加确信所涉及的类型。如果你从另一个类中调用copy来处理任意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; 
}
< p > 将 duplicate 的第一行分配给 T ,如果您提出的两个边界中较弱的不安全,则该操作将不符合类型安全性,因此代码将无法编译。即使您明智地定义了所有子类。< /p >

你的代码中没有其他地方引用 Operation - 你是不是想在某个地方加上 implements Operation - Eric

4
假设你有一个名为Animal的类。
class Animal<T extends Animal>

如果 T 是 Dog,那意味着 Dog 应该是 Animal< Dog>、Animal< Cat>、Animal< Donkey> 等的子类。
class Animal<T extends Animal<T>>

在这种情况下,如果 T 是 Dog,则它必须是 Animal< Dog> 的子类,不能是其他任何东西,也就是说,你必须有一个类。
class Dog extends Animal<Dog>

您无法拥有

class Dog extends Animal<Cat>

即使你拥有这个,你也不能将Dog用作Animal的类型参数,必须要有

class Cat extends Animal<Cat>

在许多情况下,两者之间存在很大的差异,特别是如果您有一些约束条件。


只要 class Cat extends Animal<Cat>,你可以在 两种情况下 都有 class Dog extends Animal<Cat> - Marcelo
只要 Cat 继承自 Animal <Dog>,你的意思是什么。 - aps
不,我的意思是只要Cat扩展Animal<Cat>,那么Dog就可以扩展Animal<Cat>。泛型模板只是表示T应该扩展Animal<T>,而Cat 可以 扩展Animal<Cat>。 - Marcelo
但是你不能直接将那个 Dog 与 Animal 一起使用。也就是说,你不能有一个类 Animal<T extends Animal<T>>,其中 T 后来被指定为 Dog,因为它必须完全匹配。 - aps

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 不是类型 D<T> 的有界参数 <T extends D<T>> 的有效替代品


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接