关于java:circular泛型:接口IFoo <T扩展了IFoo <T >>

circular generics : interface IFoo <T extends IFoo <T>>

这是Java中一个众所周知的习惯用法。例如,参见此SO讨论。因此,基本上定义一个接口,实现该接口的类需要一种方法来与您的类的对象进行比较:

1
2
3
public interface IComparable<T extends IComparable< T >> {
    public int compare(T item);
}

(注意:这显然是解决特定用例的不必要复杂的方法-请参阅此文章-但我正在询问如何解释递归语法,请不要介意特定的应用程序)。

简而言之,这是一个递归定义,在递归中没有明显的结尾,我看不到编译器/类型系统如何实现此目的。

此外,尝试输入文字(" IComparable是一个其实例可以与实现IComparable接口的类的对象进行比较的类"),这会产生一个不合逻辑的循环定义,该定义在Philosophy中是不可能的,逻辑/数学,但显然在编译器设计中是可行的(!)。

此外,如果原始成语语法可以接受,那么似乎也可以这样说:

1
2
3
public interface IComparable<T extends IComparable<T extends IComparable< T >>> {
    public int compare(T item);
}

...但是编译器对此表示不满,显然只允许一个级别的extend

有人可以帮我弄清楚这种递归泛型定义吗?

更新

基于公认的答案(BartoszKP),我现在想知道的是以下内容:

"递归"定义不应被理解为(代表"定义取决于"关系的箭头):

1
[ IComparable< T > ] -------> [ IComparable< T > ]

...这是不合逻辑的(圆形),而是:

1
2
3
4
[ IComparable< T > ] ----------> [ list of methods (parameterized by T) ]
          \\                                      ^
           \\                                     |
            \\-------> [ type bound on T ]-------/

...这不是不合逻辑的,因此可以在编译器中使用。因此,换句话说,IComparable的定义是根据它定义的方法列表和它在类型T上定义的界限给出的,后者又取决于方法列表。因此,没有递归。


This is a well-known idiom in Java.

不。它没有用,因此不应使用。除了enum(这是一种特殊情况,因为enum类型是编译器生成的)之外,在官方Java库中的任何地方或任何官方Java文档中的任何地方都不会看到这种模式。

类型变量的泛型边界的目的是建立关系,以便泛型类或泛型方法中的代码可以使用该保证来执行某些操作而无需强制转换,这可能是不安全的。泛型边界应尽可能不受限制,同时仍可防止代码中的强制转换。

因此,绑定interface IComparable<T extends IComparable<T>>的唯一合法目的是,如果1)它实际上是一个类,而不是接口(接口没有代码,因此没有要避免的强制转换),以及2 )中的代码需要执行以下操作:使用方法使T脱离自身,然后调用需要IComparable<T>的方法。然后,保证T扩展IComparable<T>可以允许执行此操作而无需强制转换。

我已经看到了这种模式的许多用法,在99%的情况下,他们不需要执行上述操作。相反,在99%的时间中,人们想要做的是以某种方式表示T "等于" IComparable<T>,上述模式不能保证(并且用Java无法表达)。它仅保证T扩展IComparable<T>,但不能保证IComparable<T>扩展T

在大多数情况下,人们使用此模式时,会在内部执行(T)this的类中使用该模式。需要进行强制转换的事实表明这可能是不安全的。边界不能保证其类型安全(不能保证(IComparable<T>类型的this扩展了T))。实际上这是不安全的;一个众所周知的示例是class Foo extends IComparable<Foo>,然后是class Bar extends IComparable<Foo>,因此this(类型为Bar)不会扩展T(Foo)。

因此,如果您看到过该代码,几乎可以肯定会对它的功能有误解,并且应该几乎总是将其更改为:

1
2
3
public interface IComparable< T > {
    public int compare(T item);
}

作为练习,我挑战您找到一个片段,其中interface IComparable<T extends IComparable<T>>有效,而interface IComparable<T>不起作用。


实际上-您不需要递归定义。

1
2
3
4
5
6
7
8
9
10
public interface IComparable< T > {
  public int compare(T item);
}

public class Foo implements IComparable<Foo> {
  @Override
  public int compare(Foo o) {
    return 0;
  }
}

足够定义您要尝试的内容。

递归定义的常见地方是使用enum时。您经常会看到E extends Enum<E>,在这种情况下,这实际上很有用,因为enum类是用枚举的通用类型E定义的。

作为@NPE在引用的讨论中的帖子的叙述:

1
2
3
4
5
public interface ResultItem<T extends ResultItem< T >> {
    public int getConfidence();
    public boolean equals(T item);
    public T cloneWithConfidence(int newConfidence);
}

这里发生的是,您不是在按照特定的通用类T定义类,而是声明T必须扩展ResultItem,即它是ResultItem的任何子类。当然,由于ResultItem是泛型类,因此您必须为其指定泛型参数,在本例中为T本身。因此T extends ResultItem<T>。这不是递归定义,它是子类捕获定义。


首先,对于您对复发的怀疑:

这是某种形式的重复(因为X是由与X相关的东西定义的),但是即使是这样,它也只是一个级别。想象一下,编译器具有一个define函数,该函数通过读取和编译源文件来定义它尚不知道的内容,以及一个实用程序函数get_list_of_methods。因此,对于ResultItem,我们有:

1)define(interface ResultItem<T extends ResultItem<T>>)

为此,需要内部ResultItem<T>的定义。但是我们不需要了解有关ResultItem<T>的所有内容,我们只想知道扩展了ResultItem<T>的含义,因此首先:

2)get_list_of_methods(ResultItem<T>)

这将解析源文件,以获取ResultItem<T>所有方法的列表-我们现在不必编译它们。

3)创建通用约束,以保护T将具有ResultItem<T>

的所有方法

4)继续执行步骤1)-解析源文件,以获取ResultItem<T>的所有方法的定义。知道步骤2中T正在实现哪些方法时,我们可以编译这些方法。

实际上这可能更复杂,但这有助于看到不需要重复进行解释。因此,与具有接受X的方法的class X相比,它没有更多的重复出现。它只需要一个以上的通行证。在较旧的语言中,不可能使用这样的lookahead定义。即使在C语言中,您也需要前向声明:

1
2
3
4
5
6
7
8
9
10
11
class X;

class Y
{
private:
  X* x;
};

class X
{
};

接下来,关于它的可用性。从评论和相关主题中收集信息,我声明该机制用于表达实现给定接口的类的意图,并使该接口更加有用和方便。本主题对此进行了详细说明:如何使接口实例方法仅接受同一类的参数?。

在第二种情况下,实现该接口的类没有任何方便之处:

1
2
3
public interface IComparable<T extends IComparable< T >> {
  public int compare(T item);
}

但是,可以将其解释为表示以下事实:comparable的实体仅可与相同类型的可比较项进行比较。正如其他答案和评论所述,这不是最佳示例,因为只要Y也具有可比性,您仍然可以编写class X implements IComparable<Y>

在这里有更多详细信息:如何使接口实例方法只接受同一类的参数呢?

有关此习语用法的更多详细信息:http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ106。