繁体   English   中英

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

[英]Understanding upper and lower bounds on ? in Java Generics

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

  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<? super MyClass> MyList<? super MyClass>表示如果MyList具有MyClass对象或它实现的任何接口或类,我可以打印它。 也就是说, MyClass是一个下界 它是继承层次结构中的最后一个类。 这意味着我最初的假设是错误的。

所以,假设MyClass看起来像:

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

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


更新 2:

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

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

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

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

通配符 ( ? ) 不是正式的类型参数,而是可以用作类型参数 在你给出的例子中, ? extends Serializable ? extends Serializable被给定为一个类型参数的一般类型MyList ,所述的printAll方法的参数。

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

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

我了解上限? . printAll(MyList<? extends Serializable>)表示如果具有实现 Serialzable 接口的对象,printAll 将打印 MyList

更准确地说,它意味着一个呼叫printAll如果它传递一个只会编译MyList有一些通用的类型,它是或实现Serializable 在这种情况下,它将接受MyList<Serializable>MyList<Integer>等。

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

super为界的通配符是下界 因此,我们可以说一个呼叫printAll如果它传递一个只会编译MyList有一些通用的类型为MyClass或一些超级型MyClass 因此,在这种情况下,它将接受MyList<MyParentClass> MyList<MyClass> ,例如MyList<MyParentClass>MyList<MyParentClass> MyList<Object>

所以,假设 MyClass 看起来像:

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

然后,printAll() 将打印如果

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

你在正确的轨道上。 但是我认为说例如“如果列表中有MyClass对象,它就会打印”是有问题的。 这听起来像是在定义运行时行为 - 泛型都是关于编译时检查的。 例如,不能将MyList<MySubclass>作为参数传递给MyList<MySubclass> MyList<? super MyClass> 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> MyList<? super X>其中XMyClassThreadRunnableActionListenerEventListenerObject

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

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

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

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

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

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

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

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

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

让我们从头开始。

严格来说,任何有效的 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因为用户可以传递在其继承层次结构中具有Runnable的任何东西的Collection

第二个两个示例演示super -最严格的约束,你可以从假设CollectionObject ,因为我们允许任何处于继承层次Runnable

对于第一个问题:你不能用? 作为类型参数。 以下不会编译:

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<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);
}

下限表示:您可以使用在 '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()); 

嗯,您对super ( printAll(MyList<? super MyClass>) ( printAll(MyList<? super MyClass>) ) 的声明不清楚。 这意味着,假设 Myclass 扩展Object是你可以printAll(MyList<MyClass>)并且你可以printAll(MyList<Object>)但没有别的......这意味着printAll(MyList<Object>)的泛型类型必须是一个超类(不是 MyClass 的子类)。 这和你说的不一样。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM