一、为什么要使用泛型,泛型的产生由来?
Java集合有个缺点——把一个对象“丢进”集合里之后,集合就会“忘记”这个对象的数据类型,当再次取出该对象的时候,该对象的编译类型就变成了Object类型(其运行时类型没变)。Java集合之所以被设计成这样,是因为集合的设计者不知道我们会用集合来保存什么类型的对象,所以就设计成能保存任何类型的对象,只要求具有很好的同用性。但是这样会带来两个问题:
1.集合对元素类型没有任何限制,这样可能会引发一些问题。
2.把对象丢进集合的时候,集合丢失了对象的状态信息,集合只知道它盛装的是Object,因此取出集合元素通常还需要进行强制类型转换。这种强制转换既增加了编程的复杂度,也可能引发ClassCastException异常。
所以在JDK1.5之前只能把元素类型设计为Object, JDK1.5之后使用泛型来解决。 因为这个时候除了元素的类型不确定, 其他的部分是确定的, 例如关于这个元素如何保存, 如何管理等是确定的, 因此此时把元素的类型设计成一个参数, 这个类型参数叫做泛型。 Collection<E>, List<E>, ArrayList<E> 这个<E>就是类型参数, 即泛型。使用泛型可以解决上面两个问题。
1、解决元素存储的安全性问题。2、解决获取数据元素时,需要类型强制转换的问题。
总结:Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮。
二、什么是泛型?
所谓泛型, 就是允许在定义类、 接口、方法时使用类型形参,这个类型形参(或叫泛型)将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参)。通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。 JDK1.5改写了集合框架中的全部接口和类, 为这些接口、 类增加了泛型支持,从而可以在声明集合变量、 创建集合对象时传入类型实参,这就是在前面程序中看到的List<String>和ArrayList<String>两种类型。
//创建一个只能存放String类型的List集合
List<String> strList = new ArrayList<>();
三、怎么使用泛型?
这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
(一)自定义泛型类、接口、方法:
下面自定义一个Apple类,这个Apple类就可以包含一个泛型声明。
//定义Apple类时使用了泛型声明
public class Apple<T>
{
//使用T类型定义实例变量
private T info;
public Apple(){};
//下面方法中使用T类型来定义构造器
public Apple(T info)
{
this.info = info;
}
public void setInfo(T info)
{
this.info = info;
}
public T getInfo(){
return this.info;
}
public static void main(String[] args){
//由于传给T形参的是String,所以构造器参数只能是String
Apple<String> a1 = new Apple<>("苹果");
System.out.println(a1.getInfo());
//由于传给T形参的是Double,所以构造器参数只能是Double或double
Apple<Double> a2 = new Apple<>(5.67);
System.out.println(a2.getInfo());
}
}
(二)类型通配符
为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?>(意思是元素类型未知的List)。这个问号(?)被称为通配符,它的元素类型可以匹配任何类型。
public class Test {
public static void main(String[] args) {
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();
name.add("abc");
age.add(25);
number.add(123);
getData(name);
getData(age);
getData(number);
}
public static void getData(List<?> data) {
System.out.println("data :" + data.get(0));
}
}
1、设定类型通配符的上限
当直接使用List<?>这种形式时,即表明这个List集合可以是任何泛型List的父类。单还有一种特殊的清形,程序不希望这个List<?>是任何泛型List的父类,只希望它代表某一类泛型List的父类,则可以使用类型通配符的上限这个语法,此处的“上限”指的是“父类”。
//定义类型通配符的上限
//? extends A:
//G<? extends A> 可以作为G<A>和G<B>的父类,其中B是A的子类
List<? extends Number>
使用思路:先定义Number的子类One、Two,然后就可以用<? extends Number>代表List <One>、List<Two>了。
特点:这种指定通配符上限的集合,只能从集合中取元素(取出的元素总是上限的类型),不能向集合中添加元素(因为编译器没法确定集合元素实际是哪种子类型)。
2、设定通配符的下限
为了支持类型型变。比如One是Number的子类,当程序需要一个A<? super Number>变量时,程序可以将A<One>、A<Object> 赋值给A<? super Number>类型的变量,这种型变方式被称为逆变。
//定义类型通配符的下限
//? super A:
//G<? super A> 可以作为G<A>和G<B>的父类,其中B是A的父类
List<? super Number>
特点:能向其中添加元素(因为实际赋值的集合元素总是逆变声明的父类),从集合中取元素时只能被当成Object类型处理(编译器无法确定取出的到底是哪个父类的对象)。