为什么要使用泛型?
Java 语言以严谨著称,但在设计的时候忽略了「泛型」这个重要的概念,增加了使用者的责任,使用者需要记住每个元素的类型,同时还得强制转型,编译器无法帮忙,在运行时才会抛出 Class Cast 异常。
泛型是什么?
Java 5 以后,引入了参数化类型(parameterized type),Java 的参数化类型被称为泛型(Generic)。所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参(或叫泛型)将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参)。
深入泛型
上述代码中,向 List 中添加了一个字符串和整数,看起来没问题,但是在使用的时候,我们必须要知道第一个元素是字符串类型,第二个元素是整型,并且还得强制转换,否则运行时会报错。这大大增加了使用者的责任,编译器无法帮忙,只有在运行时才会抛出 Class Cast 异常。
那么怎么解决这个问题呢?答案就是使用泛型。
可以看看上面代码,其中的 T 我们可以把它想象成一个占位符,将来可以传入任意类型,如 Integer、String 等。同时我们也可以看到上面泛型的定义也比较简单,除了尖括号中的内容——这就是泛型的实质:允许在定义类、接口和方法时声明泛型形参,泛型形参在整个接口、类体内可当成类型使用,几乎所有可使用普通类型的地方都可以使用这种泛型形参。可是说来轻巧,那么泛型又是如何实现的呢?
每次实例化一个泛型/模板类都会生成一个新的类。例如模板类是 List,用 int、double、string、Employee 分别去实例化,那么在编译的时候,就会生成 4 个新类,如 List_int、List_double、List_string、List_Employee。
PS 包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参。从而可以动态地生成无数多个逻辑上的子类,但这种子类在物理上是不存在的。所以即使生成了很多新类,但也不必担心系统会膨胀爆炸。
因为 Java 中对此使用的是擦除法,简单来说就是一个参数化的类型经过擦除后会去除参数,如 ArrayList<T> 会被擦除为 ArrayList。我们传入的 String、Integer 等非但没有消失,反而都变成了 Object,如 ArrayList<Integer>被擦除成了原始的 ArrayList。
但是问题又来了,我们使用泛型是为了避开强制转型而减少使用者的责任,而现在类型被擦除了,都变成了 Object,本来可以写成 Integer i = list1.get(0);,但现在被擦除成了 Object,我们该怎么处理呢?
其实很简单,就是在编译的时候做一点手脚,加一个自动的转型,如 Integer i = (Integer)list1.get(0);。
泛型方法
前面说的是泛型类,但是对于一些静态方法来说,怎么将其变成泛型呢?其实很简单,就是将泛型类型 <T> 移到方法上去。如下例子:
不过除了使用 extends 关键字之外,Java 泛型的限制符还支持 super,实际上为了更灵活使用,上面的 Comparable<T> 应该写成 Comparable<? super T>。
泛型和继承
类图代码清单
从上述类图和代码中,我们发现当传递一个 ArrayList<Apple>时编译会出错,可是这到底是怎么回事儿呢? print() 方法能接收的参数不是 ArrayList<Fruit> 吗?
其实这里存在一个误区,就是 Apple 虽然是 Fruit 的子类,但是 ArrayList<Apple> 却不是 ArrayList<Fruit> 的子类,实际上它们之间是没有任何关系的,不能执行转型操作,所以在调用 print() 方法的时候会报错。
【注】如果 Foo 是 Bar 的一个子类型(子类或者子接口),而 G 是具有泛型声明的类或接口,G<Foo> 并不是 G<Bar> 的子类型。
可是为什么不能将 ArrayList<Apple> 转换成 ArrayList<Fruit> 呢?那是因为如果可以这么做,那么我们不但可以向这个 Lsit 中加入 Apple,还能加入 Orange,泛型就被破坏了。
那么对于这个问题要怎么解决呢?可以通过引用通配符的方式加以解决,可以将方法的输入参数修改如下:
也就是说,传进来的参数只要是 Fruit 或者 Fruit 的子类都可以,这样一来就可以接收 ArrayList<Fruit>、ArrayList<Apple> 和 ArrayList<Orange> 这样的参数了。但是有一点需要注意的是,这种带通配符的 List 仅表示它是各种泛型 List 的父类,并不能向其中添加元素,比如如下代码就会引起编译错误。这是因为程序无法确定集合是哪种类型,所以只能对集合进行读操作,而不能向其中添加数据。
本文参考
《码农翻身》
《疯狂Java讲义》
《一文读懂Java泛型中的通配符?》(趣谈编程)
网友评论