了解上限和下限? 在Javagenerics中

我真的很难理解外卡参数。 我有几个问题。

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

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

在我出错的地方纠正我。

简而言之,只有TEKVN可用作定义generics类的类型参数。 ? 只能用于方法


更新1:

 public void printAll(MyList){ // code code code } 

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

所以,如果MyClass看起来像:

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

然后, printAll()将打印if
1.列表中有MyClass对象
2. ListThreadActionListener对象


更新2:

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

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

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

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

通配符( ? )不是forms类型参数,而是可以用作类型参数 。 在你给出的例子中, ? extends Serializable ? extends Serializable作为printAll方法参数的generics类型printAll的类型参数给出。

方法也可以声明像类这样的类型参数 ,例如:

 static  void printAll(MyList myList) 

我明白了上限?printAll(MyList)表示如果printAll具有实现Serialzable接口的对象,则printAll将打印MyList

更准确地说,这意味着printAll的调用只有在传递printAll才会编译 ,该printAll具有一些或实现Serializablegenerics类型 。 在这种情况下,它将接受MyListMyList等。

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

super绑定的通配符是下限 。 所以我们可以说printAll的调用只有在传递printAll才会编译,其中printAll类型为MyClassMyClass超类型。 所以在这种情况下它会接受MyList MyList ,例如MyListMyList MyList

所以,如果MyClass看起来像:

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

然后,printAll()将打印if

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

你走在正确的轨道上。 但我认为例如“如果列表中有MyClass对象将会打印”是有问题的。 这听起来像是在定义运行时行为 – generics都是关于编译时检查的。 例如,无法将MyList作为MyList MyList的参数传递MyList MyList ,即使它可能包含inheritance的MyClass实例。 我会改写它:

printAll(MyList)的调用只有在传递给它时才会编译:

  1. MyList
  2. MyList
  3. MyList
  4. MyList
  5. MyList
  6. MyList
  7. MyList MyList其中XMyClassThreadRunnableActionListenerEventListenerObject

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

? extends T ? extends T表示任何扩展T的类 。 因此,我们指的是T的子女。因此, T是上界。 inheritance层次结构中最上层的类

? super T ? super T表示任何? super T类/接口。因此,我们指的是T. T的所有父类, 因此是下限。 inheritance层次结构中最低级的类

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

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

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

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

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

当你想要接受一个与T兼容的类型(因此T是一个那种类型)时,后者会很有用。 这里可以找到一个实际的例子。

让我们从头开始。

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

 public static final class MyGenericClass { } 

是完全有效的Java。

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

 public static final class MyGenericClass { private final Collection myThings; public MyGenericClass(Collection myThings) { this.myThings = myThings; } public void doStuff(final Collection myThings) { } } 

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

 final Collection myThings = new ArrayList(); 

当涉及到extendssuper这称为协方差与反方差。 它确定允许的类层次结构中允许的行进方向:

 final Collection example1 = new ArrayList(); final Collection example2 = new ArrayList(); final Collection example3 = new ArrayList(); final Collection example4 = new ArrayList(); 

前两个示例演示了extends – 您可以从Collection的最严格的绑定是Runnable因为用户可以在其inheritance层次结构中传递任何具有RunnableCollection

第二个示例演示了super – 您可以从Collection is Object假设的最严格的边界,因为我们允许Runnable的inheritance层次结构中的任何内容。

嗯你的super声明( printAll(MyList)printAll(MyList) )并不清楚。 这意味着,假设Myclass扩展了Object ,你可以printAll(MyList) ,你可以printAll(MyList)但没有别的……这意味着printAll(MyList)的generics类型必须是超类(不是MyClass的子类。 这跟你说的不一样。

至于T,E,K,V或N,它们本身就是毫无意义的名称。 你可以使用你想要的任何东西。 惯例建议使用单字母大写值,T通常用于generics方法,E用于类….

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

 void  foo() {} 

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

 void foo(List e) {} 

你也可以写课:

 public class Bar> { } 

对于super的使用:

 public void printAll(MyList){ // code code code } 

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

要了解它,请考虑使用Number类层次结构的简单示例。 FloatIntegerNumber子元素。 你可以这样写你的方法:

 public void printAll(List){ // code code code } 

然后,您可以使用List调用该方法:

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

在这种情况下,这可能不会非常有用。 例如,当您想要将Float添加到集合而不希望它只是List时,它会有用,例如:

 public void addFloat(List list){ list.add(2.5); } 

? 也可以用作方法返回类型

 List xxx() { ... } 

或作为字段或变量类型

 List xxx = ... List xxx = ...