现在的位置: 首页 > 综合 > 正文

java 泛型

2014年02月13日 ⁄ 综合 ⁄ 共 4426字 ⁄ 字号 评论关闭

一、

使用泛型的好处在于,它在编译的时候进行类型安全检查,并且在运行时所有的转换都是强制的,隐式的,大大提高了代码的重用率。

请看下面这个例子

public class Gen<T> {

 private T ob; // 定义泛型成员变量

 public Gen(T ob) {
  this.ob = ob;
 }

 public T getOb() {
  return ob;
 }

 public void setOb(T ob) {
  this.ob = ob;
 }

 public void showTyep() {
  System.out.println("T的实际类型是: " + ob.getClass().getName());
 }

}

public class GenDemo {

 public static void main(String[] args) {
  // 定义泛型类Gen的一个Integer版本
  Gen<Integer> intOb = new Gen<Integer>(88);
  intOb.showTyep();

  int i = intOb.getOb();
  System.out.println("value= " + i);
  System.out.println("----------------------------------");

  // 定义泛型类Gen的一个String版本
  Gen<String> strOb = new Gen<String>("Hello Gen!");
  strOb.showTyep();
  String s = strOb.getOb();
  System.out.println("value= " + s);
 }
}

运行结果:

T的实际类型是: java.lang.Integer
value= 88
----------------------------------
T的实际类型是: java.lang.String
value= Hello Gen!

 

可见参数T可以接受所有的类型,这里相当于object的作用,但是它可以增加代码的复用性。要是不用泛型的话,我们要分别针对String和Integer写2个类,如果不写的话,写object要强制转换,这样就可能抛出ClassCastException异常。这是潜在的,而用泛型在编译期就进行检测,所以出现这种可能性的问题小。

二、

  List<String> l1 = new ArrayList<String>();
  List<Integer> l2 = new ArrayList<Integer>();
  System.out.println(l1.getClass() == l2.getClass());

你也许会认为这段代码的运行结果是false,可是它是true。

因为一个泛型类型的声明只被编译一次,并且得到一个class文件,就像普通的class或者interface的声明一样。打印出来的话它是

class java.util.ArrayList,可是都是:

类型参数就跟在方法或构造函数中普通的参数一样。就像一个方法有形式参数来描述它操作的参数的种类一样,一个泛型声明也有形式类型参数。当一个方法被调用,实参替换形参,方法体被执行。当一个泛型声明被调用,实际类型参数取代形式类型参数。就把后面的看成普通参数,所以这样程序的运行结果是true了。

三、

List<String> list = new ArrayList<String>();
List<Object> lo = list;

这2行代码是无法编译通过,当你第一眼看到的时候可能会认为他们是对的,因为String本来就是Object的一个子类,不过请看下面代码:

lo.add(new Number());

String string = lo.get(0);

这样显然是不对的。因为他传递的是一个引用,强行把Number转换成String肯定是不正确的。所以在泛型和子类继承上应该注意。

四、

考虑写一个例程来打印一个集合(Collection)中的所有元素。下面是在老的语言中你可能写的代码:

            void printCollection(Collection c) {

                 Iterator i = c.iterator();

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

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

                  }

            }

下面是一个使用泛型的幼稚的尝试(使用了新的循环语法):

      void printCollection(Collection<Object> c) {

           for (Object e : c) {

                 System.out.println(e);

           }

      }

问题是新版本的用处比老版本小多了。老版本的代码可以使用任何类型的collection作为参数,而新版本则只能使用Collection<Object>,我们刚才阐述了,它不是所有类型的collections的父类。

那么什么是各种collections的父类呢?它写作: Collection<?>(发音为:"collection of unknown"),就是,一个集合,它的元素类型可以匹配任何类型。显然,它被称为通配符。我们可以写:

void printCollection(Collection<?> c) {

for (Object e : c) {

System.out.println(e);

}

}

现在,我们可以使用任何类型的collection来调用它。注意,我们仍然可以读取c中的元素,其类型是Object。这永远是安全的,因为不管collection的真实类型是什么,它包含的都是objects。但是将任意元素加入到其中不是类型安全的:

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

c.add(new Object()); // 编译时错误

因为我们不知道c的元素类型,我们不能向其中添加对象。

add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。唯一的例外是null,它是所有类型的成员。

另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object,因此把get的返回值赋值给一个Object类型的对象或者放在任何希望是Object类型的地方是安全的。

下面是一个通配符具体的用法:

public abstract class Shape {
 public abstract void draw(Canvas c);
}

public class Circle extends Shape {

 private int x, y, radius;

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

}

public class Rectangle extends Shape {

 private int x, y, width, height;

 public void draw(Canvas c) {

  // ...

 }

这些类可以在一个画布(Canvas)上被画出来:

public class Canvas {

  public void draw(Shape s) {

    s.draw(this);

  }

}

所有的图形通常都有很多个形状。假定它们用一个list来表示,Canvas里有一个方法来画出所有的形状会比较方便:

public void drawAll(List<Shape> shapes) {

  for (Shape s : shapes) {

     s.draw(this);

  }

}

我们添加一个这样的调用:

List<Shape> shapes = new ArrayList<Shape>();
shapes.add(c);
shapes.add(r);
ca.drawAll(shapes);

很明显这调用时正确的,可是下面这种调用时错误的。

List<Circle> list = new ArrayList<Circle>();
list.add(c);
Canvas ca = new Canvas();
ca.draw(c);
ca.drawAll(list);

在最后一行出现错误,提示只能接受Shape类型的,可见并没有因为Circle是Shape的子类而改变什么,因为在这个时候编译器只是接受Shape类型,而我们所需要时能够接受Shape所有的子类,所以我们采用下面这种方法:

public void  drawAll(List<? extends Shape> shapes) {
  for (Shape s : shapes) {
   s.draw(this);
  }

}

表明接受Shape下左右的方法,这样我们在传递Shape子类的方式就行了。此处是通配符的一种用法,当然此处还可以用super表示接受Shape以上的。

五、

下面代码:

 static void fromArrayToCollection(Object[] a, Collection<Object> c) {
  for (Object o : a) {
   c.add(o); 
  }
 }

String[] str = new String[10];
  Collection<String> list = new ArrayList<String>();
  
  Object[] strO = new Object[10];
  Collection<Object> listO = new ArrayList<Object>();
  
  fromArrayToCollection(strO, listO);
  fromArrayToCollection(str, list);

在最后一行出现编译期异常,因为只能接受Object,这个特点上面已经说清楚了这就需要做一下修改了

 static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
  for (T o : a) {
   c.add(o); 

  }
 }

这样就没有问题了

此处T和Object的区别是T是一个参数,没有具体指出是什么类型,所以可以接受所有的类型,而Object必须接受Object类型,这就是泛型的好处。

泛型函数允许类型参数被用来表示方法的一个或多个参数之间的依赖关系,或者参数与其返回值的依赖关系。如果没有这样的依赖关系,不应该使用泛型方法。

总结:

以上就是个人对泛型的理解,不过还有一点就是我们应该什么时候使用泛型方法,又什么时候使用通配符类型呢?

参考文档:

http://blog.csdn.net/turkeyzhou/archive/2008/09/05/2888774.aspx

抱歉!评论已关闭.