从JDK1.5以后,Java引入了“参数化类型”的概念,允许我们在创建集合时指定集合元素的类型,这就是我们要谈的泛型。
为什么要泛型?
为什么要搞泛型这个东西呢?这还得从Java集合的缺点说起。熟悉Java集合的朋友们都知道,把一个对象加入集合里,集合便会“忘记”该对象的数据类型,所以当再次取出该对象时,其编译类型变成了Object,但其运行时类型没变。因此,取出该对象时需要强制类型转换为目标类型,且很容易产生ClassCastException的异常。如下代码说明:
public class NoTypeList {
public static void main(String[] args) {
List list = new ArrayList();
list.add("list1");
list.add("list2");
list.add(100);//不小心加入了100,但不会引起编译异常
for (int i = 0; i < list.size(); i++) {
String name = (String) list.get(i); //当运行时,强制类型转换遇到100时,报ClassCastException异常
System.out.println("name:" + name);
}
}
}
什么是泛型?
public class GenericList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("list1");
list.add("list2");
list.add(100); //引起编译错误
for (int i = 0; i < list.size(); i++) {
String name = list.get(i); //无需强制转换,因为已经指定集合元素为String类型
System.out.println("name:" + name);
}
}
}
泛型允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定。例如:
//定义接口
public interface List<E>
{
void add(E x);
Iterator<E> iterator();
}
public interface Iterator<E>
{
E next();
boolean hasNext();
}
//定义一个类
class GenericClass<T> {
private T data;
public GenericClass () {
}
public GenericClass (T data) {
this.data = data;
}
public T getData() {
return data;
}
}
public class GenericTest {
public static void main(String[] args) {
GenericClass<String> name = new GenericClass<String>("corn");
GenericClass<Integer> age = new GenericClass<Integer>(712);
System.out.println("name class:" + name.getClass());
System.out.println("age class:" + age.getClass());
System.out.println(name.getClass() == age.getClass()); // true
}
}
如上,我们发现,System.out.println(name.getClass() == age.getClass())输出结果为true。这是因为使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为GenericClass ),当然,在逻辑上我们可以理解成多个不同的泛型类型。
究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
public class GenericTest {
public static void main(String[] args) {
GenericClass<Number> name = new GenericClass<Number>(99);
GenericClass<Integer> age = new GenericClass<Integer>(712);
getData(name);
getData(age);
}
public static void getData(GenericClass<Number> data){
System.out.println("data :" + data.getData());
}
}
可是我们就是需要一个在逻辑上可以用来表示同时是GenericClass <Integer>和GenericClass <Number>的父类的一个引用类型,该怎么办呢?不急,我们可以用类型通配符来解决。
类型通配符一般是使用 ? 代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且GenericClass <?>在逻辑上是GenericClass <Integer>、GenericClass <Number>...等所有GenericClass <具体类型实参>的父类。由此,我们依然可以定义泛型方法,来完成此类需求。
public class GenericTest {
public static void main(String[] args) {
GenericClass <Number> name = new GenericClass <Number>(99);
GenericClass <Integer> age = new GenericClass <Integer>(712);
getData(name);
getData(age);
}
public static void getData(GenericClass <?> data){
System.out.println("data :" + data.getData());
}
}
类型通配符上限
public class GenericTest {
public static void main(String[] args) {
GenericClass<String> name = new GenericClass<String>("corn");
GenericClass<Integer> age = new GenericClass<Integer>(712);
GenericClass<Number> number = new GenericClass<Number>(314);
getData(name);
getData(age);
getData(number);
}
public static voidgetData(GenericClass<? extends Number> data){
System.out.println("data :" + data.getData());
}
}
类型通配符上限通过形如 GenericClass <? extends Number>形式定义,此时其限定了getData()类型实参只能是Number类及其子类 ,即Number是?通配符的上限。
类型通配符下限
与类型通配符上限对应的就是类型通配符下限,形如:<? super Type>,这个通配符表示其要么是Type本身,要么是Type的父类。
泛型使用注意
1.不能创建带有类型变量或类型形参的数组,如:
List<String>[] ls=new List<String>[10];
<T> T[] makeArray(Collection<T> c)
{
reutrn new T[c.size()];
}
2.Java允许创建无上限的通配符泛型数组,如:
<span style="font-size:12px;">List<?>[] ls=new ArrayList<?>[10];</span>
3.泛型方法和类型通配符的区别
大多数时候都可以使用泛型方法来代替类型通配符,如:
public interface Collection<E>
{
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
}
我们可以采用泛型方法来替代上面的方法:
public interface Collection<E>
{
boolean <T> containsAll(Collection<T> c);
boolean <T extends E> addAll(Collection<T> c);
}
上面方法类型形参T,其唯一的效果就是在不同的调用点传入不同的实际类型,对于这种情况,应该使用通配符。
泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的依赖关系,不应该使用泛型方法。