了解上界和下界?在 Java 泛型中

Posted

技术标签:

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

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

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

    我了解? 的上限。 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:

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

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

    ? 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 的所有超类/接口开始掌握它。 Why can't a Java type parameter have a lower bound?的可能重复 【参考方案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

    列表中有MyClass的对象 列表中有Thread或ActionListener的对象

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

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

    MyList&lt;MyClass&gt; MyList&lt;Thread&gt; MyList&lt;Runnable&gt; MyList&lt;ActionListener&gt; MyList&lt;EventListener&gt; MyList&lt;Object&gt; 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 是在反复阅读您的答案几次后发布的。

以上是关于了解上界和下界?在 Java 泛型中的主要内容,如果未能解决你的问题,请参考以下文章

java泛型中extends 和 super的区别

Java中泛型中的几个符号

java泛型中的下限

java泛型中的上下界通配符

Scala的泛型

Java泛型和内部类