java 泛型详解

泛型

	泛型的原理
		类型擦除
			类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。在使用泛型时,任何具体的类型都被擦除,唯一知道的是你在使用一个对象。比如:List<String>和List<Integer>在运行事实上是相同的类型。他们都被擦除成他们的原生类型,即List。
		既然在编译的时候会在方法和类中擦除实际类型的信息,那么在返回对象时又是如何知道其具体类型的呢?
			擦除在方法体中移除了类型信息,所以在运行时的问题就是边界:即对象进入和离开方法的地点,这正是编译器在编译期执行类型检查并插入转型代码的地点。泛型中的所有动作都发生在边界处:对传递进来的值进行额外的编译期检查,并插入对传递出去的值的转型。


	泛型的基本使用(查看jdk帮助文档某个类是否可以使用泛型)
		泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息<>中的类型,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。


	泛型的术语(用于交流):
		整个ArrayList<E>称为泛型类型
		ArrayList<E>中的E称为类型变量或类型参数
		整个ArrayList<Integer>称为参数化的类型
		ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
		ArrayList<Integer>中的<>念着typeof
		ArrayList称为原始类型


	参数化类型与原始类型的兼容性:
		参数化类型可以引用一个原始类型的对象,编译器报警告,例如,
			Collection<String> c = new Vector();//如果通不过,以前写的程序就全废了
		原始类型可以引用一个参数化类型的对象,编译器报警告,例如,			
			Collection c = new Vector<String>();//原来的方法接收一个集合参数,新的类型也要能传进去


	参数化类型不考虑类型参数的继承关系:
			Vector<String> v = new Vector<Object>();//错误
			Vector<Object> v = new Vector<String>();//也错误


	在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:
			Vector<Integer> vectorList[] = new Vector<Integer>[10];

	思考题:下面的代码会报错误吗?不会,编译器是一行一行执行的
			Vector v1 = new Vector<String>();
			Vector<Object> v = v1;


	泛型中的?通配符:
		使用?通配符可以引用其他各种参数化的类型。?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。	
			public static void printCollection(Collection<?> collection){
				//collection.add(1);不能调用这个方法,因为它与参数有关(因为它不知自己未来匹配就一定是String)
				collection.size();//可以调用,因为与类型参数无关
				collection = new HashMap<Date>();//可以
			}


	泛型中的?通配符的扩展

		限定通配符的上边界:
			正确:Vector<? extends Number> x = new Vector<Integer();
			//表示?可以是任意类型,但必须是Number或者是Number的子类,设定了上边界,必须是它的子类
			错误:Vector<? extends Number> x = new Vector<String>();

		限定通配符的下边界:
			正确:Vector<? super Integer> x = new Vector<Number>();
			//设定下边界,?表示可以是任意类型,但必须是Integer上面的类是Integer的父类
			错误:Vector<? super Integer> x = new Vector<Byte>();


	存取原则和PECS法则

		总结 ? extends 和 the ? super 通配符的特征,我们可以得出以下结论:
			如果你想从一个数据类型里获取数据,使用 ? extends 通配符
			如果你想把对象写入一个数据结构里,使用 ? super 通配符
			如果你既想存,又想取,那就别用通配符。

		
	java泛型与c++比较:java中的泛型类型类似于c++中的模板。但是这种相似性仅限于表面,java语言中的泛型基本上完全是在编译器中实现的,用于编译器执行类型检查和类型推断,然后生成普通的非泛型字节码,这种实现技术称为擦除(编译器使用泛型类型信息保证类型安全,然后再生成字节码之前将其擦除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为java厂商升级JVM造成难以逾越的障碍。所以,java的泛型采用了可以完全在编译器中实现的擦除方法。

	java泛型方法没有c++模板函数功能强大,java中的如下代码无法通过编译:
		 <T> T add(T x,T y){
		   return (T)(x + y);//因为x和y不知道有没有 +这个方法
		 }


	泛型方法:
		用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。

		只有引用类型才能作为泛型方法的实际参数,swap(new int[3],3,5);语句会报编译错误(因为new int[3]本身已经是对象了)
		除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如,Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如<V extends Serializable & cloneable> void method(){}
		普通方法,构造方法和静态方法中都可以使用泛型

		也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句中。如:
		private static <T extends Exception> sayHello() throws T{try{}catch(Exception e){throw(T)e;}}
		在泛型中可以同时有多个类型参数,在定义他它们的尖括号中用逗号分,例如:public static <K,V> getValue(K key){return map.get(key);}

		使用泛型方法还是?通配符:在一个类型变量用来表达两个参数或者参数和返回值之间的关系时,即同一个类型变量用来表达两个参数之间或者参数和返回值之间的关系时,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用而不是仅在签名的时候使用,才需呀使用泛型方法。


	类型参数的类型推断:

		编译器判断泛型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
		根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:

			当某个类型变量只在整个参数列表中的所有参数和返回值的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
				swap(new String[3],3,4)->static <E> void swap(E[] a ,int i,int j)

			当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:
				add(3,5)->static <T> add(T a,T b)

			当某个类型变量在整个参数列表中的所有参数和返回值中多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候多个参数中的最大交集类型,例如:下面语句实际对应的类型就是Number了,编译阶段没问题,只是运行时出问题:
				fill(new Integer[3],3.5f)->static <T> void fill(T[] a, T y)

			当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应对应到了不同的类型,并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
				int x = (3,3.5f) -> static <T> add(T a,T b)

			参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
				copy(new Integer[5] ,new String[5])->static  <T> void copy(T[] a,T[] b);
				copy(new Vector<String>(),new Integer[5])->static <T> void copy(Collection<T> a,T[] b);


	泛型类(型)
		如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:
			public class GenericDao<T>{
				private T field;
				public void save(T obj){}
				public T getById(int id){}
			}

		类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:
			GenericDao<String> dao = null;
			new GenericDao<String>();

		注意:
			在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能使基本类型。
			当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的参数类型。

		问题:类中多个方法需要使用泛型,是使用类级别的泛型,还是使用方法级别的泛型?类级别的
		
		为什么您选择使用泛型方法,而不是将类型 T 添加到类定义呢?(至少)有两种情况应该这样做:
			当泛型方法是静态的时,这种情况下不能使用类类型参数。
			当 T 上的类型约束对于方法真正是局部的时,这意味着没有在相同类的另一个方法签名中使用相同类型 T 的约束。通过使得泛型方法的类型参数对于方法是局部的,可以简化封闭类型的签名。
				

以上为java泛型知识点总结.

1介绍

Java泛型编程是JDK1.5版本后引入的。泛型让编程人员能够使用类型抽象,通常用于集合里面。下面是一个不用泛型例子:

List myIntList=newLinkedList(); //1

myIntList.add(newInteger(0));//2

Integerx=(Integer)myIntList.iterator().next(); //3

注意第3行代码,但这是让人很不爽的一点,因为程序员肯定知道自己存储在List里面的对象类型是Integer,但是在返回列表中元素时,还是必须强制转换类型,这是为什么呢?原因在于,编译器只能保证迭代器的next()方法返回的是Object类型的对象,为保证Integer变量的类型安全,所以必须强制转换。
这种转换不仅显得混乱,更可能导致类型转换异常ClassCastException,运行时异常往往让人难以检测到。保证列表中的元素为一个特定的数据类型,这样就可以取消类型转换,减少发生错误的机会,这也是泛型设计的初衷。下面是一个使用了泛型的例子:

List<Integer>myIntList=newLinkedList<Integer>(); //1’

myIntList.add(newInteger(0));//2’

Integerx=myIntList.iterator().next(); //3’

在第1行代码中指定List中存储的对象类型为Integer,这样在获取列表中的对象时,不必强制转换类型了。


2定义简单的泛型

下面是一个引用自java.util包中的接口ListIterator的定义,其中用到了泛型技术。

public interface List<E> {

void add(E x);

Iterator<E> iterator();

}

public interfaceIterator<E> {

E next();

boolean hasNext();

}

这跟原生类型没有什么区别,只是在接口后面加入了一个尖括号,尖括号里面是一个类型参数(定义时就是一个格式化的类型参数,在调用时会使用一个具体的类型来替换该类型)。

也许可以这样认为,List<Integer>表示List中的类型参数E会被替换成Integer

public interfaceIntegerList {

void add(Integer x)

Iterator<Integer> iterator();

}

类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上,因此泛型类型中的静态变量是所有实例共享的。此外,需要注意的是,一个static方法,无法访问泛型类的类型参数,因为类还没有实例化,所以,若static方法需要使用泛型能力,必须使其成为泛型方法类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法在使用泛型时,任何具体的类型都被擦除,唯一知道的是你在使用一个对象。比如:List<String>List<Integer>在运行事实上是相同的类型。他们都被擦除成他们的原生类型,即List因为编译的时候会有类型擦除,所以不能通过同一个泛型类的实例来区分方法,如下面的例子编译时会出错,因为类型擦除后,两个方法都是List类型的参数,因此并不能根据泛型类的类型来区分方法。

/*会导致编译时错误*/ 
 publicclass Erasure{
           public void test(List<String> ls){
              System.out.println("Sting");
           }
           public void test(List<Integer> li){
              System.out.println("Integer");
           }
  }

那么这就有个问题了,既然在编译的时候会在方法和类中擦除实际类型的信息,那么在返回对象时又是如何知道其具体类型的呢?如List<String>编译后会擦除掉String信息,那么在运行时通过迭代器返回List中的对象时,又是如何知道List中存储的是String类型对象呢?擦除在方法体中移除了类型信息,所以在运行时的问题就是边界即对象进入和离开方法的地点,这正是编译器在编译期执行类型检查并插入转型代码的地点。泛型中的所有动作都发生在边界处:对传递进来的值进行额外的编译期检查,并插入对传递出去的值的转型。


3.泛型和子类型

为了彻底理解泛型,这里看个例子:(AppleFruit的子类)

List<Apple>apples = new ArrayList<Apple>(); //1

List<Fruit> fruits = apples; //2

行代码显然是对的,但是第2行是否对呢?我们知道Fruitfruit = newApple(),这样肯定是对的,即苹果肯定是水果,但是第2行在编译的时候会出错。这会让人比较纳闷的是一个苹果是水果,为什么一箱苹果就不是一箱水果了呢?可以这样考虑,我们假定第2行代码没有问题,那么我们可以使用语句fruits.add(newStrawberry())StrawberryFruit的子类)在fruits中加入草莓了,但是这样的话,一个List中装入了各种不同类型的子类水果,这显然是不可以的,因为我们在取出List中的水果对象时,就分不清楚到底该转型为苹果还是草莓了。通常来说,如果FooBar的子类型,G是一种带泛型的类型,则G<Foo>不是G<Bar>的子类型。这也许是泛型学习里面最让人容易混淆的一点。


