介绍泛型之前,先来张图,从宏观上认识一下我们今天要介绍的主角:泛型。这张导图是笔者自己对泛型的一个整体印象,下面我们从why、what、how三个角度来分析一下,我们说的泛型。
图一 泛型概括
一、Why
Java集合有个缺点,把一个对象“丢进”集合里之后,集合就会“忘记”这个对象的数据类型,当再次取出对象时,该对象的编译类型就变成了Object类型。集合最元素类型没有任何限制,这样可能引发一些问题:例如想创建一个只能保存Dog对象的集合,但程序也可以轻易地将Cat对象“丢”进去,所以可能引发异常;由于把对象"丢进"集合时,集合丢失了对象的状态信息,集合只知道它盛装的是Object,因此取出来集合元素后通常还需要进行强制类型转换。这种强制转换既增加了编程的复杂度,也可能引发ClassCastException异常。
从Java5以后,Java引入了“参数化类型”的概念,允许程序在创建集合时指定集合元素的类型(还有定义类的时候的成员变量,在类型不确定时也可以使用泛型表示),如我们平时定义list时:List<String>,这表明List只能保存字符串类型的对象。Java的参数化类型被称为泛型(Generic)。引入泛型,便可以是List对象记住它所有集合元素都是什么类型,很大程度上提高了程序的健壮性。
二、What
所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参)。在Java7之前,如果使用带泛型的接口、类定义变量,那么调用构造器创建对象时构造器后面也必须带泛型:
三、How
1.定义泛型接口、泛型类和泛型方法
(1)定义泛型接口
//定义接口时指定了一个类型形参,该形参名为E
public interface List<E> extends Collection<E> {
//在该接口里,E可作为类型使用
//下面的方法里使用E作为参数类型
void add(E X);
Iterator<E> iterator();
}
//定义该接口时指定了两个类型形参,K、V
public interface Map<K,V>
{
//在该接口里K、V完全可以作为类型使用
Set<K> keySet();
V put(K key, V value)
}
package cn.gome.d_generic;
import java.util.List;
import org.omg.CORBA.portable.ApplicationException;
public class Apple<T>{
//使用T类型形参定义实例变量
private T info;
//使用T类型参数定义构造器
public Apple(T info){
this.info = info;
}
public T getInfo() {
return info;
}
public void setInfo(T info) {
this.info = info;
}
public static void main(String[] args) {
//传给T的形参类型是String,所以构造器参数只能是String
Apple<String> apple = new Apple<>("苹果");
System.out.println(apple.getInfo());
//传给T的形参类型是Double,所以构造器参数只能是Double
Apple<Double> apple2 = new Apple<>(5.26);
System.out.println(apple2.getInfo());
}
}
//继承Apple类时,Apple类不能跟类型形参,编译错误
public class AppleChild1 extends Apple<T>{}
如果想从Apple类派生一个子类,则可以改为如下代码
package cn.gome.d_generic;
public class AppleChild extends Apple<String>{
public AppleChild(String info) {
super(info);
}
public static void main(String[] args) {
AppleChild appleChild = new AppleChild("嘟嘟");
System.out.println(appleChild.getInfo());
}
}
Java5提供了对泛型方法的支持,在定义类、接口时没有使用类型形参,但定义方法时想自己定义类型形参,也是可以的。
假如我们需要实现这样一个方法:该方法负责将一个Object数组的所有元素添加到一个Collection集合中,考虑用如下代码类实现该方法:
package cn.gome.d_generic;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class GenericTest1 {
public static void fromArrayToCollection(Object[] a,Collection<Object> c){
for (Object aObject : a) {
c.add(aObject);
}
}
public static void main(String[] args) {
String[] strArr = {"a","b"};
List<String> strList = new ArrayList<>();
//Collection<String>对象不能当成Collection<Object>使用,下面代码出现编译错误
GenericTest1.fromArrayToCollection(strArr, strList);
}
}
泛型方法的语法格式如下:
修饰符 <T,S> 返回值类型 方法名(行参列表){
//方法体...
}
package cn.gome.d_generic;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class GenericTest2 {
public static <T> void fromArrayToCollection(T[] a,Collection<T> c){
for (T t : a) {
c.add(t);
}
}
public static void main(String[] args) {
Object[] object = new Object[100];
Collection<Object> collection = new ArrayList<>();
//T代表Object类型
fromArrayToCollection(object,collection);
String[] strings = new String[100];
Collection<String> cStrings = new ArrayList<>();
//T代表String类型
fromArrayToCollection(strings, cStrings);
Integer[] integers = new Integer[100];
Collection<Number> cNumbers = new ArrayList<>();
//T代表Number类型
fromArrayToCollection(integers, cNumbers);
}
}
2.类型形参
Java泛型可以在定义类型形参时设定上限,用于表示传给改类型形参的实际类型要么是该上限类型,要么是该上限类型的子类。
package cn.gome.d_generic;
public class Banana<T extends Number> {
T col;
public static void main(String[] args) {
Banana<Integer> bi = new Banana<>();
Banana<Double> ad = new Banana<>();
//String不是Number的子类型,所以引起编译错误
//Banana<String> bs = new Banana<>();
}
}
3.类型通配符
为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个(?),将一个问号作为类型实参传给List集合,写作List<?>,也就是元素类型是未知类型的List。这个(?)被称为通配符,它的元素可以匹配任何类型。下面通过一个简单的例子来解释一下:
(1)抽象的Shape类
package cn.gome.d_generic;
//形状抽象类
public abstract class Shape {
public abstract void draw(Canvas c);
}
Circle类:
package cn.gome.d_generic;
public class Circle extends Shape {
@Override
public void draw(Canvas c) {
System.out.println("在画布" + c + "上换一个圆");
}
}
package cn.gome.d_generic;
public class Rectangle extends Shape {
@Override
public void draw(Canvas c) {
System.out.println("在画布" + c + "上画一个三角形");
}
}
package cn.gome.d_generic;
import java.util.ArrayList;
import java.util.List;
//画布类
public class Canvas {
public void drawAll(List<Shape> shapes){
for (Shape shape : shapes) {
shape.draw(this);
}
}
public static void main(String[] args) {
List<Circle> circles = new ArrayList<>();
Canvas canvas = new Canvas();
//不能把List<Circle>当成List<Shape>使用,所以下面代码引起编译错误
//canvas.drawAll(shapes);
}
}
package cn.gome.d_generic;
import java.util.ArrayList;
import java.util.List;
//画布类
public class Canvas {
public void drawAll(List<?> shapes){
for (Object o: shapes) {
Shape shape = (Shape)o;
shape.draw(this);
}
}
public static void main(String[] args) {
List<Circle> circles = new ArrayList<>();
Canvas canvas = new Canvas();
canvas.drawAll(circles);
}
}
(1)设定通配符上限:List<? extends T> 表示List接收的形式参数,只能是类型T的或者类型T的子类。
(2)设定通配符下限:List<? super T> 表示List接收的形式参数,只能是T本身,或者是T的父类。
使用上限通配符,改写上面的Canvas类如下:
package cn.gome.d_generic;
import java.util.ArrayList;
import java.util.List;
//画布类
public class Canvas {
public void drawAll(List<? extends Shape> shapes){
for (Shape o: shapes) {
o.draw(this);
}
}
public static void main(String[] args) {
//画圆
List<Circle> circles = new ArrayList<>();
circles.add(new Circle());
circles.add(new Circle());
//画三角形
List<Rectangle> rectangles = new ArrayList<>();
rectangles.add(new Rectangle());
rectangles.add(new Rectangle());
rectangles.add(new Rectangle());
Canvas canvas = new Canvas();
canvas.drawAll(circles);
canvas.drawAll(rectangles);
}
}