Java泛型

简介

JDK 5.0以后增加了几个新的扩展功能,其中一个就是泛型。泛型允许我们把类型泛化。以5.0版本以前的Collections为例子:

List myIntList = new LinkedList(); // 1
myIntList.add(new Integer(0)); // 2
Integer x = (Integer) myIntList.iterator().next(); // 3 

比如说第三行,程序员实际上是知道数据是什么类型,但不得不进行强制转化,因为编译器只能保证从iterator返回的类型是Object。当然有时候程序员也会犯错,比如说记错返回类型导致运行时错误。
为了避免上述问题,引入泛型,改造如下

List<Integer> myIntList = new LinkedList<Integer>(); // 1'
myIntList.add(new Integer(0)); // 2'
Integer x = myIntList.iterator().next(); // 3'

通过传入类型参数Integer,在第三步把烦人的强制转化给去掉了,编译器现在在编译阶段确保返回的类型安全,避免了上述问题,这也增强了程序的可读性及稳定性。

定义基本泛型

拿List作为例子

public interfacep  List <E> {
    void add(E x);
    Iterator<E> iterator();
}

在简介中我们定义了List<Integer>,我们或者可以认为List<Integer>是E被Integer代表的List的版本,类似下面的代码:

public interface IntegerList {
    void add(Integer x);
    Iterator<Integer> iterator();
}

但实际上在Java中,源文件、可执行文件、硬盘已经内存中都不会存在多份不同类型的List实现,这跟C++的模板实现方式很不一样。泛化类只会编译一次,生成一个class文件,跟原来的类或者接口生成一样。

泛型及子类型

比如下面的代码

List<String> ls = new ArrayList<String>(); // 1
List<Object> lo = ls; // 2 

第一步明显不会有问题,第二步是不是合法呢,也就是说List<String>是不是List<Object>的子类。为了确认一下,我们增加多几行代码

lo.add(new Object()); // 3
String s = ls.get(0); // 4: 试图把Object强制转化成String

假设第二步合法,则我们可以在lo中插入任意对象。因此ls可能会包含非String对象,从而有可能导致出错。
实际上第2步代码,编译器在编译阶段就会报编译错误,因为List<String>不是List<Object>的子类。

通配符

假设需要为collection写一个打印所有元素的方法,在5.0版本以前可以这样写

void printCollection(Collection c) {
    Iterator i = c.iterator();
    for (k = 0; k < c.size(); k++) {
        System.out.println(i.next());
    }
}

5.0版本以后尝试写一个新的通用版本,比如下面的代码

void printCollection(Collection<Object> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}

实际上这个新版本会不如老的那个版本。老的版本可以支持任意的collection类型,而新版本只支持Collection<Object>,因为Collection<Object>并不是我们想要任意collection类型的父类。

那什么才是任意collection的父类呢?Collection<?>,?就是所说的通配符,表示未知类型的collection,可以匹配任意的collection。代码改写如下

void printCollection(Collection<?> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}

上面代码中我们读取出来的元素赋予Object类型,这样做是安全的,因为无论是哪类型的collection,它里面的元素都可以转成Object。但是,向里面添加任意对象确是不安全的,例如

Collection<?> c = new ArrayList<String>();
c.add(new 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) {
        ...
    }
}

public class Canvas {
    public void draw(Shape s) {
        s.draw(this);
   }
}

每个画布上实际上有一系列形状要画,假设我们添加一个drawAll的函数

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

很明显上述只能支持List<Shape>的绘制,与我们的预期不符合。通过带限定的通配符,可以改成

public void drawAll(List<? extends Shape> shapes) {
    ...
}

通过这种方式,告诉编译器,?号代表的未知类型实际上是Shape的自身或者子类。

泛型方法

假设我们需要写一个方法,把Object数组的元素全部放入Collection中,假设代码如下

static void fromArrayToCollection(Object[] a, Collection<?> c) {
    for (Object o : a) { 
        c.add(o); // 编译错误
    }
}

当然通过上述章节,可以把Collection<?>改成Collection<Object>,但这并非是我们想要的。通过泛型方式改造

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

上面会涉及到一个问题,什么时候使用通配符,什么时候需要使用泛型方法。为了了解,我们看一下Collection内部的几个方法

interface Collection<E> {
    public boolean containsAll(Collection<?> c);
    public boolean addAll(Collection<? extends E> c);
}

我们可以用泛型方法重写

interface Collection<E> {
    public <T> boolean containsAll(Collection<T> c);
    public <T extends E> boolean addAll(Collection<T> c);
}

可以看到,在两个方法中,T实际上只是被用到1次,同时返回值并不依赖于T。而在上述例子中只是为了支持不同类型的Collection可以被传入而已,返回值也跟传入的类型参数无关,使用通配符更加适合。

关于泛型的Class

假设有以下这段代码

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

第3步返回的结果实际上是true,因为无论泛化类型是否一样,同样泛型类的实体对象在运行时的Class都一样。

的确,事实上一个泛型化的类是指该类支持无论传入任何的泛化类型都遵守同样的行为。因此,泛化参数和泛化修饰符无法作用于静态方法或者静态区域,因为静态方法类的所有的实例都会公用,违背上述原则。

基于上面的描述,假设以下的代码

Collection cs = new ArrayList<String>();
if (cs instanceof Collection<String>) { ... }

第2步实际上非法,因为所有的泛型类实例共享同一个泛型类,并不存在Collection<String>。同样的,如下面的代码

Collection<String> cstr = (Collection<String>) cs; //1

<T> T badCast(T t, Object o) {
    return (T) o;    // 2
}

第1段代码会抛出Unchecked warning,因为Collection<String>在运行时并不存在,所以运行时系统实际上并不会做检查。同样,第3段代码中T在运行时也不存在。这表示使用过程中我们并不能保证代码安全。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 泛型,一个孤独的守门者。 大家可能会有疑问,我为什么叫做泛型是一个守门者。这其实是我个人的看法而已,我的意思是说泛...
    传奇内服号阅读 442评论 0 0
  • 参考地址:《Java 泛型,你了解类型擦除吗?》 《Java中的逆变与协变》 《java 泛型中 T、E .....
    琦小虾阅读 3,149评论 0 11
  • 泛型,一个孤独的守门者。 大家可能会有疑问,我为什么叫做泛型是一个守门者。这其实是我个人的看法而已,我的意思是说泛...
    jackcooper阅读 496评论 2 2
  • Java1.5版本中增加了泛型。在没有泛型之前,从集合中读取到的每一个对象都必须进行转换。如果不小心插入了错误类型...
    塞外的风阅读 972评论 0 0
  • 开发人员在使用泛型的时候,很容易根据自己的直觉而犯一些错误。比如一个方法如果接收List作为形式参数,那么如果尝试...
    时待吾阅读 1,127评论 0 3

友情链接更多精彩内容