4.通配符

4.1通配符?

先看一个打印集合中所有元素的代码。

//不使用泛型

voidprintCollection(Collection c) {

Iterator i=c.iterator();

for (k=0;k < c.size();k++) {

System.out.println(i.next());

}

}

//使用泛型

voidprintCollection(Collection<Object> c) {

for (Object e:c) {

System.out.println(e);

}

}

很容易发现,使用泛型的版本只能接受元素类型为Object类型的集合如ArrayList<Object>();如果是ArrayList<String>,则会编译时出错。因为我们前面说过,Collection<Object>并不是所有集合的超类。而老版本可以打印任何类型的集合,那么如何改造新版本以便它能接受所有类型的集合呢?这个问题可以通过使用通配符来解决。修改后的代码如下所示:

//使用通配符?,表示可以接收任何元素类型的集合作为参数

voidprintCollection(Collection<?> c) {

for (Object e:c) {

System.out.println(e);

}

}

这里使用了通配符?指定可以使用任何类型的集合作为参数。读取的元素使用了Object类型来表示,这是安全的,因为所有的类都是Object的子类。这里就又出现了另外一个问题,如下代码所示,如果试图往使用通配符?的集合中加入对象,就会在编译时出现错误。需要注意的是,这里不管加入什么类型的对象都会出错。这是因为通配符?表示该集合存储的元素类型未知,可以是任何类型。往集合中加入元素需要是一个未知元素类型的子类型,正因为该集合存储的元素类型未知,所以我们没法向该集合中添加任何元素。唯一的例外是null,因为null是所有类型的子类型,所以尽管元素类型不知道,但是null一定是它的子类型。

