Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3336288
  • 博文数量: 530
  • 博客积分: 13360
  • 博客等级: 上将
  • 技术积分: 5473
  • 用 户 组: 普通用户
  • 注册时间: 2006-07-13 13:32
文章分类

全部博文(530)

文章存档

2017年(1)

2015年(2)

2013年(24)

2012年(20)

2011年(97)

2010年(240)

2009年(117)

2008年(12)

2007年(8)

2006年(9)

分类: Java

2013-07-15 17:50:27

1.概述
泛型是JAVA SE 1.5的新特性。
如果一个类或接口上有一个或多个类型变量,那它就是泛型。类型变量由尖括号界定,放在类或接口名的后面:
      public interface List extends Collection {


泛型的本质:将操作的数据类型被指定为一个参数。
     List list=…
     List接口里的get方法是这样的:
           T get(int index);
     通过泛型,就可以让编译器对类型进行检查

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

2.为什么要使用泛型
理解Java泛型最简单的方法是把它看成一种便捷语法,能节省你某些Java类型转换(casting)上的操作:
        1 List box = ...;
        2 Apple apple = box.get(0);
       上面的代码自身已表达的很清楚:box是一个装有Apple对象的List。get方法返回一个Apple对象实例,这个过程不需要进行类型转换。
没有泛型,上面的代码需要写成这样:
        1 List box = ...;
        2 Apple apple = (Apple) box.get(0);
     
泛型的主要好处就是让编译器保留参数的类型信息,执行类型检查,执行类型转换操作:编译器保证了这些类型转换的绝对无误。
相对于依赖程序员来记住对象类型、执行类型转换——这会导致程序运行时的失败,很难调试和解决,而编译器能够帮助程序员在编译时强制进行大量的类型检查,发现其中的错误。

如果我们试图在List装入另外一种对象,编译器就会提示错误:
          list.add(apple); // 不能编译
          类型安全的读取数据…

当我们在使用List对象时,它总能保证我们得到的是一个Apple对象:
       Apple apple = list.get(0);

3.泛型与Object
没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
 举例

点击(此处)折叠或打开

  1. public class Gen<T> {
  2.         private T ob;     //定义泛型成员变量
  3.         public Gen(T ob) {
  4.                 this.ob = ob;
  5.         }
  6.         public T getOb() {
  7.                 return ob;
  8.         }
  9.         public void setOb(T ob) {
  10.                 this.ob = ob;
  11.         }
  12.         public void showTyep() {
  13.                 System.out.println("T的实际类型是: " + ob.getClass().getName());
  14.         }
  15. }
测试代码

点击(此处)折叠或打开

  1. public class GenDemo {
  2.         public static void main(String[] args){

  3.                 //定义泛型类Gen的一个Integer版本
  4.                 Gen<Integer> intOb=new Gen<Integer>(88);
  5.                 intOb.showTyep();
  6.                 int i= intOb.getOb();
  7.                 System.out.println("value= " + i);

  8.                 System.out.println("----------------------------------");

  9.                 //定义泛型类Gen的一个String版本
  10.                 Gen<String> strOb=new Gen<String>("Hello Gen!");
  11.                 strOb.showTyep();
  12.                 String s=strOb.getOb();
  13.                 System.out.println("value= " + s);
  14.         }
  15. }
运行结果:
T的实际类型是: java.lang.Integer
value= 88
----------------------------------
T的实际类型是: java.lang.String
value= Hello Gen!

未使用泛型举例

点击(此处)折叠或打开

  1. public class Gen2 {
  2.         private Object ob; //定义一个通用类型成员

  3.         public Gen2(Object ob) {
  4.                 this.ob = ob;
  5.         }
  6.         public Object getOb() {
  7.                 return ob;
  8.         }
  9.         public void setOb(Object ob) {
  10.                 this.ob = ob;
  11.         }

  12.         public void showTyep() {
  13.                 System.out.println("T的实际类型是: " + ob.getClass().getName());
  14.         }
  15. }
测试代码

点击(此处)折叠或打开

  1. public class GenDemo2 {
  2.         public static void main(String[] args) {
  3.                 //定义类Gen2的一个Integer版本
  4.                 Gen2 intOb = new Gen2(new Integer(88));
  5.                 intOb.showTyep();
  6.                 int i = (Integer) intOb.getOb();
  7.                 System.out.println("value= " + i);
  8.                 System.out.println("----------------------------------");
  9.                 //定义类Gen2的一个String版本
  10.                 Gen2 strOb = new Gen2("Hello Gen!");
  11.                 strOb.showTyep();
  12.                 String s = (String) strOb.getOb();
  13.                 System.out.println("value= " + s);
  14.         }
  15. }

运行结果:

T的实际类型是: java.lang.Integer

value= 88

----------------------------------

T的实际类型是: java.lang.String

value= Hello Gen!


4.泛型与继承
     假设有父类Fruit<-Apple,Strawberry,Apple<-FujiApple

以下代码无效:
     List apples;
     List fruits = ...;
     Fruits= apples;

为什么一箱苹果就不是一箱水果了呢?现在假设以上操作允许,那么下面的操作会出错误
     fruits.add(new Strawberry());

一个List中装入了各种不同类型的子类水果,这显然是不可以的,因为我们在取出List中的水果对象时,就分不清楚到底该转型为苹果还是草莓了。
所以,泛型在使用时并不考虑类与类之间的继承关系, List并不是 List的子类。

5.泛型与通配符?
不使用泛型,打印集合中所有元素的代码

点击(此处)折叠或打开

  1. void printCollection(Collection c) {
  2.     Iterator i=c.iterator();
  3.     for (k=0;k < c.size();k++) {
  4.         System.out.println(i.next());
  5.     }
  6. }
使用泛型,打印集合中所有元素的代码

点击(此处)折叠或打开

  1. void printCollection(Collection<Object> c) {
  2.     for (Object e:c) {
  3.         System.out.println(e);
  4.     }
  5. }
使用泛型的版本只能接受元素类型为Object类型的集合如ArrayList();如果是ArrayList,则会编译时出错。
因为我们前面说过,Collection并不是所有集合的超类。而老版本可以打印任何类型的集合,那么如何改造新版本以便它能接受所有类型的集合呢?这个问题可以通过使用通配符来解决。修改后的代码如下所示:

点击(此处)折叠或打开

  1. //使用通配符?,表示可以接收任何元素类型的集合作为参数
  2. void printCollection(Collection<?> c) {
  3.     for (Object e:c) {
  4.        System.out.println(e);
  5.     }
  6. }
注意:
   通配符?表示该集合存储的元素类型未知,可以是任何类型。往集合中加入元素需要是一个未知元素类型的子类型,正因为该集合存储的元素类型未知,所以我们没法向该集合中添加任何元素

点击(此处)折叠或打开

  1. Collection<?> c=new ArrayList<String>();
  2. c.add(newObject()); //compile time error,不管加入什么对象都出错,除了null外。
  3. c.add(null);
    另一方面,我们可以从List lists中获取对象,虽然不知道List中存储的是什么类型,但是可以肯定的是存储的类型一定是Object的子类型,所以可以用Object类型来获取值。如for(Object obj: lists),这是合法的。

6.?extends通配符,向上造型一个泛型对象的引用
? extends XX,XX 类是用来限定通配符的上界,XX 类是能匹配的最顶层的类,它只能匹配 XX 类以及 XX 类的子类

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

点击(此处)折叠或打开

  1. public abstract class Shape {
  2.    public abstract void draw(Canvas c);
  3.    }
  4.   
  5. public class Circle extends Shape {
  6.    private int x,y,radius;
  7.    public void draw(Canvas c) { ... }
  8. }
  9.   
  10. public class Rectangle extends Shape
  11.    private int x,y,width,height;
  12.    public void draw(Canvasc) { ... }
  13. }
    为了画出集合中所有的形状,我们可以定义一个函数,该函数接受带有泛型的集合类对象作为参数。
   但是不幸的是,我们只能接收元素类型为Shape的List对象,而不能接收类型为List的对象,这在前面已经说过。为了解决这个问题,所以有了边界通配符的概念。

   这里可以采用public void drawAll(List shapes)来满足条件,这样就可以接收元素类型为Shape子类型的列表作为参数了。

