【问题标题】:circular generics : interface IFoo <T extends IFoo <T>>循环泛型:接口 IFoo <T extends IFoo <T>>
【发布时间】:2013-08-31 22:49:57
【问题描述】:

这是 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 上定义的边界给出的,而后者又取决于方法列表。所以,没有递归。

【问题讨论】:

  • 我不明白你为什么认为这是 recursive 或 circular... 阅读 OldCurmudgeon 的答案,它清楚地解释了它的原因不是。
  • 您的第二个代码示例混淆了T 的声明和使用。不幸的是,C 行语法故意使两者看起来相似。
  • "这是 Java 中众所周知的成语。"它并不为人所知。而且没用。

标签: java generics


【解决方案1】:

这是 Java 中众所周知的成语。

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

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

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

我见过很多这种模式的使用,在99%的情况下,他们不需要做上述操作。相反,在 99% 的情况下,人们想要做的是以某种方式表达 T“等于”IComparable&lt;T&gt;,上述模式并不能保证这一点(这在 Java 中是不可能表达的)。它只保证T 扩展IComparable&lt;T&gt;,但不保证IComparable&lt;T&gt; 扩展T。

大多数情况下,人们使用这种模式时,他们会将它用于他们在其中执行(T)this 的类。需要演员表的事实表明这可能是不安全的;边界不保证其类型安全(不能保证this(IComparable&lt;T&gt; 类型)扩展T)。它实际上是不安全的;一个著名的例子是class Foo extends IComparable&lt;Foo&gt;,然后是class Bar extends IComparable&lt;Foo&gt;,因此this(Bar 类型)不会扩展T(Foo)。

因此,如果您看到该代码,几乎可以肯定它是在对它的作用有些误解的情况下编写的,并且几乎总是应该更改为:

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

作为一个练习,我挑战你找到一个sn-p,其中interface IComparable&lt;T extends IComparable&lt;T&gt;&gt; 有效,而interface IComparable&lt;T&gt; 无效。

【讨论】:

    【解决方案2】:

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

    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&lt;E&gt;,在这种情况下,它实际上很有意义,因为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&lt;T&gt;。这不是递归定义,而是子类捕获定义。

    【讨论】:

    • 是的,我知道,我刚刚发布了:stackoverflow.com/questions/18553621/… 我只是想提供一个简单的例子来理解这种递归定义。
    • 这很好地解释了为什么术语 recursive 在此对话中无关紧要。 +1
    • 在引用的讨论中,ResultItem 也应该声明为public interface ResultItem&lt;T&gt;。 extends ResultItem&lt;T&gt; 没有添加任何内容。
    【解决方案3】:

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

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

    1)define(interface ResultItem&lt;T extends ResultItem&lt;T&gt;&gt;)

    为此,需要定义内部ResultItem&lt;T&gt;。但是我们不需要知道ResultItem&lt;T&gt; 的所有内容,我们只想知道扩展ResultItem&lt;T&gt; 的含义,因此首先:

    2)get_list_of_methods(ResultItem&lt;T&gt;)

    这会解析源文件,获取ResultItem&lt;T&gt; 的所有方法的列表 - 我们现在不必编译它们。

    3) 创建通用约束保护,T 将拥有ResultItem&lt;T&gt; 的所有方法

    4) 继续步骤 1) - 解析源文件,获取 ResultItem&lt;T&gt; 的所有方法的定义。从第 2 步了解T 正在实现哪些方法,我们可以编译这些方法。

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

    class X;
    
    class Y
    {
    private:
      X* x;
    };
    
    class X
    {
    };
    

    接下来,关于这个的可用性。从 cmets 和相关主题中收集信息,我会声明这种机制用于表达实现给定接口的类的意图,并使接口更加有用和方便。本主题更详细地解释它: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 的实体只能与相同类型的可比较项进行比较。正如其他答案和 cmets 中所指出的,这不是最好的例子,因为只要 Y 也具有可比性,您仍然可以写 class X implements IComparable&lt;Y&gt;。更多细节在这里: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。

    【讨论】:

      猜你喜欢
      • 2020-08-22
      • 2013-11-06
      • 2015-06-14
      • 2013-08-13
      • 2018-10-04
      • 2022-12-17
      • 2016-10-11
      • 2013-08-29
      • 1970-01-01
      相关资源
      最近更新 更多