一.为什么会使用泛型
从Java程序设计语音1.0以来,变化最大部分就是泛型.泛型正是我们需要的程序设计手段.使用泛型机制编写的程序代码要比哪些杂乱的使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性.
在Java中增加泛型类之前,泛型程序设计就是用继承实现的.ArrayList类只维护一个Object引用的数组.类似于下面的代码:
public class Arraylist{
private Object [] elementData;
...
public Object get(int i){...}
public void add(Object o){...}
}
这样实现会有两个问题:①单获取一个值时必须进行强制类型转换.②可以向数组列表中添加任何类的对象,编译和运行都不会出错,然而在其他地方,将get的结果强制转换成其它类型,程序就会报错.
泛型提供了一个更好的解决方案,参数类型(Type Pararmeters).ArrayList类有一个类型参数用来指示元素的类型:
ArrayList<String> arrayList = new ArrayList<String>();
这使得代码有更好的可读性,人们一看就知道这个数组列表中包含的是String对象.
二.泛型类
首先定义一个简单的sutdent类
public class Student {
private String major;
public Student(String major) {
this.major = major;
}
public String getMajor() {
return major;
}
public void setMajor(String major) {
this.major = major;
}
}
这样做的一个坏处是Student里面现在只能装入String类型的元素,今后如果我们需要装入Integer等其他类型的元素,还必须要另外重写一个Student,代码得不到复用,使用泛型可以很好的解决这个问题。
public class Student<T> {
private T major;
public T getMajor() {
return major;
}
public void setMajor(T major) {
this.major = major;
}
}
这样我们的Student就可以复用,使用时只要将T换成想要的类型.
三.泛型方法
声明一个泛型方法只要在返回类型前面加上一个类型变量<T>就可以了:
public class Test {
public static <T> boolean compare(T a,T b) {
return a > b;
}
}
public class Pair<T> {
private T value;
public Pair(T value) {
this.value = value;
}
public void setValue(T value) { this.value = value; }
public T getValue() { return value; }
}
对泛型方法的调用如下:
Pair<Integer> a = new Pair<>(1);
Pair<Integer> b = new Pair<>(2);
boolean same = Test.compare(p1, p2);
在泛型接口、泛型类和泛型方法的定义过程中,我们常见的如T、E、K、V等形式的参数常用于表示泛型形参,由于接收来自外部使用时候传入的类型实参。那么对于不同传入的类型实参,生成的相应对象实例的类型是不是一样的呢?
public class GenericTest {
public static void main(String[] args) {
Pair<Number> a = new Pair<>(1);
Pair<Integer> b = new Pair<>(2);
System.out.println("a class:" + a.getClass()); // com.test.Pair
System.out.println("b class:" +b.getClass()); // com.test.Pair
System.out.println(a.getClass() == b.getClass()); // true
}
}
由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Pair),当然,在逻辑上我们可以理解成多个不同的泛型类型。
究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
四.通配符
对于上面第三点代码中的泛型方法,现在做如下调用:
Pair<Number> a = new Pair<>(1);
Pair<Integer> b = new Pair<>(2);
getData(a);
getDate(b);
public static void getData(Pair<Number> data) {
System.out.println("data :" + data.getData());
}
上面的代码在运行getData方法的时候会报错,原因是因为在泛型中Pair<Number> a 和Pair<Integer>,是不能等同,也不能视为子父类关系的.对此我们可以这样思考,如果把它们视为子父类,那么通过getData()方法取出的数据是什么类型呢?
那么这种情况应该怎么解决呢?通配符 <?> 就应运而生了,注意了,此处是类型实参,而不是类型形参!且Box<?>在逻辑上是Pair<Integer>、Pair<Number>...等所有Box<具体类型实参>的父类。
Pair<Number> a = new Pair<>(1);
Pair<Integer> b = new Pair<>(2);
getData(a);
getDate(b);
public static void getData(Pair<?> data) {
System.out.println("data :" + data.getData());
}
上面使用通配符这段代码就不会有问题了.
五.类型变量的限定
在通配符的代码上我们做如下修改:
Pair<Number> a = new Pair<>(1);
Pair<Integer> b = new Pair<>(2);
Pair<String> c = new Pair<>(2);
getData(a);
getDate(b);
getDate(c); //1
public static void getData(Pair<?> extends Number data) {
System.out.println("data :" + data.getData());
}
上面的代码在 1 处会报错,原因是getData方法限定了泛型类型只能为Number类的子类,这就是类型通配符上限.用Pair<?> extends Number表示.