1. 概念
1. 什么是泛型
泛型的本质就是参数化类型,我们可以在不创建新的类型的情况下,根据泛型指定的类型来限制传入的类型实参。
在定义泛型时,指定一个类型作为形参,我们称之为类型形参,然后在使用时传入具体的类型,称之为类型实参。
类型实参只能是引用类型,不能是基本数据类型;定义泛型时,可以声明任意数量的类型形参;泛型只能存在于编译阶段,在编译过程中,正确检查泛型结果后,泛型相关的信息都会被擦除掉,并且会在对象进入和离开方法的边界处添加类型检查和类型转换的方法,也就是说,泛型不会存在于运行时阶段。
//测试
class TestGeneric {
public static void main(String[] args) throws Exception {
List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();
Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();
System.out.println("stringArrayList运行时的类型: " + classStringArrayList.toString());
System.out.println("integerArrayList运行时的类型: " + classIntegerArrayList.toString());
if(classStringArrayList.equals(classIntegerArrayList)){
System.out.println("类型相同");
}
}
}
//结果
stringArrayList运行时的类型: class java.util.ArrayList
integerArrayList运行时的类型: class java.util.ArrayList
类型相同
2. 泛型的使用
泛型的使用有 5 种方式:
-
泛型类:当泛型类型用于类的定义时,该类就称为泛型类,最经典的泛型类就是各种容器类,例如 HashMap 、ArrayList 等等;类中定义的泛型可以在整个类里面使用,除了静态部分,因为泛型是在类实例化时确定的,而类的静态部分只与类相关,与实例无关,如果静态方法需要使用到泛型,可以将该方法声明为静态泛型方法;另外,类里面的方法和类可以再次声明同名泛型,且该泛型会覆盖掉父类的同名泛型
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型 //在实例化泛型类时,必须指定T的具体类型 class Generic<T> { //在类中声明的泛型整个类里面都可以用,除了静态部分,因为泛型是实例化时声明的。静态区域的代码在编译时就已经确定,只与类相关 class A<E> { T t; } //类里面的方法或类中再次声明同名泛型是允许的,并且该泛型会覆盖掉父类的同名泛型T class B<T> { T t; } //静态内部类也可以使用泛型,实例化时赋予泛型实际类型 static class C<T> { T t; } public static void main(String[] args) { //报错,不能使用T泛型,因为泛型T属于实例不属于类 T t = null; } public static <T> void staticGeneric() { }//声明静态泛型方法 //key这个成员变量的类型为T,T的类型由外部指定,如果外部不指定 T 的类型,则 key 的类型可以为任意类型 private T key; //泛型构造方法形参key的类型也为T,T的类型由外部指定,如果外部不指定 T 的类型,则 key 的类型可以为任意类型 public Generic(T key) { this.key = key; } public <T> Generic() { } //声明同名泛型覆盖类的泛型 //泛型方法getKey的返回值类型为T,T的类型由外部指定,如果外部不指定 T 的类型,则返回值的类型可以为任意类型 public T getKey() { return key; } }
-
泛型接口:和泛型类的定义很相像,但是需要注意的是,如果一个类实现了某泛型接口,而此时没有给该泛型接口传入类型实参,那么该类也要同时声明该泛型;
public interface Generator<T> { public T next(); }
class FruitGenerator<T> implements Generator<T>{ @Override public T next() { return null; } }
当然,如果此时有给该泛型接口传入类型实参,则该实现类不需要声明该泛型;
//这个时候 FruitGenerator 不需要加泛型了,因为接口 Generator 传入了一个具体泛型 String,而非T public class FruitGenerator implements Generator<String> { private String[] fruits = new String[]{"Apple", "Banana", "Pear"}; @Override public String next() { Random rand = new Random(); return fruits[rand.nextInt(3)]; } }
另外,我们可以通过给泛型接口传入不同的类型实参,进而来构造出无数种不同类型的该接口
-
泛型方法:其定义和上面同理,当我们调用方法的同时,需要给该方法传入类型实参;需要注意的是,静态变量不能被声明为静态泛型变量;还有就是,只有声明了
<T>
的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法,如 public T next(),这并不是泛型方法public <T> T genericMethod(Class<T> tClass)throws InstantiationException , IllegalAccessException{ T instance = tClass.newInstance(); return instance; }
静态方法无法访问类上定义的泛型,因为在 Java 中泛型只是一个占位符,必须在传递类型后才能使用。就泛型类而言,类实例化时才能传递真正的类型参数,由于静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数时,静态方法就已经加载完成。显然,静态方法不能使用/访问泛型类中的泛型;所以当静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上
public static <T> void show(T t){ ··· }
-
泛型可变参数:普通可变参数只能适配一种类型,如 String 类型,而泛型的可变参数可以匹配所有类型的参数:
//普通可变参数只能适配一种类型 public void print(String ... args) { for(String t : args){ System.out.println(t); } }
//泛型的可变参数可以匹配所有类型的参数。有点无敌 public <T> void printMsg( T... args){ //泛型方法,用了自己定义的泛型 for(T t : args){ System.out.println(t); } }
-
泛型数组:当一个数组的类型不确定的时候,我们可以用通配符创建一个泛型数组
List<?>[] ls = new ArrayList<?>[10]; //泛型数组的创建 List<String>[] ls = new ArrayList<String>[10]; // 这个试了一下好像编译不通过 //也可以 List<String>[] ls = new ArrayList[10];
对于用通配符创建数组的方式,最后取出数据是要做显式的类型转换
class TestGeneric { public static void main(String[] args) throws Exception { List<?>[] lsa = new List<?>[10]; // 用泛型创建数组 Object o = lsa; Object[] oa = (Object[]) o; // 把统配符类型转成 Object,这样可以接受任意类型 List<Integer> li = new ArrayList<Integer>(); //声明 Integer 类型 ArrayList<String> li2 = new ArrayList<>(); li.add(new Integer(3)); li2.add("hello"); oa[1] = li; // Correct.把 Integer 转换为 Object 类型 oa[2] = li2;// Correct.把 String 转换为 Object 类型 Integer i = (Integer) lsa[1].get(0); String str = (String) lsa[2].get(0); System.out.println(i + str); } } //结果 3hello
//一个错误例子 List<String>[] lsa = new List[10]; //这里不能使用 new List<String>[10],否则会编译报错 Object o = lsa; Object[] oa = (Object[]) o; List<Integer> li = new ArrayList<Integer>(); li.add(new Integer(3)); oa[1] = li; // Correct.把 Integer 转换为 Object 类型 String s = lsa[1].get(0); // Run-time error: ClassCastException. Integer 不能转成 String String s = (String) lsa[1].get(0); //同样是类型转换异常
3. 泛型通配符
泛型通配符用 ?
表示,他表示可以接收任意类型的类型实参。
通配符又分 限定通配符和非限定通配符,只有一个 ?
的通配符称为非限定通配符;而限定通配符又分有上下边界两种类型,类型上限使用 extends 关键字来实现,类型下限使用 super 关键子来实现
//只能传入number的子类或者number
public void showKeyValue1(Generic<? extends Number> obj){
System.out.println(obj);
}
//只能传入Integer的父类或者Integer
public void showKeyValue2(Generic<? super Integer> obj){
System.out.println(obj);
}
4. 类型擦除
泛型是 JDK 1.5 的时候才引进的概念,但很显然,泛型的代码能够很好地与之前的代码相兼容,这是因为,泛型信息只存在于编译阶段,而在进入 JVM 之前,泛型的相关信息都会被擦除掉,专业术语叫做类型擦除;
在泛型类型被擦除的时候,如果泛型的类型参数没有指定类型上限的话,则该类型参数会被转换为 Object 类型,如果有指定类型上限的话,则该类型参数会被转换为类型上限。