36

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

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

  2. 我了解?. printAll(MyList<? extends Serializable>)意思是:“如果它有实现接口的对象,printAll就会打印MyListSerialzable。 ”
    我对super. printAll(MyList<? super MyClass>)意思是:“如果它有对象或任何扩展的类(的后代)printAll将打印。MyListMyClassMyClassMyClass

纠正我哪里出错了。

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


更新1:

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

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

所以,说如果MyClass看起来像:

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

然后,printAll()将打印如果
1.MyClass列表中
有 的对象 2. 有ThreadActionListener的对象List


更新 2:

因此,在阅读了该问题的许多答案之后,这是我的理解:

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

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

4

6 回答 6

19

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

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

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

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

我了解?. printAll(MyList<? extends Serializable>)意味着printAll 将打印 MyList 如果它有实现 Serialzable 接口的对象

更准确地说,这意味着只有当它被传递给带有某种泛型类型的 a 或 implements时,调用printAllMyListSerializable才会编译。在这种情况下,它将接受MyList<Serializable>,MyList<Integer>等。

我对super. printAll(MyList<? super MyClass>)意味着printAll 将打印 MyList 如果它有 MyClass 的对象或任何扩展 MyClass 的类(MyClass 的后代)

有界的通配符super下界。所以我们可以说printAll只有当它被传递给MyList带有某种泛型类型的aMyClass或某种超类型的MyClass. 所以在这种情况下,它会接受MyList<MyClass>,例如MyList<MyParentClass>,或MyList<Object>

所以,假设 MyClass 看起来像:

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

然后, printAll() 将打印 if

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

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

printAll(MyList<? super MyClass>)只有通过以下方式调用才会编译:

  1. MyList<MyClass>
  2. MyList<Thread>
  3. MyList<Runnable>
  4. MyList<ActionListener>
  5. MyList<EventListener>
  6. MyList<Object>
  7. MyList<? super X>其中X, MyClass, Thread, Runnable, ActionListener,EventListenerObject.

因此,在阅读了该问题的许多答案之后,这是我的理解:

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

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

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

于 2013-11-05T18:20:42.587 回答
16

首先Tor Eor Kor what 不是固定的名字。它们只是类型变量,由您决定它们的名称。T, E,K只是示例,但您可以调用它Foo或其他名称。

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

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

  • ? extends T: 一个未知类型,它是T
  • ? super T: 一个未知类型,它是T

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

于 2013-11-05T18:17:46.027 回答
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>();

当谈到extendsvssuper这被称为协方差 vs 逆变。它确定允许沿类层次结构提供的类型移动的方向:

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- 您可以从中假设的最严格的界限CollectionRunnable,因为用户可以传递其继承层次结构中Collection的任何内容。Runnable

后两个示例演示- 您可以从中假设super最严格限制是CollectionObject因为我们允许.Runnable

于 2013-11-05T18:24:17.823 回答
1

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

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类层次结构的简单示例。Float并且Integer是 的孩子Number。你可以这样写你的方法:

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

然后您可以使用以下命令调用该方法List<Number>

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);
}
于 2013-11-05T18:18:16.773 回答
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()); 
于 2019-11-29T15:23:01.467 回答
0

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

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

于 2013-11-05T18:19:06.017 回答