0%

理解Java泛型

前言

泛型,广泛使用于集合框架中。在Java面试时,面试官经常会提到泛型。本文对泛型进行介绍,并总结了Java泛型的常见面试题。

什么是泛型 – 问题1

泛型,即参数化的类型,参数化的类型是指将原来需要具体化的类型参数化,类似于方法中的变量参数,此时,变量参数的类型也定义成参数的形式。
例如,定义一个方法打印整数类型的数组:

1
public static void print(int[] array)

若我们需要定义一个方法打印任意类型的数组,类型由调用者自己传入:

1
public static <E> void pring(E[] array) //E: 参数化的类型, 由调用者决定

例如,我们常使用Java容器就可以装各种类型的对象,这些类型可以是Java定义的类型,也可以是用户自定义的类型。下图为ArrayList容器定义源码:

为什么要使用泛型 – 问题2

在Java1.5之前,通过对类型Object的引用实现类型的任意化,并在使用前进行显式的强制类型转换,强制类型转换在可以预知的情况下进行,很不方便且容易在运行时出错,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型,避免了在运行时出现ClassCastException。例如,我们现在向ArrayList中添加一些String类型的数据,在遍历的时候必须对取出来的元素进行转型,很容易出错。为了解决这个问题,我们可以为String类型写一个ArrayList,似乎就可以不进行类型转换,可是让我们试想一下,ArrayList是一个容器,可以添加各种类型的对象,我们不可能为每一种类型都写上一个ArrayList。因此,我们需要一个模板,这个模板可以支持添加任意类型的数据,就像我们英语考试之前希望背上一个万能模板,任意的主题都可以往里套,而不用为每一个类创建新的ArrayList。泛型就是这样一个模板,ArrayList<T>,T可以是任意类型。

注意这里T不能转型为父类,如ArrayList<Integer>可以转型为List<Integer>,而不能转型为ArrayList<Number>,Number为Integer的父类。

类型擦除是什么 – 问题3

泛型是通过类型擦除来实现的,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。例如,在代码中定义List<Object>和List<String>等类型,在编译后都会变成List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。也就是说,再编译过程中,正确检验泛型结果后,会将泛型相关信息擦除,泛型信息不会进入到运行时阶段。

1
2
3
4
5
6
7
public static void main(String[] args) {
List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
System.out.println(list1.getClass());//class java.util.ArrayList
System.out.println(list2.getClass());//class java.util.ArrayList
System.out.println(list1.getClass().equals(list2.getClass()));//true
}

泛型通配符(什么是泛型中的限定通配符和非限定通配符 – 问题4)

在通常情况下,我们使用T、E、K、V、?来表示通配符。

T:Type,具体的Java类型

E:代表Element

K, V:代表Java键值中的Key,Value

?:表示不确定的Java类型

限定通配符包括上界通配符、下界通配符,非限定通配符包括无界通配符。

上界通配符

? extends T表示上界通配符,指定了T为上界,?可以表示T的任意子类。

下界通配符

? super T表示下界通配符,指定了T为下界,?可以表示T的任意父类。

无界通配符

无界通配符使用单独的?表示,当不确定类型时使用。例如List<?>,那它与List有什么区别呢? List<?>表示某种特定类型的List,但不知道具体的类型,无法添加对象。List所有的元素的类型是Object,可以添加任何类型的对象。