循环泛型:接口IFoo <t extends =“”ifoo =“”<t =“”>&gt; </t>

时间:2013-08-31 22:49:57

标签: java generics

这是Java中一个众所周知的习语。例如,请参阅this SO discussion。所以基本上定义一个接口,实现它的类需要有一个方法来与你的类的对象进行比较:

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

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

简而言之,这是一个递归定义,在递归中没有明显的结束,我也看不到编译器/类型系统如何将其关闭。

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

此外,似乎如果原始的成语语法是可接受的,那么也可以允许一个人说:

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

...但编译器显然只允许extend的一个级别。

任何人都可以帮助我理解这种递归的通用定义吗?

更新

根据接受的答案(BartoszKP)我认为我现在理解的是:

不应将“递归”定义读作(表示“定义取决于”关系的箭头):

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

......这是不合逻辑的(循环的),而是:

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

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

3 个答案:

答案 0 :(得分:5)

  

这是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课程。需要进行演员表明这可能是不安全的;边界不保证其类型安全性(不保证this(类型IComparable<T>)扩展T)。它实际上是不安全的;一个众所周知的例子是class Foo extends IComparable<Foo>,然后是class Bar extends IComparable<Foo>,因此this(类型Bar)不会延伸TFoo

因此,如果您看到该代码,几乎可以肯定地写了一些关于它的作用的误解,并且它应该几乎总是被改为:

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

作为练习,我向您挑战,找到interface IComparable<T extends IComparable<T>>工作的代码段,以及interface IComparable<T>不起作用的地方。

答案 1 :(得分:3)

实际上 - 在你的情况下你不需要递归定义。

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关于引用讨论的帖子的叙述:

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>。这不是递归定义,而是子类捕获定义。

答案 2 :(得分:1)

首先,关于你对复发的疑虑:

这是某种复发(因为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>的所有方法的定义。知道了T从步骤2开始实现的方法,我们可以编译方法。

可能实际上这更复杂,但这有助于看到解释这一点不需要再发生。所以它不再是class X有一个接受X的方法。它只需要一次通过。在较旧的语言中,这种lookahead定义不可能。即使在C ++中,您现在也需要前向声明:

class X;

class Y
{
private:
  X* x;
};

class X
{
};

接下来,关于这个的可用性。从评论和相关主题收集信息,我声明这个机制用于表达实现给定接口的类的意图,并使界面更有用和方便。本主题更详细地解释了它:How can I make an interface instance method accept arguments of the same class only?

在第二种情况下,实现接口的类的方便性没有增加:

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

但是,它可以被解释为表达这样一个事实:comparable的实体仅与相同类型的可比较项目具有可比性。然而,正如其他答案和评论中所指出的那样,它不是最好的例子,因为只要Y也具有可比性,您仍然可以写class X implements IComparable<Y>。更多细节:How can I make an interface instance method accept arguments of the same class only, really?

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