Collection<?>c=new ArrayList<String>();

c.add(newObject());//compile time error,不管加入什么对象都出错,除了null外。

c.add(null); //OK

另一方面,我们可以从List<?>lists中获取对象,虽然不知道List中存储的是什么类型,但是可以肯定的是存储的类型一定是Object的子类型,所以可以用Object类型来获取值。如for(Objectobj: lists),这是合法的。

4.2边界通配符

1extends通配符

假定有一个画图的应用,可以画各种形状的图形,如矩形和圆形等。为了在程序里面表示,定义如下的类层次:

public abstractclass Shape {

public abstract void draw(Canvas c);

}

}

public class Circleextends Shape {

private int x,y,radius;

public void draw(Canvas c) { ... }

}

}

public classRectangle extends Shape

private int x,y,width,height;

public void draw(Canvasc) { ... }

}

}

为了画出集合中所有的形状,我们可以定义一个函数,该函数接受带有泛型的集合类对象作为参数。但是不幸的是,我们只能接收元素类型为ShapeList对象,而不能接收类型为List<Cycle>的对象,这在前面已经说过。为了解决这个问题,所以有了边界通配符的概念。这里可以采用publicvoid drawAll(List<? extends Shape>shapes)来满足条件,这样就可以接收元素类型为Shape子类型的列表作为参数了。

