Java泛型浅谈
Java泛型浅谈
为什么需要泛型
关于Java中引入泛型的原因,我认为Java核心技术第十版中的8.1阐述的非常通俗易懂。下面我以书中ArrayList的例子简单说明一下(ps.可以在微信读书上去看这本书哦,免费!!!)
我们知道,ArrayList是一个可以聚集任何类型的数组列表。但是不知道大家有没有注意过ArrayList是如何实现任意类型聚集的,查看ArrayList源码,答案显而易见就是通过泛型进行聚集的。

实际上在Java SE 5.0增加泛型机制之前,就已经存在一个ArrayList类,彼时的ArrayList是通过继承关系实现任意类型聚集的。

通过这样的方式获取元素时,必须进行强制类型转换(String str = (String)arraylist.get(i))。添加元素时,没有错误检查,可以向列表中添加任意类型的元素(arraylist.add(new Integer(1)))。对于这样的调用,单看这个语句,编译与运行都不会有问题。但当在获取这个数组列表元素并进行强制类型转换时,就会得到一个类型转换异常。
而使用泛型机制实现ArrayList就不会出现上述的问题。
//JDK1.8中的ArrayList实现
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
transient Object[] elementData;
...
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
...
}
在获取元素时,我们不需要进行强制类型转换,编译器会根据定义ArrayList的变量时给出的数据类型自动判断返回值类型。添加元素时,编译器会进行错误检查,以防加入错误的类型元素。其他方法道理大致同上。
所以,泛型机制给程序带来了更高的可读性与安全性。
泛型的简单使用
泛型类
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;
}
}
-
在Java库中,使用变量E表示集合的元素类型,K和V分别表示表的关键字与值的类型,T(需要时还可以用临近的字母U和S)表示任意具体的一个类型(类比
ArrayList<E>,这个写法和ArrayList<T>实际上是一个意思,表示需要我们在实例化时传入一个确定的类型参数),?为通配符,表示不确定的类型(在泛型的通配符中会讲解?的用法)。实际上使用A-Z任何字母表示泛型都可以,但是一般遵守上述规则。 -
Generic引入一个类型变量T,用
<>括起来,并放在类名的后面。当我们这样实例化Generic时:
Generic<String> generic = new Generic<>()、Generic<String> generic = new Generic<String>()(Java SE 7之后,构造函数可以省略泛型)、Generic<?> generic = new Generic<>("string"),实际得到如下所示的类与方法public class Generic<String> { private String key; public Generic(String key) { this.key = key; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } } -
泛型类可以有多个类型变量。
public class Generic<T,E> { private T key1; private E key2; public Generic(T key1, E key2) { this.key1 = key1; this.key2 = key2; } public T getKey1() { return key1; } public void setKey1(T key1) { this.key1 = key1; } public E getKey2() { return key2; } public void setKey2(E key2) { this.key2 = key2; } }初始化时,可以为T和E传入所需的不同或相同的类型参数,其方法参数及返回值也会被自动翻译为对应类型。
泛型方法
public class GenericTest {
public static void main(String[] args) {
testGeneric("str");
testGeneric(123);
testGeneric(1.0);
testGeneric(false);
}
public static <T> void testGeneric(T argement){
System.out.println(argement.getClass());
}
}
- 泛型方法可以定义在普通类中也可以定义在泛型类中
- 泛型方法的定义中,类型变量放在修饰符(这里是public static)的后面,返回参数的前面。如
testGeneric(T argement)。
泛型接口
这里就不自己举例了,最典型的一个泛型接口之一就是Comparable接口。
public interface Comparable<T> {
public int compareTo(T o);
}
class User implements Comparable<User>{
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(User o) {
if( name.equals(o.name))
return age-o.age;
return name.compareTo(o.name);
}
}
类型擦除
概念
编译器在编译泛型的类型信息时,会将类型变量清除,并替换为限定类型。如果未进行限定(即使用T或?作为类型变量),则使用Object进行替换。若限定了类型(即使用了extends或者super限定泛型),则使用限定范围的最小类进行替换。
例如上文Generic的原始类型应该为:
public class Generic {
private Object key;
public Generic(Object key) {
this.key = key;
}
public Object getKey() {
return key;
}
public void setKey(Object key) {
this.key = key;
}
}
限定类型的类型擦除如下(图源Java核心技术):

举例说明
举两个例子说一下类型擦除。
Demo1
@Test
public void testClear() {
ArrayList<Integer> integers = new ArrayList<>();
ArrayList<String> strings = new ArrayList<>();
System.out.println(integers.getClass());
System.out.println(strings.getClass());
System.out.println(integers.getClass() == strings.getClass());
}
输出结果:

