了解上界和下界?在 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
的后代)的类。”
纠正我哪里出错了。
简而言之,只有T
或E
或K
或V
或N
可以用作定义泛型类的类型参数。 ?
只能在方法中使用
更新 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()
将打印 if
1.列表中有MyClass
的对象
2.List
中有Thread
或ActionListener
的对象
更新 2:
所以,在阅读了这个问题的许多答案之后,这是我的理解:
? extends T
表示任何扩展 T
的类。因此,我们指的是T
的子代。因此,T
是上限。继承层次结构中最上层的类
? super T
表示 super
的 super
的任何类/接口。因此,我们指的是T
的所有父母。 T
因此是下限。继承层次结构中最底层的类
【问题讨论】:
几乎可以肯定你检查过这个,但如果你还没有,this link 可以为你提供一些启示。 AMyList<? super MyClass>
也将接受 MyList<java.lang.Object>
。不要拘泥于“列表中”的内容。声明是关于可以传递哪些类型。由于printAll(MyList<? super MyClass>)
也将接受MyList<Object>
,因此MyList<Object>
甚至可能包含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<? extends Serializable>)
我不能用?
定义类作为类型参数。
通配符 (?
) 不是正式的类型参数,而是可以用作类型参数。在您给出的示例中,? extends Serializable
作为printAll
方法参数的泛型类型MyList
的类型参数给出。
方法can also declare type parameters类似于类,例如:
static <T extends Serializable> void printAll(MyList<T> myList)
我了解
?
的上限。printAll(MyList<? extends Serializable>)
表示 printAll 将打印 MyList 如果它有实现 Serialzable 接口的对象
更准确地说,这意味着对printAll
的调用只有在传递MyList
时才能编译,该泛型类型是或实现Serializable
。在这种情况下,它将接受 MyList<Serializable>
、MyList<Integer>
等。
super
有点问题。printAll(MyList<? super MyClass>)
表示 printAll 将打印 MyList 如果它有 MyClass 的对象或任何扩展 MyClass 的类(MyClass 的后代)
以super
为界的通配符是下限。所以我们可以说对printAll
的调用只有在传递MyList
且具有某种泛型类型MyClass
或某种超类型MyClass
时才会编译。所以在这种情况下,它会接受MyList<MyClass>
,例如MyList<MyParentClass>
,或MyList<Object>
。
所以,假设 MyClass 看起来像:
public class MyClass extends Thread implements ActionListener // whatever
那么,printAll() 将打印 if
列表中有MyClass的对象 列表中有Thread或ActionListener的对象
你在正确的轨道上。但我认为说例如“如果列表中有MyClass
的对象,它将打印”是有问题的。这听起来像是在定义运行时行为——泛型都是关于编译时检查的。例如,不能通过继承将MyList<MySubclass>
作为MyList<? super MyClass>
的参数传递,即使它可能包含MyClass
的实例。我将其改写为:
对printAll(MyList<? super MyClass>)
的调用只有在传递以下参数时才会编译:
MyList<MyClass>
MyList<Thread>
MyList<Runnable>
MyList<ActionListener>
MyList<EventListener>
MyList<Object>
MyList<? super X>
其中X
是MyClass
、Thread
、Runnable
、ActionListener
、EventListener
或Object
。
所以,在阅读了这个问题的许多答案之后,这是我的 理解:
? 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>();
当谈到 extends
与 super
时,这称为协方差与反方差。它确定允许沿类层次结构提供的类型移动的方向:
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
类层次结构的简单示例。 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);
【讨论】:
嗨@cyrille-ka,你确定public void addFloat(List<? super Float> list) list.add(2.5);
吗?它不会编译,但public void addFloat(List<? super Number> list) list.add(2.5);
可以,因为它保证了列表项的一致性【参考方案5】:
嗯,您对super
(printAll(MyList<? super MyClass>)
) 的陈述不清楚。这意味着什么,假设 Myclass 扩展 Object
是您可以 printAll(MyList<MyClass>)
并且您可以 printAll(MyList<Object>)
但仅此而已......这意味着 MyList 的泛型类型必须是 MyClass 的超类(而不是子类) .这和你说的不一样。
至于 T、E、K、V 或 N,这些本身就是毫无意义的名称。你可以使用任何你想要的东西。不过,约定建议使用单字母大写值,并且 T 通常用于泛型方法,而 E 用于类......
【讨论】:
【参考方案6】:首先是T
或E
或K
或其他非固定名称。它们只是类型变量,由您决定它们的名称。 T
、E
、K
只是示例,但您可以将其称为 Foo
或其他名称。
现在开始您的第一个问题:由于通配符 ?
代表“任何和未知”类型,即未指定类型,因此在未指定类型上声明泛型类没有任何意义。当您不关心类型时,在方法的参数或变量中使用通配符很有用。
现在关于您的第二个问题:下限为您的通用方法提供了更大的灵活性。 extends
和 super
都是相反的:
? extends T
:未知类型,它是T
的子类型
? super T
:未知类型,它是T
的超类型
当您想要接受与 T 兼容的类型时,后者可能很有用(这样 T 就是那个类型)。一个实际的例子可以找到here。
【讨论】:
更新 2 是在反复阅读您的答案几次后发布的。以上是关于了解上界和下界?在 Java 泛型中的主要内容,如果未能解决你的问题,请参考以下文章