Java泛型编程

thanks:http://www.cnblogs.com/oubo/archive/2012/01/07/2394639.html

以下介绍经常使用的集合类,这里不介绍集合类的使用方法,只介绍每个集合类的用途和特点,然后通过比较相关集合类的不同特点来让我们更深入的了解它们。  

Collection接口

  • Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。
  • 所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个 Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后 一个构造函数允许用户复制一个Collection。
  • 如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代器,使用该迭代器即可遍历访问Collection中每一个元素,通过Iterator的遍历是无序的

典型的用法如下:

Iterator it = collection.iterator(); // 获得一个迭代子
while(it.hasNext()) {
Object obj = it.next(); // 得到下一个元素
}
 

List接口

  • List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
  • 除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个 ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素, 还能向前或向后遍历。
  • 实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。

 

LinkedList类

  • LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
  • 注意LinkedList没有同步性。如果多个线程同时访问一个LinkedList,则必须自己实现访问同步。另一种解决方法是在创建List时构造一个同步的List:List list = Collections.synchronizedList(new LinkedList(...));

 

ArrayList类

  • ArrayList实现了大小可变的数组。它允许所有元素,包括null。ArrayList没有同步性。
  • size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
  • 每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。

 

Vector类

Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和 ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了 Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出 ConcurrentModificationException,因此必须捕获该异常。  

Stack 类

Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop 方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。

Vector、ArrayList和LinkedList比较

  1. 1.Vector是线程同步的,所以它也是线程安全的,而ArrayList和LinkedList是非线程安全的。如果不考虑到线程的安全因素,一般用ArrayList和LinkedList效率比较高。
  2. 2.ArrayList和Vector是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
  3. 3.如果集合中的元素的数目大于目前集合数组的长度时,Vector增长率为目前数组长度的100%,而ArrayList增长率为目前数组长度的50%.如果在集合中使用数据量比较大的数据,用vector有一定的优势;反之,用ArrayList有优势。
  4. 3.如果查找一个指定位置的数据,Vector和ArrayList使用的时间是相同的,花费时间为O(1),而LinkedList需要遍历查找,花费时间为O(i),效率不如前两者。
  5. 4.而如果移动、删除一个指定位置的数据花费的时间为0(n-i)n为总长度,这个时候就应该考虑使用LinkedList,因为它移动一个指定位置的数据所花费的时间为0(1)。
  6. 5.对于在指定位置插入数据,LinedList比较占优势,因为ArrayList要移动数据。

 

Set接口

  • Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。
  • 很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。
  • 请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。

 

Map接口

请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个 value。

Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。  


Hashtable类

  • Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value
  • 添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。
  • Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。
  • 由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object。
  • Hashtable是同步的。

 

HashMap类

HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap 的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。  


TreeMap类

  • HashMap通过hashcode对其内容进行快速查找,无序的;而TreeMap中所有的元素都保持着某种固定的顺序,有序的。
  • 在Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()和 equals()的实现。
  • TreeMap没有调优选项,因为该树总处于平衡状态。

 

WeakHashMap类

WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。  


总结

  • 如果涉及到堆栈,队列等操作,应该考虑用List;对于需要快速插入,删除元素,应该使用LinkedList;如果需要快速随机访问元素,应该使用ArrayList。
  • 如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高;如果多个线程可能同时操作一个类,应该使用同步的类。
  • 要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
  • 使用Map时,查找、更新、删除、新增最好使用HashMap或HashTable;对Map进行自然顺序或自定义键顺序遍历时,最好使用TreeMap;
  • 尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。

/*****************************************************************************************************************************/


thanks:http://qiemengdao.iteye.com/blog/1525624

1介绍

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

 

Java代码   收藏代码
  1. List myIntList=new LinkedList(); //1  
  2. myIntList.add(newInteger(0)); //2  
  3. Integer x=(Integer)myIntList.iterator().next(); //3  
 

