10

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

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

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

简而言之,这是一个递归定义,没有明显的递归结束,我看不出编译器/类型系统如何实现这一点。

此外,尝试用语言表达(“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而后者又取决于方法列表。所以,没有递归。

4

3 回答 3

5

这是 Java 中一个众所周知的习语。

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

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

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

我见过很多这种模式的使用,在 99% 的情况下,他们不需要做上述操作。相反,在 99% 的情况下,人们想要做的是以某种方式表达T“等于” IComparable<T>,而上述模式并不能保证这一点(这在 Java 中是不可能表达的)。它只保证Textends IComparable<T>,但不保证IComparable<T>extends 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>无效。

于 2013-09-01T08:16:02.727 回答
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>. 这不是递归定义,而是子类捕获定义。

于 2013-08-31T23:02:15.687 回答
2

首先,关于您对复发的疑问:

这是某种重复(因为 X 是由与 X 相关的东西定义的),但即使是,它也只是一个级别。想象一下,编译器有一个函数define,它通过读取和编译源文件来定义它不知道的东西,以及一个实用函数get_list_of_methods。所以在ResultItem我们有的情况下:

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

为此,需要对内部进行定义ResultItem<T>。但是我们不需要知道关于 的所有内容ResultItem<T>,我们只想知道某事物 extends 的含义ResultItem<T>,因此首先:

2)get_list_of_methods(ResultItem<T>)

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

3)创建T将具有所有方法的通用约束保护ResultItem<T>

4) 继续步骤 1) - 解析源文件,获取ResultItem<T>. 从第 2 步知道正在实现哪些方法,T我们可以编译这些方法。

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

class X;

class Y
{
private:
  X* x;
};

class X
{
};

接下来,关于这个的可用性。从评论和相关主题中收集信息,我会声明这种机制用于表达关于实现给定接口的类的意图,并使接口更有用和方便。本主题更详细地解释它:如何使接口实例方法只接受同一类的参数?.

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

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

但是,它可以被解释为表达了这样一个事实,即一个实体comparable只能与相同类型的可比较项目进行比较。class X implements IComparable<Y>正如其他答案和评论中所指出的那样,这不是最好的例子,因为只要 Y 也具有可比性,您仍然可以编写。更多细节在这里:我怎样才能让接口实例方法只接受同一个类的参数,真的吗?.

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

于 2013-09-01T11:23:53.553 回答