{
}
class Particle{
}
public class LostInformation {
public static void main(String[] args) {
Listlist = new ArrayList ();
Mapmap = new HashMap ();
Quarkquark = new Quark ();
Particlep = new Particle ();
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));//[E]
System.out.println(Arrays.toString(map.getClass().getTypeParameters()));//[K, V]
System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));//[Q]
System.out.println(Arrays.toString(p.getClass().getTypeParameters()));//[POSITION, MOMENTUM]
}
}根据JDK文档的描述,Class.getTypeParameters()将“返回一个TypeVariable对象数组,表示有泛型声明所声明的类型参数……”这好像是在暗示你可能发现参数类型的信息,但是,正如你从输出中所看到的,你能够发现输出的只是用作参数占位符的标识符,这并非有用的信息。因此,残酷的现实是:在泛型代码内部,无法获得任何有关泛型参数类型的信息 。
因此,你可以知道诸如类型参数标识符(像上面程序所输出的)和泛型类型边界这类的信息——你却无法知道用来创建某个特定实例的实际的类型参数,这与C++不同。
Java泛型是使用擦除来实现的,这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此 List和List 在运行时事实上是相同的类型。这两种形式都被擦除成它们的“原生”类型,即List。
下面看看C++与java中的模板对比,这样更易理解他们之间的区别。
C++的方式
下面是C++模板,用于参数化类型的语法与java十分相似,因为Java是受C++的启发:
#include
using namespace std;
templateclass Manipulator {
T obj;
public:
Manipulator(T x) { obj = x; }
void manipulate() { obj.f(); }//C++泛型可以调用类型参数对象的方法,java是不可以的
};
class HasF {
public:
void f() { cout << "HasF::f()" << endl; }
};
int main() {
HasF hf;
Manipulatormanipulator(hf);
manipulator.manipulate();
} /* Output:
HasF::f()
///:~Manipulator类存储了一个类型T的对象,有意思的地方是manipulate()方法,它在obj上调用方法f()。它怎么能知道类型参数T有f()方法的呢?当你实例化这个模版时,C++编译器将进行检查,因此在Manipulator被实例化(C++编译后还会存在类型参数的信息,但Java在编译时就擦除掉了)的这一刻,它看 到HasF拥有一个方法f()。如果没有这个方法,就会得到一个编译期错误,这样类型安全就得到了保障。
Java泛型就不同了。下面是HasF的Java版本:
public class HasF {
public void f() {
System.out.println("HasF.f()");
}
}
class Manipulator{
private T obj;
public Manipulator(T x) {
obj = x;
}
public void manipulate() {
//! Error: cannot find symbol: method f():
obj.f();
}
}
public class Manipulation {
public static void main(String[] args) {
HasF hf = new HasF();
Manipulatormanipulator = new Manipulator (hf);
manipulator.manipulate();
}
}由于有了擦除,Java编译器无法在obj上调用f(),因为运行时无法将f()映射到HasF上。为了调用f(),我们必须协助泛型类,给定泛型类的边界,以此告知编译器只能接受遵循这个边界的类型。这里重用了extends关键字。由于有了边界,下面的代码就可以编译了:
class Manipulator
边界声明T必须是类型HasF或者是HasF的子类,这样就可以安全地在obj上调用f()了。
泛型类型参数将擦除到它的第一个边界(它可能会有多个边界,稍候你就会看到),在编译时,编译器实际上会把类型参数替换为它的擦除边界类型,就像上面的示例一样,T擦除到了HasF,就好像在类的声明中用HasF替换了T一样。
上面的class Manipulator类在编译时进行了擦除,擦除后成了没有泛型的类,就好像是:
class Manipulator {
private HasF obj;
public Manipulator(HasF x) {
obj = x;
}
public void manipulate() {
obj.f();
}
}擦除只因为兼容性
擦除这不是一个语言特性。它是Java的泛型实现中的一种折中,因为泛型不是Java语言出现时就有的组成部分,所以这种折中是必需的。
如果泛型在Java 1.0中就已经是其一部分了,那么这个特性将不会使用擦除来实现——它将使用具体化,使类型参数保持为第一类实体,因此你就能够在类型参数上执行基于类型的语言操作和反射操作。
在基于擦除的实现中,泛型类型被当作第二类类型处理,即不能在某些重要的上下文环境中使用的类型。泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为它们的非泛型上界。例如,诸如List这样的类型注解将被擦除为List,而普通的类型变量在未指定边界的情况下将被擦除为Object。
Java使用擦除来实现泛型的真真动机是它使得泛化的客户端可以用非泛化的类库来使用,反之亦然,这经常被称为“迁移兼容性”。因此Java泛型不仅必须支持向后兼容性,即现有的代码和类文件仍旧合法,并且继续保持其之前的含义;而且还要支持迁移兼容性,使得类库变为泛型的,并且当某个类库变为泛型时,不会破坏依赖于它的代码和应用程序。在决定这就是目标之后,设计者们决策认为擦除是唯一可行的解决方案。通过允许非泛型代码与泛型代码共存,擦除使得这种向着泛型的迁移成为可能。
擦除的问题
因此,擦除主要的正当理由是从非泛化代码到泛化代码的转变过程,以及在不破坏现有类库的情况下,将泛型融入Java语言。擦除使得现有的非泛型客户端代码能够在不改变的情况下继续使用,直到客户端使用用泛型重写这些代码。这是一个时间性的问题,因为它不会突然间破坏所有现有的代码
擦除的代价是显著的。泛型不能用于显式地引用于运行时类型的操作之中,例如转型、instanceof操作和new表达式。因为所有关于参数的类型信息都丢失了,无论何时,当你在编写泛型代码时,必须时刻提醒自己,你只是看起来好像拥有有关参数的类型信息而已。因此,如果你编写了下面这样的代码段:
class Foo{T var;}
那么,看起来当你在创建Foo的实例时:
Foof = new Foo ();
class Foo中的代码应该知道现在工作于Cat之上,而泛型语法也在强烈暗示:在整个类中的各个地方,类型T都在被替换 。但是事实并非如此,无论何时,当你在编写这个类的代码时,必须提醒自己:“不,它只是一个Object 。”
另外,擦除和迁移兼容性意味着,使用泛型并不是强制的,所以你可以这样:
class GenericBase{
private T element;
public void set(T arg) {
element = arg;
}
public T get() {
return element;
}
}
@SuppressWarnings("unchecked")
class Derived2 extends GenericBase {//子类没有泛化也可,但会警告
} //warning
public class ErasureAndInheritance {
public static void main(String[] args) {
Derived2 d2 = new Derived2();
//由于类型的擦除,返回的为Object类型
Object obj = d2.get();
d2.set("str"); // Warning here!
System.out.println(d2.get());
}
}边界处的动作
泛型中的所有动作都发生在边界处——对传递进来的值进行额外的编译期检查,并插入对传递出去的值的转型 。
擦除的补偿
擦除丢失了在泛型代码中执行某些操作的能力。任何在运行时需要知道确切类型信息的操作都将无法工作:
public class Erased{
private static final int SIZE = 100;
public void f(Object arg) {
//if(arg instanceof T) {} // Error
//T var = new T(); // Error
//T[] array = new T[SIZE]; // Error
//ArrayListgenArr[] = new ArrayList [2]; // 此句无法通过编译,不能创类型参数的数组
T[] array = (T[]) new Object[SIZE]; // Unchecked warning
}
}
示例中对使用instanceof的尝试最终失败了,因为其类型信息已经被擦除了。如果引入类型标签,就可以转而使用动态的isInstance():
class Building {
}
class House extends Building {
}
public class ClassTypeCapture{
Classkind;
public ClassTypeCapture(Classkind) {
this.kind = kind;
}
public boolean f(Object arg) {
//判定指定的 Object 是否与此 Class 所表示的对象赋值兼容。此方法
//是 Java 语言 instanceof 运算符的动态等效方法
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapturectt1 = new ClassTypeCapture (Building.class);
System.out.println(ctt1.f(new Building()));//true
System.out.println(ctt1.f(new House()));//true
ClassTypeCapturectt2 = new ClassTypeCapture (House.class);
System.out.println(ctt2.f(new Building()));//false
System.out.println(ctt2.f(new House()));//true
}
}创建类型实例
在Erased中对创建一个new T()的尝试将无法实现,部分原因是因为擦除,而另一部分原因是因为编译器不能验证T具有默认(无参)构造器。Java中的解决方案是传递一个工厂对象,并使用它来创建新的实例。最便利的工厂对象就是Class对象,因此如果使用类型标签,那么你就可以使用newInstance()来创建这个类型的新对象:
class ClassAsFactory{
T x;
public ClassAsFactory(Classkind) {
try {
x = kind.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class Employee {
}
public class InstantiateGenericType {
public static void main(String[] args) {
ClassAsFactoryfe = new ClassAsFactory (Employee.class);
//ClassAsFactorysucceeded
System.out.println("ClassAsFactorysucceeded");
try {
ClassAsFactoryfi = new ClassAsFactory (Integer.class);
} catch (Exception e) {
//ClassAsFactoryfailed
System.out.println("ClassAsFactoryfailed");
}
}
}这可以编译,但是运行时会因ClassAsFactory而失败,因为Integer没有任何默认的构造器。因为这个错误不是在编译期捕获的,所以建议使用显式的工厂,并将限制其类型,使得只能接受实现了这个工厂的类:
//工厂泛型接口,创建某个T的实例前需实现创建它的工厂
interface FactoryI{
//工厂方法,实例创建接口,因为不同的类可能具有不同的创建方式
T create();
}
//Integer实例工厂,实现了FactoryI泛型接口
class IntegerFactory implements FactoryI{
public Integer create() {
//Integer没有默认的构造函数,创建时需传入参数
return new Integer(0);
}
}
class Widget {
//这里巧妙地使用了静态的内部类来充当Widget的工厂类,很适合使用内部类~~!
public static class Factory implements FactoryI{
public Widget create() {
return new Widget();
}
}
}
class Foo2{
public final T x;
//只接收实现了FactoryI泛型接口的工厂实例
public> Foo2(F factory) {
//调用工厂方法
x = factory.create();
}
}
public class FactoryConstraint {
public static void main(String[] args) {
//创建Integer实例
Integer intg = new Foo2(new IntegerFactory()).x;
//创建Widget实例
Widget wg = new Foo2(new Widget.Factory()).x;
}
}另一种方式是模版方法设计模式。在下面的示例中,create ()是模版方法,而create()是在父类中定义的、用来产生子类类型的对象:
abstract class GenericWithCreate{
final T element;
GenericWithCreate() {
element = create();
}
//模板方法
abstract T create();
}
class X {
}
class XCreator extends GenericWithCreate{
X create() {
return new X();
}
void f() {
System.out.println(element.getClass().getSimpleName());
}
}
class IntgCreator extends GenericWithCreate{
Integer create() {
return new Integer(0);
}
void f() {
System.out.println(element.getClass().getSimpleName());
}
}
public class CreatorGeneric {
public static void main(String[] args) {
XCreator xc = new XCreator();
xc.f();//X
X x = xc.element;
IntgCreator ic = new IntgCreator();
ic.f();//Integer
Integer intg = ic.element;
}
}泛型数组
正如你在Erased中所见,不能创建泛型数组。一般的解决方案是在任何想要创建泛型数组的地方都使用ArrayList:
import java.util.ArrayList;
import java.util.List;
public class ListOfGenerics{
private Listarray = new ArrayList ();
public void add(T item) {
array.add(item);
}
public T get(int index) {
return array.get(index);
}
}有时,你仍旧希望创建泛型类型的数组(例如,ArrayList内部使用的是数组 E[] elementData;),这是可以的,你可以定义一个数组引用,例如:
class Generic{
}
public class ArrayOfGenericReference {
static Generic[] gia;
}编译器将接受这个程序,而不会产生任何警告。但是,永远都不能创建这个确切类型的数组(包括类型参数),因此这有一点令人困惑。
如果我们创建一个Object数组,并将其转型为所希望的数组类型。事实上这可以编译,但是不能运行,它将产生ClassCase-Exception:
package generic;
public class ArrayOfGeneric {
static final int SIZE = 100;
static Generic[] gia;
public static void main(String[] args) {
// Compiles; produces ClassCastException:
//! gia = (Generic[])new Object[SIZE];
// Runtime type is the raw (erased) type:
gia = (Generic[]) new Generic[SIZE];
// 编译通不过,因为不能创建泛型数组
//! gia = new Generic[SIZE];
System.out.println(gia.getClass().getSimpleName());
gia[0] = new Generic();
//! gia[1] = new Object(); // Compile-time error
// Discovers type mismatch at compile time:
//! gia[2] = new Generic();
}
}
上面(gia = (Generic[])new Object[SIZE]; )的问题在于数组将跟踪它们的实际类型,而这个类型是在数组被创建时确定的,因此,即使gia已经被转型为 Generic [],但是这个信息只存在于编译期(并且如果没有@Suppress Warnings注解,你将得到有关这个转型的警告)。在运行时,它仍旧是Object数组,因而引发问题。成功创建泛型数组的唯一方式就是创建一个被擦除类型的新数组,然后对其转型。
让我们看一个更复杂的示例。考虑一个简单的泛型数组包装器:
public class GenericArray{
private T[] array;
public GenericArray(int sz) {
/*
* 这里这样做只是为了通过编译器的类型检查,在编译时会擦除掉T,使用
* Object替换T,所以在运行时是正确的,这里按常理构造一个Object数
* 组后强制转换成其他任何类型时一定会报错,如:String[] strArr
* =(String[]) new Object[sz];运行时肯定会报ClassCastException
*/
array = (T[]) new Object[sz]; //外界不管怎样转型,实质都为Object
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return array[index];
}
// 注,这里返回的其实还是Object数据,这里由于编译时擦除引起
public T[] rep() {
return array;
}
public static void main(String[] args) {
GenericArraygai = new GenericArray (10);
// This causes a ClassCastException:
//! Integer[] ia = gai.rep();
// This is OK:
Object[] oa = gai.rep();
System.out.println(oa.getClass());
}
}与前面相同,我们并不能声明T[] array = new T[sz],因此我们创建了一个对象数组,然后将其转型。rep()方法将返回T[],它在main()中将用于gai,因此应该是Integer[],但是如果调用它,并尝试着将结果赋值给Integer[]引用,就会得到ClassCastException,这还是因为实际的运行时类型是Object[]。
因为有了擦除,数组的运行时类型就只能是Object[]。如果我们立即将其转型为T[],那么在编译期该数组的实际类型就将丢失,而编译器可能会错过某些潜在的错误检查。正因为这样,最好是在集合内部使用Object[],然后当你使用数组元素时,添加一个对T的转型。让我们看看这是如何作用于GenericArray.java示例的:
public class GenericArray2{
private Object[] array;
public GenericArray2(int sz) {
array = new Object[sz];//外界不管怎样转型,实质都为Object
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return (T) array[index];/*转型*/
}
public T[] rep() {
//Object数组转换Object数组当然没有问题,编译与运行时都没有问题,但运行时返回的为Object数组
return (T[]) array; // Warning: unchecked cast
}
public static void main(String[] args) {
GenericArray2gai = new GenericArray2 (10);
for (int i = 0; i < 10; i++) {
gai.put(i, i);
}
for (int i = 0; i < 10; i++) {
Integer intg = gai.get(i);
//0 1 2 3 4 5 6 7 8 9
System.out.print(intg + " ");
}
System.out.println();
try {
Integer[] ia = gai.rep();//只能通过编译,运行就会抛异常
} catch (Exception e) {
//java.lang.ClassCastException: [Ljava.lang.Object;
System.out.println(e);
}
}
}初看起来,这好像没多大变化,只是转型挪了地方。但是,现在的内部表示是Object[]而不是T[]。当get()被调用时,它将对象转型为T,这实际上是正确的类型,因此这是安全的。然而,如果你调用rep(), 它还是尝试着将Object[]转型为T[],这仍旧是不正确的,将在编译期产生警告,在运行时产生异常。因此,没有任何方式可以推翻底层的数组类型,它只能是Object[]。在内部将array当作Object[]而不是T[]处理的优势是:我们不太可能忘记这个数组的运行时类型,从而意外地引入缺陷。
从上面两个程序可以看出在创建数组时都是直接采用new Object[sz];方式来创建的,所以在外界使用一个非Object数组变量来引用该Object数组会出错误运行时转型异常,为了外界使用真真类型来引用数组,则应该使用Array.newInstance方式动态地创建数组,所以,应该传递一个类型标记。在这种情况下,GenericArray看起来会像下面这样:
import java.lang.reflect.Array;
public class GenericArrayWithTypeToken{
private T[] array;
public GenericArrayWithTypeToken(Classtype, int sz) {
//这里运行时实质上是转换成了Object类型,向上转换是可以的
array = (T[]) Array.newInstance(type, sz);
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return array[index];
}
//这里实质上返回的还是Object数组,只是在返回赋值给引用变量时编译时会插入强制转型字节码:
public T[] rep() {
return array;
}
public static void main(String[] args) {
GenericArrayWithTypeTokengai = new GenericArrayWithTypeToken (
Integer.class, 10);
/*
* 现在返回的是一个真真的Integer数组了,在返回的操作边界插入了强制转换操作:
* (Integer[])gai.rep();,可以看生成的字节码证明,这里之所以能强制转换是
* 因为返回的数组本身是一个Integer数组
*/
Integer[] ia = gai.rep();
}
}类型标记Class被传递到构造器中,以便从擦除中恢复,使得我们可以创建需要的实际类型的数组。一旦我们获得了实际类型,就可以返回它,并获得想要的结果,就像在main()中看到的那样。该数组的运行时类型是确切的类型T[]。
遗憾的是,如果查看Java SE5标准类库中的源代码,你就会看到从Object数组到参数化类型的转型遍及各处。例如,下面是经过整理和简化之后的从Collection中复制ArrayList的构造器:
public ArrayList(Collection c) {
size = c.size();
elementData = (E[]) new Object[size];
//...
}Neal Gafter(Java SE5的领导开发者之一)在他的博客中指出,在重写Java类库时,他十分懒散,而我们不应该像他那样。Neal还指出,在不破坏现有接口的情况下,他将无法修改某些Java类库代码。因此,即使在Java类库源代码中出现了某些惯用法,也不能表示这就是正确的解决之道。当查看类库代码时,你不能认为它就是应该在自己的代码中遵循的示例。
.泛型边界:
Java泛型编程时,编译器忽略泛型参数的具体类型,认为使用泛型的类、方法对Object都适用,这在泛型编程中称为类型信息檫除。
例如:
class GenericType{
public static void main(String[] args){
System.out.println(new ArrayList().getClass());
System.out.println(new ArrayList().getClass());
}
}
输出结果为:
java.util.ArrayList
java.util.ArrayList
泛型忽略了集合容器中具体的类型,这就是类型檫除。
但是如果某些泛型的类/方法只想针对某种特定类型获取相关子类应用,这时就必须使用泛型边界来为泛型参数指定限制条件。
例如:
interface HasColor{
java.awt.Color getColor();
}
class Colored{
T item;
Colored(T item){
this.item = item;
}
java.awt.Color color(){
//调用HasColor接口实现类的getColor()方法
return item.getColor();
}
}
class Dimension{
public int x, y, z;
}
Class ColoredDimension{
T item;
ColoredDimension(T item){
this.item = item;
}
T getItem(){
return item;
}
java.awt.Color color(){
//调用HasColor实现类中的getColor()方法
return item.getColor();
}
//获取Dimension类中定义的x,y,z成员变量
int getX(){
return item.x;
}
int getY(){
return item.y;
}
int getZ(){
return item.z;
}
}
interface Weight{
int weight();
}
class Solid{
T item;
Solide(T item){
this.item = item;
}
T getItem(){
return item;
}
java.awt.Color color(){
//调用HasColor实现类中的getColor()方法
return item.getColor();
}
//获取Dimension类中定义的x,y,z成员变量
int getX(){
return item.x;
}
int getY(){
return item.y;
}
int getZ(){
return item.z;
}
int weight(){
//调用Weight接口实现类的weight()方法
return item.weight();
}
}
class Bounded extends Dimension implements HasColor, Weight{
public java.awt.Color getColor{
return null;
}
public int weight(){
return 0;
}
}
public class BasicBounds{
public static void main(String[] args){
Solidsolid = new Solid (new Bounded());
solid.color();
solid.getX();
solid.getY();
solid.getZ();
solid.weight();
}
}
Java泛型编程中使用extends关键字指定泛型参数类型的上边界(后面还会讲到使用super关键字指定泛型的下边界),即泛型只能适用于extends关键字后面类或接口的子类。
Java泛型编程的边界可以是多个,使用如语法来声明,其中只能有一个是类,并且只能是extends后面的第一个为类,其他的均只能为接口(和类/接口中的extends意义不同)。
使用了泛型边界之后,泛型对象就可以使用边界对象中公共的成员变量和方法。
2.泛型通配符:
泛型初始化过程中,一旦给定了参数类型之后,参数类型就会被限制,无法随着复制的类型而动态改变,如:
class Fruit{
}
class Apple extends Fruit{
}
class Jonathan extends Apple{
}
class Orange extends Fruit{
}
如果使用数组:
public class ConvariantArrays{
Fruit fruit = new Apple[10];
Fruit[0] = new Apple();
Fruit[1] = new Jonathan();
try{
fruit[0] = new Fruit();
}catch(Exception e){
System.out.println(e);
}
try{
fruit[0] = new Orange();
}catch(Exception e){
System.out.println(e);
}
}
编译时没有任何错误,运行时会报如下异常:
java.lang.ArrayStoreException:Fruit
java.lang.ArrayStoreException:Orange
为了使得泛型在编译时就可以进行参数类型检查,我们推荐使用java的集合容器类,如下:
public class NonConvariantGenerics{
Listflist = new ArrayList ();
}
很不幸的是,这段代码会报编译错误:incompatible types,不兼容的参数类型,集合认为虽然Apple继承自Fruit,但是List的Fruit和List的Apple是不相同的,因为泛型参数在声明时给定之后就被限制了,无法随着具体的初始化实例而动态改变,为解决这个问题,泛型引入了通配符”?”。
对于这个问题的解决,使用通配符如下:
public class NonConvariantGenerics{
List extends Fruit> flist = new ArrayList();
}
泛型通配符”?”的意思是任何特定继承Fruit的类,java编译器在编译时会根据具体的类型实例化。
另外,一个比较经典泛型通配符的例子如下:
public class SampleClass < T extendsS> {…}
假如A,B,C,…Z这26个class都实现了S接口。我们使用时需要使用到这26个class类型的泛型参数。那实例化的时候怎么办呢?依次写下
SampleClass a = new SampleClass();
SampleClass a = new SampleClass();
…
SampleClassa = new SampleClass();
这显然很冗余,还不如使用Object而不使用泛型,使用通配符非常方便:
SampleClass Extends S> sc = newSampleClass();
3.泛型下边界:
在1中大概了解了泛型上边界,使用extends关键字指定泛型实例化参数只能是指定类的子类,在泛型中还可以指定参数的下边界,是一super关键字可以指定泛型实例化时的参数只能是指定类的父类。
例如:
class Fruit{
}
class Apple extends Fruit{
}
class Jonathan extends Apple{
}
class Orange extends Fruit{
}
public superTypeWildcards{
public static void writeTo(List super Apple> apples){
apples.add(new Apple());
apples.add(new Jonathan());
}
}
通过? Super限制了List元素只能是Apple的父类。
泛型下边界还可以使用,但是注意不能使用,即super之前的只能是泛型通配符,如:
public class GenericWriting{
static Listapples = new ArrayList ();
static Listfruits = new ArrayList ();
staticvoid writeExact(List list, T item){
list.add(item);
}
staticvoid writeWithWildcards(List super T> list, T item){
list.add(item);
}
static void f1(){
writeExact(apples, new Apple());
}
static void f2(){
writeWithWildcards(apples, new Apple());
writeWithWildcards(fruits, new Apple());
}
public static void main(String[] args){
f1();
f2();
}
}
4.无边界的通配符:
泛型的通配符也可以不指定边界,没有边界的通配符意思是不确定参数的类型,编译时泛型檫除类型信息,认为是Object类型。如:
public class UnboundedWildcard{
static List list1;
static List> list2;
static List extends Object> list3;
static void assign1(List list){
list1 = list;
list2 = list;
//list3 = list; //有未检查转换警告
}
static void assign2(List> list){
list1 = list;
list2 = list;
list3 = list;
}
static void assign3(List extends Object> list){
list1 = list;
list2 = list;
list3 = list;
}
public static void main(String[] args){
assign1(new ArrayList());
assign2(new ArrayList());
//assign3(new ArrayList()); //有未检查转换警告
assign1(new ArrayList());
assign2(new ArrayList());
assign3(new ArrayList());
List> wildList = new ArrayList();
assign1(wildList);
assign2(wildList);
assign3(wildList);
}
}
List和List>的区别是:List是一个原始类型的List,它可以存放任何Object类型的对象,不需要编译时类型检查。List>等价于List