注意第3行代码,但这是让人很不爽的一点,因为程序员肯定知道自己存储在List里面的对象类型是Integer,但是在返回列表中元素时,还是必须强制转换类型,这是为什么呢?原因在于,编译器只能保证迭代器的next()方法返回的是Object类型的对象,为保证Integer变量的类型安全,所以必须强制转换。

这种转换不仅显得混乱,更可能导致类型转换异常ClassCastException,运行时异常往往让人难以检测到。保证列表中的元素为一个特定的数据类型,这样就可以取消类型转换,减少发生错误的机会, 这也是泛型设计的初衷。下面是一个使用了泛型的例子:

 

Java代码   收藏代码
  1. List<Integer> myIntList=newLinkedList<Integer>(); //1’  
  2. myIntList.add(newInteger(0)); //2’  
  3. Integerx=myIntList.iterator().next(); //3’  
 

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

 

2定义简单的泛型

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

 

Java代码   收藏代码
  1. public interface List<E> {  
  2. <span style="white-space: pre;">    </span>void add(E x);  
  3. <span style="white-space: pre;">    </span>Iterator<E> iterator();  
  4. }  
  5. public interface Iterator<E> {  
  6. <span style="white-space: pre;">    </span>E next();  
  7. <span style="white-space: pre;">    </span>boolean hasNext();  
  8. }  
 

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

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

 

Java代码   收藏代码
  1. public interface IntegerList {  
  2. <span style="white-space: pre;">    </span>void add(Integer x)  
  3. <span style="white-space: pre;">    </span>Iterator<Integer> iterator();  
  4. }  
 

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

 

Java代码   收藏代码
  1. /*会导致编译时错误*/   
  2.  public class Erasure{  
  3.             public void test(List<String> ls){  
  4.                 System.out.println("Sting");  
  5.             }  
  6.             public void test(List<Integer> li){  
  7.                 System.out.println("Integer");  
  8.             }  
  9.   }  
 

       那么这就有个问题了,既然在编译的时候会在方法和类中擦除实际类型的信息,那么在返回对象时又是如何知道其具体类型的呢?如List<String>编译后会擦除掉String信息,那么在运行时通过迭代器返回List中的对象时,又是如何知道List中存储的是String类型对象呢?

       擦除在方法体中移除了类型信息,所以在运行时的问题就是边界即对象进入和离开方法的地点,这正是编译器在编译期执行类型检查并插入转型代码的地点。泛型中的所有动作都发生在边界处:对传递进来的值进行额外的编译期检查,并插入对传递出去的值的转型。

 

3.泛型和子类型

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

 

Java代码   收藏代码
  1. List<Apple> apples = new ArrayList<Apple>(); //1  
  2. List<Fruit> fruits = apples; //2  
 

第1行代码显然是对的,但是第2行是否对呢?我们知道Fruit fruit = new Apple(),这样肯定是对的,即苹果肯定是水果,但是第2行在编译的时候会出错。这会让人比较纳闷的是一个苹果是水果,为什么一箱苹果就不是一箱水果了呢?可以这样考虑,我们假定第2行代码没有问题,那么我们可以使用语句fruits.add(new Strawberry())(Strawberry为Fruit的子类)在fruits中加入草莓了,但是这样的话,一个List中装入了各种不同类型的子类水果,这显然是不可以的,因为我们在取出List中的水果对象时,就分不清楚到底该转型为苹果还是草莓了。

通常来说,如果FooBar的子类型,G是一种带泛型的类型,则G<Foo>不是G<Bar>的子类型。这也许是泛型学习里面最让人容易混淆的一点。

 

4.通配符

4.1通配符?

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

 

Java代码   收藏代码
  1. //不使用泛型  
  2. void printCollection(Collection c) {                  
  3. <span style="white-space: pre;">    </span>Iterator i=c.iterator();  
  4. <span style="white-space: pre;">    </span>for (k=0;k < c.size();k++) {  
  5. <span style="white-space: pre;">        </span>System.out.println(i.next());  
  6. <span style="white-space: pre;">    </span>}  
  7. }  
 

 

