【问题标题】:What is the benefit of extending a generic by specifying the new type as actual type of generic通过将新类型指定为泛型的实际类型来扩展泛型有什么好处
【发布时间】:2013-06-14 15:56:20
【问题描述】:

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

class A extends B<A> {

}

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

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

跳转到:public class EditIssue extends LoadableComponent&lt;EditIssue&gt; {

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

【问题讨论】:

标签: java generics


【解决方案1】:

当然,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
    }
}

【讨论】:

  • 应该是B&lt;T extends B&lt;T&gt;&gt;
【解决方案2】:

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

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

同样,如果抽象/通用类型用于比较相似类型的对象,例如 java.lang.Comparable,您可能会看到类似的情况:

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

【讨论】:

  • @TimBender class Node extends AbstractList&lt;Node&gt;
  • 当我第一次看到这个时,我发誓它说的是implements Collection&lt;Node&gt;
【解决方案3】:

举个例子:

E extends Comparable<E>

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

不知道它有没有正式名称,但我会称之为递归泛型类型模式

【讨论】:

  • 通常您将拥有class MyType implements Comparable&lt;MyType&gt;。当java.lang.Enum 实现Comparable 时,你会得到经典的class Enum&lt;E extends Enum&lt;E&gt;&gt;
  • 在您的示例中,类型变量引用自身。这与类引用自身的问题不同。
【解决方案4】:

确实令人困惑,因为AB&lt;A&gt;这两种类型似乎相互依赖而存在;这在普通的 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

看来方法链是“self type”的唯一用例,所以语言可能永远不会引入它(最好直接支持方法链)

人们发现泛型可以解决这个问题

class Bar<This>
    This doA()

class Foo extends Bar<Foo>

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

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

约束Thislike也是一种流行的做法

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&lt;A&gt; 之间存在真正的约束怎么办?

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

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

<T extends Comparable<T>> 
void sort(List<T>)

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

(我真的不知道我在这部分中在说什么)

【讨论】:

    【解决方案5】:

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

    因此,为了回答您的问题,该模式所做的就是将 B&lt;A&gt; 替换为 B,其中 A 的所有用途都替换为 A 的任何类。这样做的潜在用途是在您为特定类自定义数据结构(来自java.util.Collections)的情况下,例如使用位移将Collection&lt;Boolean&gt; 压缩到更少量的内存中。我希望这是有道理的!

    【讨论】:

      猜你喜欢
      • 2011-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-18
      相关资源
      最近更新 更多