Demo2
@Test
public void testT() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
List<Integer> list = new ArrayList<>();
list.add(12);
Class<? extends List> clazz = list.getClass();
Method add = clazz.getDeclaredMethod("add",Object.class);
//通过反射可以添加成功
add.invoke(list, "kl");
System.out.println(list);
}
运行这个测试方法,会输出[12,"kl"]。若是编译后泛型类型信息依然存在,即使使用反射,这里也不应该成功。
类型变量的限定和通配符的限定
引入
下面以一个例子说明引入类型变量的限定的概念
public class WildcardTest {
public static void main(String[] args) {
ArrayList<Employee> employees = new ArrayList<>();
ArrayList<Manger> mangers = new ArrayList<>();
Employee employee1 = new Employee("张三", 18, false);
Employee employee2 = new Employee("李四", 18, true);
Employee employee3 = new Employee("王五", 18, true);
Manger manager1 = new Manger("小王", 22, false, 0.1);
Manger manager2 = new Manger("小李", 22, false, 0.1);
Manger manager3 = new Manger("小掌", 22, false, 0.1);
employees.add(employee1);
employees.add(employee2);
employees.add(employee3);
mangers.add(manager1);
mangers.add(manager2);
mangers.add(manager3);
mangers.get(1);
//使用addAll将mangers的全部元素加入employees
employees.addAll(mangers);
System.out.println(employees);
//编译不通过,因为Employee不是Manger的子类
//mangers.addAll(employee1);
}
}
class Employee {
private String name;
private int age;
private boolean sex;
public Employee(String name, int age, boolean sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
class Manger extends Employee{
private double Commission;
public Manger(String name, int age, boolean sex, double commission) {
super(name, age, sex);
Commission = commission;
}
public Manger(String name, int age, boolean sex) {
super(name, age, sex);
}
@Override
public String toString() {
return "Manger{" +
"Commission=" + Commission +
'}';
}
}
-
在
mangers.addAll(employee1),编译器会显示如下所示的错误。![image-20210308152420622]()
jdk1.8中ArrayList.addAll()的源码如下:
public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }addAll()的参数c,限定类型为Collection<? extends E>,表示Collection的类型应为E的子类型或E本身,其中E为声明数组列表时所指定的类型。
类型变量限定的“关键字”
类型变量限定的“关键字”有两个:extends和super
extends
在类型参数中使用T extends E,表示T需要是E的子类或其自身。
super(超类限定)
在类型参数中使用? super E,表示通配符?表示的类型需要是E的父类或其自身。需要注意这里为什么不能使用T super E,因为类型参数不支持超类限定。
多重限定
可以使用&表示多重限定。语法为T extends E&K...,表示T需要是E和K的子类。需要注意,通配符不支持多重限定,所以不能写作T extends E&K...
T和?的区别
在前文中我们提到,?为通配符,表示不确定的类型。那么T和?区别是什么呢?我的认为区别如下。(没有依据,如有错误,请大神指教。)
T表示任意具体的一个类型,在泛型类实例化时,泛型方法被调用时,泛型接口实现类实例化时,此时的T实际上已经被具体类型所取代。在当前对象下(就是当前实例化对象,或调用泛型方法的当前对象,或当前实现泛型接口实现类的实例化对象),T只能表示一种类型了。例如ArrayList<E>,在声明了ArrayList<String> strings = new ArrayList<>()后,strings表示的ArrayList中,E只能为String。而对于通配符?,参考前文所举的Manger和Employee例子中的addAll(),在employees.addAll()中,除了mangers,我还可以传入任意类型为Employee的子类的ArrayList。例如:
public class WildcardTest {
public static void main(String[] args) {
ArrayList<Employee> employees = new ArrayList<>();
ArrayList<Manger> mangers = new ArrayList<>();
ArrayList<Coder> coders = new ArrayList<>();
Employee employee1 = new Employee("张三", 18, false);
employees.add(employee1);
Coder coder = ...;
coders.add(coder);
//使用addAll将mangers的全部元素加入employees
employees.addAll(mangers);
employees.addAll(coders);
}
}
class Coder extends Employee{
...
}
employees.addAll(coders)的操作会成功,因为Coder是Employee的子类。根据addAll()的源码,在employees下,当前?对应的类型就为Coder和Manger,需要的时候可以是更多类型,所以说?表示不确定的类型。
参考链接
- Java核心技术第十版第八章
- Java中的通配符T,E,K,V,?
- Java类型擦除以及类型擦除带来的问题
- JavaGuide Java基础 1.2.7


浙公网安备 33010602011771号