Java代码   收藏代码
  1. //使用泛型  
  2. void printCollection(Collection<Object> c) {  
  3. for (Object e:c) {  
  4. System.out.println(e);  
  5. }  
  6. }  
 

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

 

Java代码   收藏代码
  1. //使用通配符?,表示可以接收任何元素类型的集合作为参数  
  2. void printCollection(Collection<?> c) {  
  3. <span style="white-space: pre;">    </span>for (Object e:c) {  
  4. <span style="white-space: pre;">        </span>System.out.println(e);  
  5. <span style="white-space: pre;">    </span>}  
  6. }  
 

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

 

Java代码   收藏代码
  1. Collection<?> c=new ArrayList<String>();  
  2. c.add(newObject()); //compile time error,不管加入什么对象都出错,除了null外。  
  3. c.add(null); //OK  
 

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

4.2边界通配符

   1)?extends通配符

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

 

Java代码   收藏代码
  1. public abstract class Shape {  
  2. <span style="white-space: pre;">    </span>public abstract void draw(Canvas c);  
  3. }  
  4.   
  5. public class Circle extends Shape {  
  6. <span style="white-space: pre;">    </span>private int x,y,radius;  
  7. <span style="white-space: pre;">    </span>public void draw(Canvas c) { ... }  
  8. }  
  9.   
  10. public class Rectangle extends Shape  
  11. <span style="white-space: pre;">    </span>private int x,y,width,height;  
  12. <span style="white-space: pre;">    </span>public void draw(Canvasc) { ... }  
  13. }  
 

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

 

Java代码   收藏代码
  1. //原始版本  
  2. public void drawAll(List<Shape> shapes) {  
  3. <span style="white-space: pre;">    </span>for (Shapes:shapes) {  
  4. <span style="white-space: pre;">        </span>s.draw(this);  
  5. <span style="white-space: pre;">    </span>}  
  6. }  
 

 

Java代码   收藏代码
  1. //使用边界通配符的版本  
  2. public void drawAll(List<?exends Shape> shapes) {  
  3. <span style="white-space: pre;">    </span>for (Shapes:shapes) {  
  4. <span style="white-space: pre;">        </span>s.draw(this);  
  5. <span style="white-space: pre;">    </span>}  
  6. }  
 

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

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

那么这时会出现一个编译时错误,原因在于:我们只知道shapes中的元素时Shape类型的子类型,具体是什么子类型我们并不清楚,所以我们不能往shapes中加入任何类型的对象。不过我们在取出其中对象时,可以使用Shape类型来取值,因为虽然我们不知道列表中的元素类型具体是什么类型,但是我们肯定的是它一定是Shape类的子类型。

 

2)?super通配符

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

 

Java代码   收藏代码
  1. List<Shape> shapes = new ArrayList<Shape>();  
  2. List<? super Cicle> cicleSupers = shapes;  
  3. cicleSupers.add(new Cicle()); //OK, subclass of Cicle also OK  
  4. cicleSupers.add(new Shape()); //ERROR  
 

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

 

3)边界通配符总结

<!--[if !supportLists]-->l         <!--[endif]-->如果你想从一个数据类型里获取数据,使用 ? extends 通配符

<!--[if !supportLists]-->l         <!--[endif]-->如果你想把对象写入一个数据结构里,使用 ? super 通配符

<!--[if !supportLists]-->l         <!--[endif]-->如果你既想存,又想取,那就别用通配符。

 

5.泛型方法

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

 

Java代码   收藏代码
  1. static void fromArrayToCollection(Object[]a, Collection<?> c) {  
  2. <span style="white-space: pre;">    </span>for (Object o:a) {  
  3. <span style="white-space: pre;">        </span>c.add(o); //compile time error  
  4. <span style="white-space: pre;">    </span>}  
  5. }  
 

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

 

