1. 基本概念:
(1)什么是泛型?
泛型,即“参数化类型”。即将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用或调用时传入具体的类型(类型实参)。
(2)为什么要使用泛型?
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。
也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
2. 特性:
参考如下代码:
public class GenericTest {
public static void main(String[] args) {
List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
Class class1 = list1.getClass();
Class class2 = list2.getClass();
System.out.println(class1.equals(class2)); // 返回true
}
}
在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。
因此,泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
3. 使用:
(1)泛型类:
public class Generic<T> {
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey() {
return key;
}
public void setKey(T key) {
this.key = key;
}
}
public static void main(String[] args) {
Generic gInteger = new Generic(123);
Generic gString = new Generic("abc");
System.out.println(gInteger.getKey()); // 123
System.out.println(gString.getKey()); // "abc"
}
(2)泛型接口:
public interface Generator<T> {
T getName();
}
public class IntegerGenerator implements Generator {
@Override
public Object getName() {
return 123; //返回Integer类型
}
}
public class StringGenerator implements Generator {
@Override
public Object getName() {
return "apple"; //返回String类型
}
}
StringGenerator sGenerator1 = new StringGenerator();
IntegerGenerator iGenerator1 = new IntegerGenerator();
System.out.println(sGenerator1.getName()); // "apple"
System.out.println(iGenerator1.getName()); // 123
(3)泛型方法:
public class GenericMethod {
static class Fruit{
@Override
public String toString() {
return "fruit";
}
}
static class Apple extends Fruit{
@Override
public String toString() {
return "apple";
}
}
static class Person{
@Override
public String toString() {
return "Person";
}
}
static class GenerateMethodTest<T>{
public void show_1(T t){
System.out.println(t.toString());
}
//在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
//由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
public <E> void show_3(E t){
System.out.println(t.toString());
}
//在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
public <T> void show_2(T t){
System.out.println(t.toString());
}
}
public static void main(String[] args) {
Apple apple = new Apple();
Person person = new Person();
GenerateMethodTest<Fruit> generateTest = new GenerateMethodTest<Fruit>();
generateTest.show_1(apple);//apple是Fruit的子类,所以这里可以
//generateTest.show_1(person);//编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
//使用这两个方法都可以成功
generateTest.show_2(apple);
generateTest.show_2(person);
//使用这两个方法也都可以成功
generateTest.show_3(apple);
generateTest.show_3(person);
}
}
(4)泛型通配符:
Integer 是Number的子类,在使用Generic<Number>作为形参的方法中,能否传入Generic<Integer>的实参?逻辑上能否认为Generic<Number>和Generic<Ingeter>是否是父子关系的泛型类型呢?
public class GenericWildcard {
static void showValue(Generic<Number> number){
System.out.println(number.getKey());
}
public static void main(String[] args) {
Generic<Integer> iGeneric = new Generic<Integer>(10);
Generic<Number> nGeneric = new Generic<Number>(21);
//showValue(iGeneric);//编译报错:Generic<java.lang.Integer> cannot be applied to Generic<java.lang.Number>
showValue(nGeneric);
}
}
上述示例可以看出,Generic<Integer>不能被看做是Generic<Number>的子类,即同一泛型可以对应多个版本,不同版本的泛型实例是不兼容的。那么这时就需要引入通配符的概念来解决这一问题,如下:
public class GenericWildcard1 {
static void showValue1(Generic<?> obj){
System.out.println(obj.getKey());
}
public static void main(String[] args) {
Generic iGeneric1 = new Generic(10);
Generic nGeneric1 = new Generic(21);
showValue1(iGeneric1);
showValue1(nGeneric1);
}
}
(5)泛型的上下边界:
public class GenericWildcard {
static void showValue2(Generic<? extends Number> obj){
System.out.println(obj.getKey());
}
public static void main(String[] args) {
Generic<Integer> gInteger = new Generic(100);
Generic<Double> gDouble = new Generic(10.0);
Generic<Float> gFloat = new Generic(1.1f);
Generic<String> gString = new Generic("abc");
showValue2(gInteger);
showValue2(gDouble);
showValue2(gFloat);
showValue2(gString); //这一行代码编译器会提示错误,因为String类型并不是Number类型的子类
}
}
总结:泛型的上下边界添加,必须与泛型的声明在一起 。
(6)泛型数组:
java中不能创建一个确切的泛型类型的数组,结合如下示例:
List<String>[] list1 = new ArrayList<String>[10]; // 编译不通过
Object[] obj = (Object[]) list1;
List<Integer> list2 = new ArrayList<Integer>();
list2.add(new Integer(10));
obj[1] = list2;
Integer str1 = list1[0].get(1); // Run-time error: ClassCastException.
这时由于JVM泛型的擦除机制,在运行时JVM是不知道泛型信息的,所以可以给obj[1]赋上一个ArrayList而不会出现异常,但在取数据时未做类型转换,所以就会出现Run-time error: ClassCastException。如果可以进行泛型数组的声明,上面说的这种情况在编译期将不会出现任何的警告和错误,如下:
List<?>[] list11 = new ArrayList<?>[10];
Object[] obj1 = (Object[]) list11;
List<Integer> list22 = new ArrayList<Integer>();
list22.add(new Integer(10));
obj1[1] = list22;
Integer integer = (Integer)list11[1].get(0);