【问题标题】:Understanding upper and lower bounds on ? in Java Generics了解上界和下界?在 Java 泛型中
【发布时间】:2013-11-16 17:22:04
【问题描述】:

我真的很难理解通配符参数。我对此有几个问题。

  1. ? 作为类型参数只能在方法中使用。例如:printAll(MyList<? extends Serializable>) 我不能用? 定义类作为类型参数。

  2. 我了解? 的上限。 printAll(MyList<? extends Serializable>) 表示:“printAll 将打印 MyList,如果它有实现 Serialzable 接口的对象。
    我对super 有点问题。 printAll(MyList<? super MyClass>) 表示:“printAll 将打印 MyList,如果它有 MyClass 或任何扩展 MyClass 的类(MyClass 的后代)的类。

纠正我哪里出错了。

简而言之,只有TEKVN 可以用作定义泛型类的类型参数。 ? 只能在方法中使用


更新 1:
public void printAll(MyList<? super MyClass>){
    // code code code
}

根据 Ivor Horton 的书,MyList&lt;? super MyClass&gt; 意味着我可以打印 MyList,如果它具有 MyClass 的对象或其实现的任何接口或类。也就是说,MyClass 是一个下限。它是继承层次结构中的最后一个类。这意味着我最初的假设是错误的。

所以,假设MyClass 看起来像:

public class MyClass extends Thread implements ActionListener{
    // whatever
}

那么,printAll() 将打印 if
1.列表中有MyClass的对象
2.List中有ThreadActionListener的对象


更新 2:

所以,在阅读了这个问题的许多答案之后,这是我的理解:

  1. ? extends T 表示任何扩展 T 的类。因此,我们指的是T子代。因此,T 是上限。继承层次结构中最上层的类

  2. ? super T 表示 supersuper 的任何类/接口。因此,我们指的是T 的所有父母。 T 因此是下限。继承层次结构中最底层的类

【问题讨论】:

  • 几乎可以肯定你检查过这个,但如果你还没有,this link 可以为你提供一些启示。
  • A MyList&lt;? super MyClass&gt; 也将接受 MyList&lt;java.lang.Object&gt;。不要拘泥于“列表中”的内容。声明是关于可以传递哪些类型。由于printAll(MyList&lt;? super MyClass&gt;) 也将接受MyList&lt;Object&gt;,因此MyList&lt;Object&gt; 甚至可能包含JButton,因为JButton 实例也是Object 的实例。
  • @Holger MyClass 的所有超类/接口开始掌握它。

标签: java generics subclass superclass


【解决方案1】:

下界说:你可以使用'super'关键字后面提到的类,或者它的任何超类型。这可能会变得棘手。这些超类型可能有其他(完全不同的)类从它们继承,如下例所示。

说,

List<? super Car> cars = new ArrayList <Vehicle>();

是否应该允许程序员写:

cars.add(new Helicopter()); //Helicopter is a kind of Vehicle

这显然是不允许的,它反映了使用下限的危险。

应允许程序员将车辆添加到列表中,但不能添加任何车辆。 他必须强制转换它,让 Java 知道他毕竟只是添加了一个 Car Vehicle,就像这样:

cars.add((Car) new Vehicle()); 