Java代码   收藏代码
  1. static <T> void fromArrayToCollection(T[] a, Collection<T>c){  
  2. <span style="white-space: pre;">    </span>for(T o : a) {  
  3. <span style="white-space: pre;">        </span>c.add(o);// correct  
  4. <span style="white-space: pre;">    </span>}  
  5. }  
 

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

 

Java代码   收藏代码
  1. Object[] oa = new Object[100];  
  2. Collection<Object>co = new ArrayList<Object>();  
  3. fromArrayToCollection(oa, co);// T inferred to be Object  
  4. String[] sa = new String[100];  
  5. Collection<String>cs = new ArrayList<String>();  
  6. fromArrayToCollection(sa, cs);// T inferred to be String  
  7. fromArrayToCollection(sa, co);// T inferred to be Object  
  8. Integer[] ia = new Integer[100];  
  9. Float[] fa = new Float[100];  
  10. Number[] na = new Number[100];  
  11. Collection<Number>cn = new ArrayList<Number>();  
  12. fromArrayToCollection(ia, cn);// T inferred to be Number  
  13. fromArrayToCollection(fa, cn);// T inferred to be Number  
  14. fromArrayToCollection(na, cn);// T inferred to be Number  
  15. fromArrayToCollection(na, co);// T inferred to be Object  
  16. fromArrayToCollection(na, cs);// compile-time error  
 

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

 

Java代码   收藏代码
  1. public  <T> void go(T t) {  
  2.     System.out.println("generic function");  
  3. }  
  4. public void go(String str) {  
  5.     System.out.println("normal function");  
  6. }  
  7. public static void main(String[] args) {  
  8.         FuncGenric fg = new FuncGenric();  
  9.         fg.go("haha");//打印normal function  
  10.         fg.<String>go("haha");//打印generic function  
  11.         fg.go(new Object());//打印generic function  
  12.         fg.<Object>go(new Object());//打印generic function  
  13. }  
 

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

6.其他需要注意的小点

1)方法重载

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

 

Java代码   收藏代码
  1. /*代码一:编译时错误*/   
  2. public class Erasure{  
  3.             public void test(int i){  
  4.                 System.out.println("Sting");  
  5.             }  
  6.             public int test(int i){  
  7.                 System.out.println("Integer");  
  8.             }  
  9.   }  
 

 

Java代码   收藏代码
  1. /*代码二:正确 */  
  2.  public class Erasure{  
  3.             public void test(List<String> ls){  
  4.                 System.out.println("Sting");  
  5.             }  
  6.             public int test(List<Integer> li){  
  7.                 System.out.println("Integer");  
  8.             }  
  9.   }  
 

 

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

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

      

 

Java代码   收藏代码
  1. List<String>l1 = new ArrayList<String>();  
  2. List<Integer>l2 = new ArrayList<Integer>();  
  3. System.out.println(l1.getClass() == l2.getClass()); //True  
 

 

3)instanceof

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

 

Java代码   收藏代码
  1. Collection cs = new ArrayList<String>();  
  2. if (cs instanceof Collection<String>){…}// compile error.如果改成instanceof Collection<?>则不会出错。  
 

 

4)泛型数组问题

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

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

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

 

Java代码   收藏代码
  1. List<String>[] lsa = new ArrayList<String>[10]; // 实际上并不允许这样创建数组  
  2. Object o = lsa;  
  3. Object[] oa = (Object[]) o;  
  4. List<Integer>li = new ArrayList<Integer>();  
  5. li.add(new Integer(3));  
  6. oa[1] = li;// unsound, but passes run time store check  
  7. String s = lsa[1].get(0); //run-time error - ClassCastException  
 

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

Java代码   收藏代码
  1. List<?>[] lsa = new List<?>[10]; // ok, array of unbounded wildcard type  
  2. Object o = lsa;  
  3. Object[] oa = (Object[]) o;  
  4. List<Integer>li = new ArrayList<Integer>();  
  5. li.add(new Integer(3));  
  6. oa[1] = li; //correct  
  7. String s = (String) lsa[1].get(0);// run time error, but cast is explicit  
  8. 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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值