通过将新类型指定为通用的实际类型来扩展泛型有什么好处

时间:2013-06-14 00:05:41

标签: java generics

我在某处看到了这种模式:

class A extends B<A> {

}

通过将新类型指定为泛型的实际类型,此结构扩展泛型有点不寻常。有什么用?这个模式有名字吗?有没有替代模式?

示例:https://code.google.com/p/selenium/wiki/LoadableComponent

跳转至:public class EditIssue extends LoadableComponent<EditIssue> {

编辑: 阅读完回复后,我似乎需要改变编译器对类型检查的理解。在我的脑海里,我的牛肉有这种模式,如果两个A需要相同,那么有没有办法不重复它们?但似乎没有更好的方法将派生类的类型传播给父类。

5 个答案:

答案 0 :(得分:4)

在处理recursive data structures时,您可能会这样做。例如,图形或树中的节点可以定义为其他节点的集合:

class Node extends AbstractList<Node> {
    ...
}

如果抽象/泛型类型用于比较相似类型的对象,例如java.lang.Comparable的情况,则可能会看到类似这样的内容:

class MyObject implements Comparable<MyObject> {
    public int compareTo(MyObject other) { ... }
}

答案 1 :(得分:4)

当然,OOP的答案是AB。如果A不是B而不是A,则应该只使用B来构建B以使用B的功能。

据推测,B也有一些通用实现,它们利用了对泛型类型的限制。

另一个用例是abstract class B<T extends B<T>> { public T createCopy(T t); } 看起来像:

createCopy

现在,子类可以实现class A extends B<A> { public A createCopy(A t) { return new A(t); //copy constructor } } ,客户端代码可以安全地使用它而无需强制转换......例如。

abstract class B {
    public B createCopy(B t);
}
class A extends B {
    public B createCopy(B t) { //Is the copy an A or a different subtype of B? We don't know.
        return new A(t); //copy constructor
    }
}

将上述内容与:

进行比较
{{1}}

答案 2 :(得分:2)

举个例子:

E extends Comparable<E>

这意味着E必须是一个知道如何与自身进行比较的类型,因此,递归类型定义。

不知道它是否有任何正式名称,但我会称之为递归泛型类型

答案 3 :(得分:1)

此模式与任何其他子类相同。使用泛型时真正发生的事情是JVM正在创建一个类的副本(实际上不是副本,但它有点类似),并用指定的类型替换泛型所使用的所有点。

所以,为了回答你的问题,所有这种模式都在用B<A>代替B,其中A的所有用法都被任何类A所取代。 。这种情况的潜在用途是您为特定类定制数据结构(来自java.util.Collections),例如使用位移将Collection<Boolean>压缩为较小的内存量。我希望这是有道理的!

答案 4 :(得分:1)

确实令人困惑,因为AB<A>两种类型似乎相互依赖存在;在普通的OOP中没有多大意义,那么它的用途是什么?我发现了这种模式的3个用例。

组合转为继承

假设Node 有一个子节点列表。通常的设计是通过构图

class Node
    ArrayList<Node> children = ...

有时为了获得较小的性能,人们会使用继承

class Node extends ArrayList<Node>
    // the super class represents the children...

这有点令人困惑,但没有什么难以理解的。我们知道这只是一种方便,它并不试图传达节点是一个节点列表。

LoadableComponent可以考虑此用例。它可以说是一种不太理想的设计而不是合成方法

class ComponentLoader<C>
    C get(){...}

class EditIssue
    final ComponentLoader<EditIssue> loader = new ComponentLoader<EditIssue>(){
        @Override void load(){...}
        @Override void isLoaded(){...}
    };

EditIssue compo = ...
compo.loader.get().doSomething();

设计师可能会发现这种方法更加困难。

方法链接

而不是写

foo.doA();
foo.doB();

很多人宁愿写

foo.doA().doB();

不幸的是,该语言并不直接支持方法链接,即使它正在成为越来越受欢迎的功能。解决方法是让doA()返回foo。它有点脏但可以接受。

但是,如果foo位于类型层次结构中,则解决方法将被破坏

class Bar
    Bar doA()

class Foo extends Bar
    Foo doB();

foo.doA().doB(); // doesn't compile, since doA() returns Bar

所以有些人要求一种特殊的“自我类型”来解决这个问题。假设有一个关键字This来表示“自我类型”

class Bar
    This doA()

foo.doA().doB(); // works, doA() returns the type of foo, which is Foo

似乎方法链是“自我类型”的唯一用例,因此语言可能永远不会引入它(最好直接支持方法链接)

人们发现泛型提供了解决此问题的方法

class Bar<This>
    This doA()

class Foo extends Bar<Foo>

Foo has a method "Foo doA()", inherited from Bar<Foo>

这是A extends B<A>模式最常用的用例。这是一个孤立的解决方法/技巧。它在A和B之间的关系中没有添加语义。

限制This之类的

也是一种流行的做法
class Bar<This extends Bar<This>>

这是丑陋无用的,我强烈建议不要这样做。只需使用“This”作为约定来表明它的用途。

LoadableComponent也属于此用例。在更简单的设计中我们可以做到

class LoadableComponent
    void ensureLoaded()

class EditIssue extends LoadableComponent

EditIssue compo = ...
compo.ensureLoaded();
compo.doSomething();

为了支持最后两行的方法链接,LoadableComponent以其当前形式设计,以便我们可以编写compo.get().doSomething()

更多元

所以前两个用例是一种黑客攻击。如果AB<A>之间存在真正的约束怎么办?

B不是普通超类型,而是更多元,它描述了类型A应该具有引用A本身的一些属性。这不是传统OOP的继承,而是更抽象的东西。 (虽然它仍然通过传统的继承机制实现,但可以想象该语言可以将其作为一个独立的概念进行推广。)

Comparable属于此用例。它描述了某种类型可以与自身进行比较。由于它不是传统的OOP类型,理想情况下我们不应该声明具有静态类型Comparable的对象。我们在公共方法返回/参数类型中看不到它,它没有多大意义。相反,我们会看到像

这样的东西
<T extends Comparable<T>> 
void sort(List<T>)

这里的方法需要一个符合Comparable模式的类型。

(我真的不知道我在这节谈论的是什么)