【问题标题】:What is the difference between 'E', 'T', and '?' for Java generics?“E”、“T”和“”有什么区别?对于 Java 泛型?
【发布时间】:2023-03-17 18:34:01
【问题描述】:

我遇到过这样的 Java 代码:

public interface Foo<E> {}

public interface Bar<T> {}

public interface Zar<?> {}

以上三者之间有什么区别,他们在 Java 中称这种类型的类或接口声明是什么?

【问题讨论】:

    标签: java generics


    【解决方案1】:

    前两者之间没有区别 - 他们只是为 type 参数使用不同的名称ET)。

    第三个不是有效的声明 - ? 用作提供类型 参数 时使用的 通配符,例如List&lt;?&gt; foo = ... 表示foo 指的是某种类型的列表,但我们不知道是什么。

    所有这些都是泛型,这是一个相当大的话题。您可能希望通过以下资源了解它,当然还有更多可用资源:

    【讨论】:

    • 看起来 PDF 的链接已损坏。我发现似乎是一个副本 here,但我不能 100% 确定,因为我不知道原件是什么样子。
    • @John:是的,就是这样。将编辑一个链接,无论是那个链接还是 Oracle 链接...
    • 除了 T、E 和 之外还有什么?在泛型中使用?如果是这样,它们是什么,它们是什么意思?
    • @sofs1:TE 没有什么特别之处——它们只是标识符。例如,您可以写 KeyValuePair&lt;K, V&gt;? 有特殊含义。
    • @JonSkeet “它们只是标识符”,这意味着它们必须遵循与类名、字段名、方法名等相同的名称约束。 Foo&lt;hello_world&gt; 有效。使用单个大写字母是一种命名标准,在Java Language Specification 中推荐:“类型变量名称应该简洁(如果可能的话,使用单个字符)但令人回味,并且不应包含小写字母。这使得很容易将类型参数与普通的类和接口区分开来。”
    【解决方案2】:

    它比其他任何东西都更传统。

    • T 是一个类型
    • E 是一个元素(List&lt;E&gt;:元素列表)
    • K 是 Key(在 Map&lt;K,V&gt; 中)
    • V 是 Value(作为返回值或映射值)

    它们是完全可以互换的(尽管在同一声明中存在冲突)。

    【讨论】:

    • 之间的字母只是一个名字。您在回答中描述的只是约定。它甚至不必是一个大写字母。你可以使用任何你喜欢的名字,就像你可以给类、变量等任何你喜欢的名字一样。
    • 这篇文章有更详细和清晰的描述oracle.com/technetwork/articles/java/…
    • 你没有对问号解释。投反对票。
    • 为什么还是这个答案?
    【解决方案3】:

    前面的答案解释了类型参数(T、E 等),但没有解释通配符“?”或它们之间的区别,所以我会解决这个问题。

    首先要明确一点:通配符和类型参数是不一样的。类型参数定义了一种表示作用域类型的变量(例如 T),而通配符没有:通配符只定义了一组可用于泛型类型的允许类型。没有任何界限(extendssuper),通配符的意思是“在此处使用任何类型”。

    通配符总是在尖括号之间,它只在泛型类型的上下文中有意义:

    public void foo(List<?> listOfAnyType) {...}  // pass a List of any type
    

    从不

    public <?> ? bar(? someType) {...}  // error. Must use type params here
    

    public class MyGeneric ? {      // error
        public ? getFoo() { ... }   // error
        ...
    }
    

    它们重叠的地方会变得更加混乱。例如:

    List<T> fooList;  // A list which will be of type T, when T is chosen.
                      // Requires T was defined above in this scope
    List<?> barList;  // A list of some type, decided elsewhere. You can do
                      // this anywhere, no T required.
    

    方法定义有很多重叠之处。以下在功能上是相同的:

    public <T> void foo(List<T> listOfT) {...}
    public void bar(List<?> listOfSomething)  {...}
    

    那么,如果有重叠,为什么要使用其中一个呢?有时,这只是风格:有人说如果您不需要 类型参数,则应该使用通配符以使代码更简单/更具可读性。我在上面解释的一个主要区别:类型参数定义了一个类型变量(例如,T),您可以在范围内的其他地方使用它;通配符没有。否则,类型参数和通配符之间有两个很大的区别:

    类型参数可以有多个边界类;通配符不能:

    public class Foo <T extends Comparable<T> & Cloneable> {...}
    

    通配符可以有下界;类型参数不能:

    public void bar(List<? super Integer> list) {...}
    

    在上面List&lt;? super Integer&gt;Integer 定义为通配符的下限,这意味着List 类型必须是Integer 或Integer 的超类型。泛型类型边界超出了我想要详细介绍的范围。简而言之,它允许您定义泛型类型可以是哪些类型。这使得多态处理泛型成为可能。例如。与:

    public void foo(List<? extends Number> numbers) {...}
    

    您可以为numbers 传递List&lt;Integer&gt;List&lt;Float&gt;List&lt;Byte&gt; 等。没有类型限制,这将无法工作——泛型就是这样。

    最后,这是一个方法定义,它使用通配符来做一些我认为你不能用其他方式做的事情:

    public static <T extends Number> void adder(T elem, List<? super Number> numberSuper) {
        numberSuper.add(elem);
    }
    

    numberSuper 可以是数字列表或数字的任何超类型(例如,List&lt;Object&gt;),elem 必须是数字或任何子类型。有了所有的边界,编译器就可以确定.add() 是类型安全的。

    【讨论】:

    • "public void foo(List extends Number> numbers) {...}" 应该 "extends" 是 "super" 吗?
    • 没有。该示例的重点是显示多态支持数字列表和数字子类型的签名。为此,您使用“扩展”。即,“给我一个数字列表任何扩展数字的东西”(List、List 等等)。然后,这样的方法可能会遍历列表,并且对于每个元素“e”,执行例如 e.floatValue()。不管你传递的是 Number 的什么子类型(扩展名)——你总是能够“.floatValue()”,因为 .floatValue() 是 Number 的一种方法。
    • 在您的最后一个示例中,“List super Number>”可能只是“List”,因为该方法不允许任何更通用的内容。
    • @jessarah 不。也许我的示例不清楚,但我在示例中提到 adder() 可以采用 List (Object 是 Number 的超类)。如果您希望它能够做到这一点,它必须具有签名“List”。这正是这里“超级”的意义所在。
    • 这个答案很好地解释了通配符和类型参数之间的区别,这个答案应该有一个专门的问题。我最近对泛型进行了更深入的研究,这个答案帮助我把东西放在一起,简而言之,很多精确的信息,谢谢!
    【解决方案4】:

    类型变量 可以是您指定的任何非原始类型:任何类类型、任何接口类型、任何数组类型,甚至是另一个类型变量。

    最常用的类型参数名称有:

    • E - 元素(Java 集合框架广泛使用)
    • K - 钥匙
    • N - 编号
    • T - 类型
    • V - 值

    在 Java 7 中允许这样实例化:

    Foo<String, Integer> foo = new Foo<>(); // Java 7
    Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6
    

    【讨论】:

      【解决方案5】:

      最常用的类型参数名称有:

      E - Element (used extensively by the Java Collections Framework)
      K - Key
      N - Number
      T - Type
      V - Value
      S,U,V etc. - 2nd, 3rd, 4th types
      

      您将看到这些名称在整个 Java SE API 中使用

      【讨论】:

        【解决方案6】:

        编译器在组成如下函数时会生成一个capture for each wildcard(例如,List 中的问号):

        foo(List<?> list) {
            list.put(list.get()) // ERROR: capture and Object are not identical type.
        }
        

        但是像 V 这样的泛型类型也可以,并使其成为 泛型方法

        <V>void foo(List<V> list) {
            list.put(list.get())
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-10-15
          • 2015-05-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-08-29
          • 1970-01-01
          • 2020-01-02
          相关资源
          最近更新 更多