//原始版本

public voiddrawAll(List<Shape> shapes) {

for (Shapes:shapes) {

s.draw(this);

}

}

//使用边界通配符的版本

public voiddrawAll(List<exendsShape> shapes) {

for (Shapes:shapes) {

s.draw(this);

}

}

这里就又有个问题要注意了,如果我们希望在List<exendsShape> shapes中加入一个矩形对象,如下所示

shapes.add(0, new Rectangle()); //compile-timeerror

那么这时会出现一个编译时错误,原因在于:我们只知道shapes中的元素时Shape类型的子类型,具体是什么子类型我们并不清楚,所以我们不能往shapes中加入任何类型的对象。

不过我们在取出其中对象时,可以使用Shape类型来取值,因为虽然我们不知道列表中的元素类型具体是什么类型,但是我们肯定的是它一定是Shape类的子类型。


2)?super通配符


这里还有一种边界通配符为?super。比如下面的代码:

List<Shape>shapes = new ArrayList<Shape>();

List<? superCicle> cicleSupers = shapes;

cicleSupers.add(newCicle()); //OK, subclass of Cicle also OK

cicleSupers.add(new Shape()); //ERROR

这表示cicleSupers列表存储的元素为Cicle的超类,因此我们可以往其中加入Cicle对象或者Cicle的子类对象,但是不能加入Shape对象。这里的原因在于列表cicleSupers存储的元素类型为Cicle的超类,但是具体是Cicle的什么超类并不清楚。但是我们可以确定的是只要是Cicle或者Circle的子类,则一定是与该元素类别兼容。


3)边界通配符总结

  • 如果你想从一个数据类型里获取数据,使用? extends 通配符

  • 如果你想把对象写入一个数据结构里,使用? super 通配符

  • 如果你既想存,又想取,那就别用通配符。


5.泛型方法

考虑实现一个方法,该方法拷贝一个数组中的所有对象到集合中。下面是初始的版本:

static voidfromArrayToCollection(Object[]a, Collection<?> c) {

for (Object o:a) {

c.add(o); //compiletime error

}

}

可以看到显然会出现编译错误,原因在之前有讲过,因为集合c中的类型未知,所以不能往其中加入任何的对象(当然,null除外)。解决该问题的一种比较好的办法是使用泛型方法,如下所示:

static <T>void fromArrayToCollection(T[] a, Collection<T>c){

for(T o : a) {

c.add(o);// correct

}

}

注意泛型方法的格式,类型参数<T>需要放在函数返回值之前。然后在参数和返回值中就可以使用泛型参数了。具体一些调用方法的实例如下:

Object[] oa = newObject[100];

Collection<Object>co= new ArrayList<Object>();

fromArrayToCollection(oa,co);// T inferred to be Object

String[] sa = newString[100];

Collection<String>cs= new ArrayList<String>();

fromArrayToCollection(sa,cs);// T inferred to be String

fromArrayToCollection(sa,co);// T inferred to be Object

Integer[] ia = newInteger[100];

Float[] fa = newFloat[100];

Number[] na = newNumber[100];

Collection<Number>cn= new ArrayList<Number>();

fromArrayToCollection(ia,cn);// T inferred to be Number

fromArrayToCollection(fa,cn);// T inferred to be Number

fromArrayToCollection(na,cn);// T inferred to be Number

fromArrayToCollection(na,co);// T inferred to be Object

fromArrayToCollection(na, cs);// compile-timeerror

注意到我们调用方法时并不需要传递类型参数,系统会自动判断类型参数并调用合适的方法。当然在某些情况下需要指定传递类型参数,比如当存在与泛型方法相同的方法的时候(方法参数类型不一致),如下面的一个例子:

public <T> voidgo(T t) {

System.out.println("genericfunction");

}

publicvoidgo(Stringstr) {

System.out.println("normalfunction");

}

publicstaticvoidmain(String[] args) {

FuncGenricfg =newFuncGenric();

fg.go("haha");//打印normalfunction

fg.<String>go("haha");//打印genericfunction

fg.go(newObject());//打印genericfunction

fg.<Object>go(newObject());//打印genericfunction

}

如例子中所示,当不指定类型参数时,调用的是普通的方法,如果指定了类型参数,则调用泛型方法。可以这样理解,因为泛型方法编译后类型擦除,如果不指定类型参数,则泛型方法此时相当于是publicvoid go(Objectt)。而普通的方法接收参数为String类型,因此以String类型的实参调用函数,肯定会调用形参为String的普通方法了。如果是以Object类型的实参调用函数,则会调用泛型方法。