【讨论】:

    【解决方案2】:

    ? 作为类型参数只能在方法中使用。例如:printAll(MyList&lt;? extends Serializable&gt;) 我不能用? 定义类作为类型参数。

    通配符 (?) 不是正式的类型参数,而是可以用作类型参数。在您给出的示例中,? extends Serializable 作为printAll 方法参数的泛型类型MyList 的类型参数给出。

    方法can also declare type parameters类似于类,例如:

    static <T extends Serializable> void printAll(MyList<T> myList)
    

    我了解? 的上限。 printAll(MyList&lt;? extends Serializable&gt;) 表示 printAll 将打印 MyList 如果它有实现 Serialzable 接口的对象

    更准确地说,这意味着printAll 的调用只有在传递MyList 时才能编译,该泛型类型是或实现Serializable。在这种情况下,它将接受 MyList&lt;Serializable&gt;MyList&lt;Integer&gt; 等。

    super 有点问题。 printAll(MyList&lt;? super MyClass&gt;) 表示 printAll 将打印 MyList 如果它有 MyClass 的对象或任何扩展 MyClass 的类(MyClass 的后代)

    super 为界的通配符是下限。所以我们可以说printAll 的调用只有在传递MyList 且具有某种泛型类型MyClass 或某种超类型MyClass 时才会编译。所以在这种情况下,它会接受MyList&lt;MyClass&gt;,例如MyList&lt;MyParentClass&gt;,或MyList&lt;Object&gt;

    所以,假设 MyClass 看起来像:

    public class MyClass extends Thread implements ActionListener{
        // whatever
    }
    

    那么,printAll() 将打印 if

    1. 列表中有MyClass的对象
    2. 列表中有Thread或ActionListener的对象

    你在正确的轨道上。但我认为说例如“如果列表中有MyClass 的对象,它将打印”是有问题的。这听起来像是在定义运行时行为——泛型都是关于编译时检查的。例如,不能通过继承将MyList&lt;MySubclass&gt; 作为MyList&lt;? super MyClass&gt; 的参数传递,即使它可能包含MyClass 的实例。我将其改写为:

    printAll(MyList&lt;? super MyClass&gt;) 的调用只有在传递以下参数时才会编译:

    1. MyList&lt;MyClass&gt;
    2. MyList&lt;Thread&gt;
    3. MyList&lt;Runnable&gt;
    4. MyList&lt;ActionListener&gt;
    5. MyList&lt;EventListener&gt;
    6. MyList&lt;Object&gt;
    7. MyList&lt;? super X&gt; 其中XMyClassThreadRunnableActionListenerEventListenerObject

    所以,在阅读了这个问题的许多答案之后,这是我的 理解:

    ? extends T 表示任何扩展 T 的类。因此,我们指的是 T 的孩子。因此,T 是上限。最高等级 在继承层次结构中

    ? super T 表示 T 的 super 的任何类/接口。因此我们是 指的是 T 的所有父母。因此,T 是下界。这 继承层次结构中最底层的类

    关闭,但我不会说“T 的孩子”或“T 的父母”,因为这些界限包含 - 说“@987654372”会更准确@ 或其子类型”和“T 或其超类型”。

    【讨论】:

      【解决方案3】:

      让我们从头开始。

      严格来说任何有效的java标识符都可以用作泛型类型参数——它只是一种特殊类型的变量:

      public static final class MyGenericClass<MyGenericType> {
      
      }
      

      是完全有效的 Java。

      接下来,您可以在任何可以声明的地方使用?。您可以在声明变量时使用通配符,但在实例化它们时不能使用:

      public static final class MyGenericClass {
          private final Collection<? extends String> myThings;
      
          public MyGenericClass(Collection<? extends String> myThings) {
              this.myThings = myThings;
          }  
      
          public void doStuff(final Collection<? extends String> myThings) {
      
          }
      }
      

      再次全部有效,您不能这样做:

      final Collection<? extends String> myThings = new ArrayList<? extends String>();
      

      当谈到 extendssuper 时,这称为协方差与反方差。它确定允许沿类层次结构提供的类型移动的方向:

      final Collection<? extends Runnable> example1 = new ArrayList<Runnable>();
      final Collection<? extends Runnable> example2 = new ArrayList<TimerTask>();
      final Collection<? super Runnable> example3 = new ArrayList<Runnable>();
      final Collection<? super Runnable> example4 = new ArrayList<Object>();
      

      前两个示例演示了extends - 您可以从Collection 假设的最严格的界限是Runnable,因为用户可以传递Collection 任何在其继承层次结构中具有Runnable 的东西。

      后两个示例演示了super - 您可以从Collection 中假设的最严格的界限是Object,因为我们允许Runnable 的继承层次结构中的任何内容。

      【讨论】:

        【解决方案4】:

        对于第一个问题:你也不能用? 作为类型参数来定义一个方法。以下将无法编译:

        void <?> foo() {}
        

        ? 用于绑定到另一个泛型而不提供类型参数。你可以为方法写:

        void foo(List<?> e) {}
        

        你也可以为课程写作:

        public class Bar<E extends List<?>> { }
        

        super使用:

        public void printAll(MyList<? super MyClass>){
            // code code code
        }
        

        这不会像你说的那样打印列表“如果它有 MyClass 的对象”。它可以具有任何类的对象,这些类是 MyClass 的父类的子类。编译器在编译时并不知道列表中的对象是什么。

        要了解它,请考虑一个具有Number 类层次结构的简单示例。 FloatIntegerNumber 的子代。你可以这样写你的方法:

        public void printAll(List<? super Float>){
            // code code code
        }
        

        然后您可以使用List&lt;Number&gt; 调用该方法:

        List<Number> numbers = new ArrayList<>();
        numbers.add(1); // actually only add an Integer
        printAll(numbers); // compiles.
        

        在这种情况下,这可能不会超级有用。例如,当您想将 Float 添加到集合而不希望它只是一个列表时,它会很有用,例如:

        public void addFloat(List<? super Float> list){
            list.add(2.5);
        }
        

        【讨论】:

        • 嗨@cyrille-ka,你确定public void addFloat(List&lt;? super Float&gt; list){ list.add(2.5); } 吗?它不会编译,但public void addFloat(List&lt;? super Number&gt; list){ list.add(2.5); } 可以,因为它保证了列表项的一致性
        【解决方案5】:

        嗯,您对super (printAll(MyList&lt;? super MyClass&gt;)) 的陈述不清楚。这意味着什么,假设 Myclass 扩展 Object 是您可以 printAll(MyList&lt;MyClass&gt;) 并且您可以 printAll(MyList&lt;Object&gt;) 但仅此而已......这意味着 MyList 的泛型类型必须是 MyClass 的超类(而不是子类) .这和你说的不一样。

        至于 T、E、K、V 或 N,这些本身就是毫无意义的名称。你可以使用任何你想要的东西。不过,约定建议使用单字母大写值,并且 T 通常用于泛型方法,而 E 用于类......

        【讨论】:

          【解决方案6】:

          首先是TEK 或其他非固定名称。它们只是类型变量,由您决定它们的名称。 TEK 只是示例,但您可以将其称为 Foo 或其他名称。

          现在开始您的第一个问题:由于通配符 ? 代表“任何和未知”类型,即未指定类型,因此在未指定类型上声明泛型类没有任何意义。当您不关心类型时,在方法的参数或变量中使用通配符很有用。

          现在关于您的第二个问题:下限为您的通用方法提供了更大的灵活性。 extendssuper 都是相反的:

          • ? extends T:未知类型,它是T 的子类型
          • ? super T:未知类型,它是T 的超类型

          当您想要接受与 T 兼容的类型时,后者可能很有用(这样 T 就是那个类型)。一个实际的例子可以找到here

          【讨论】:

          • 更新 2 是在反复阅读您的答案几次后发布的。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多