点击(此处)折叠或打开

  1. //原始版本
  2. public void drawAll(List<Shape> shapes) {
  3.     for (Shapes:shapes) {
  4.        s.draw(this);
  5.     }
  6. }

  7. //使用边界通配符的版本
  8. public void drawAll(List<?exends Shape> shapes) {
  9.    for (Shapes:shapes) {
  10.       s.draw(this);
  11.    }
  12. }
注意:
   最好不要这样定义变量List<?exends Shape> list,因为以下语句会出错
         list.add(0, new Rectangle()); //compile-time error
    原因在于:我们只知道list中的元素时Shape类型的子类型,具体是什么子类型我们并不清楚

但是, 以下操作是可行的
     List apples = new ArrayList();
    List fruits = apples;

7.? super通配符,向下造型一个泛型对象的引用
? super XX,XX 类是用来限定通配符的下界,XX 类是能匹配的最底层的类,它只能匹配 XX 类及子类。

点击(此处)折叠或打开

  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对象

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

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

点击(此处)折叠或打开

  1. static void fromArrayToCollection(Object[]a, Collection<?> c) {
  2.    for (Object o:a) {
  3.       c.add(o); //compile time error
  4.    }
  5. }
可以看到显然会出现编译错误,原因在之前有讲过,因为集合c中的类型未知,所以不能往其中加入任何的对象(当然,null除外)。解决该问题的一种比较好的办法是使用泛型方法,如下所示:

点击(此处)折叠或打开

  1. static <T> void fromArrayToCollection(T[] a, Collection<T>c){
  2.    for(T o : a) {
  3.       c.add(o);// correct
  4.    }
  5. }
注意:泛型方法的格式,类型参数需要放在函数返回值之前。
调用举例
     Number[] na = new Number[100];  
      Collection cn = new ArrayList();  
      fromArrayToCollection(ia, cn); 


在某些情况下需要指定传递类型参数,比如当存在与泛型方法相同的方法的时候(方法参数类型不一致),如下面的一个例子: 

点击(此处)折叠或打开

  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. }
      当不指定类型参数时,调用的是普通的方法,如果指定了类型参数,则调用泛型方法。

深入学习泛型,再看文献《
Effective Java (泛型)

参考文献
1.Java泛型编程最全总结.http://qiemengdao.iteye.com/blog/1525624
2.java 泛型 深入.http://www.blogjava.net/fancydeepin/archive/2012/08/25/386241.html
3.Java泛型简明教程.


阅读(5246) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~