6.其他需要注意的小点

1)方法重载

在JAVA里面方法重载是不能通过返回值类型来区分的,比如代码一中一个类中定义两个如下的方法是不容许的。但是当参数为泛型类型时,却是可以的。如下面代码二中所示,虽然形参经过类型擦除后都为List类型,但是返回类型不同,这是可以的。

/*代码一:编译时错误*/ 

public class Erasure{
           public void test(int i){
              System.out.println("Sting");
           }
           public int test(int i){
              System.out.println("Integer");
           }
  }

/*代码二:正确*/

 public class Erasure{
           public voidtest(List<String> ls){
              System.out.println("Sting");
           }
           public int test(List<Integer> li){
              System.out.println("Integer");
           }
  }


2)泛型类型是被所有调用共享的

所有泛型类的实例都共享同一个运行时类,类型参数信息会在编译时被擦除。因此考虑如下代码,虽然ArrayList<String>ArrayList<Integer>类型参数不同,但是他们都共享ArrayList类,所以结果会是true

List<String>l1= new ArrayList<String>();

List<Integer>l2= new ArrayList<Integer>();

System.out.println(l1.getClass() ==l2.getClass()); //True


3instanceof

不能对确切的泛型类型使用instanceOf操作。如下面的操作是非法的,编译时会出错。

Collection cs = newArrayList<String>();

if (cs instanceof Collection<String>){…}// compileerror.如果改成instanceofCollection<?>则不//会出错。


4)泛型数组问题

不能创建一个确切泛型类型的数组。如下面代码会出错。

List<String>[]lsa = new ArrayList<String>[10];//compileerror.

因为如果可以这样,那么考虑如下代码,会导致运行时错误。

List<String>[]lsa = new ArrayList<String>[10]; //实际上并不允许这样创建数组

Object o = lsa;

Object[] oa =(Object[]) o;

List<Integer>li= new ArrayList<Integer>();

li.add(newInteger(3));

oa[1] = li;//unsound, but passes run time store check

String s = lsa[1].get(0); //run-timeerror - ClassCastException

因此只能创建带通配符的泛型数组,如下面例子所示,这回可以通过编译,但是在倒数第二行代码中必须显式的转型才行,即便如此,最后还是会抛出类型转换异常,因为存储在lsa中的是List<Integer>类型的对象,而不是List<String>类型。最后一行代码是正确的,类型匹配,不会抛出异常。

List<?>[] lsa= new List<?>[10]; // ok, array of unbounded wildcard type

Object o = lsa;

Object[] oa =(Object[]) o;

List<Integer>li= new ArrayList<Integer>();

li.add(newInteger(3));

oa[1] = li;//correct

String s = (String)lsa[1].get(0);// run time error, but cast isexplicit

Integer it = (Integer)lsa[1].get(0); // OK


http://www.aqee.net/java-generics-quick-tutorial/
http://www.infoq.com/cn/articles/cf-java-generics
http://blog.csdn.net/daniel_h1986/article/details/5708605
http://www.cnblogs.com/stephen-liu74/archive/2012/01/20/2228938.html
sun官方文档:generics-tutorial.pdf


转载自:http://qiemengdao.iteye.com/blog/1525624

参考:

IBM系列:

        Java 理论和实践: 了解泛型:http://www.ibm.com/developerworks/cn/java/j-jtp01255.html

        Java 理论与实践: 使用通配符简化泛型使用:http://www.ibm.com/developerworks/cn/java/j-jtp04298.html

        多角度看 Java 中的泛型:http://www.ibm.com/developerworks/cn/java/j-lo-gj/

        诊断 Java 代码: 轻松掌握 Java 泛型:http://www.ibm.com/developerworks/cn/java/j-djc02113/

Java泛型-类型擦除:http://blog.csdn.net/caihaijiang/article/details/6403349

java泛型:http://www.cnblogs.com/panjun-Donet/archive/2008/09/27/1300609.html

掌握 Java 泛型类型(一):http://blog.csdn.net/kalex/article/details/195562

JAVA泛型详解:http://blog.csdn.net/mtawaken/article/details/9813581

                     http://blog.csdn.net/jinuxwu/article/details/6771121

java泛型(一)、泛型的基本介绍和使用:http://blog.csdn.net/lonelyroamer/article/details/7864531

Java泛型:类型檫除、模板和泛型传递:

http://shentar.me/java泛型:类型檫除、模板和泛型传递/


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值