贫瘠之地

华北无浪漫,死海扬起帆
多少个夜晚,独自望着天

0%

Java 泛型

介绍

什么是泛型

泛型,即参数化类型

一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在调用时传入具体的类型(类型实参)

为什么要引入泛型

使用泛型进行声明可以让编译器会在编译阶段就能够帮我们发现错误类型对象

1
2
3
4
5
6
final List<Object> list = new ArrayList<>();

list.add("string");

// 类型转换异常 ClassCastException
final Student student = (Student)list.get(0);

泛型擦除

泛型是一种语法糖,在编译阶段就会进行泛型擦除,泛型信息不会进入到运行时阶段(事实上有些泛型信息会被保留,见下文的 API 介绍)

1
2
3
4
5
6
7
8
List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();

Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();

// equals = true 说明最后的 Class 信息不包含泛型信息
final boolean equals = Objects.equals(classStringArrayList, classIntegerArrayList);

类型

泛型的使用分为三种类型:泛型类、泛型接口、泛型方法

泛型类

泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口

最典型的就是各种集合类,如:List、Set、Map

简单的泛型类

泛型类

1
2
3
4
5
6
7
8
9
10
11
12
public class GenericsClass<T> {
//key这个成员变量的类型为T,T的类型由外部指定
private T key;

public GenericsClass(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}

public T getKey() { //泛型方法getKey的返回值类型为T,T的类型由外部指定
return key;
}
}

测试

1
2
3
4
5
6
// 声明该类泛型为 String
final GenericsClass<String> genericsClass = new GenericsClass<>("张三");

// 返回值返回的就是声明的类型
// key = ”张三“
final String key = genericsClass.getKey();

不指明泛型则没有限定

1
2
3
4
5
6
7
final GenericsClass genericsClass1 = new GenericsClass<>("张三");
final GenericsClass genericsClass2 = new GenericsClass<>(123);

// key1 是 String
final Object key1 = genericsClass1.getKey();
// key2 是 Integer
final Object key2 = genericsClass2.getKey();

泛型不能使用 instanceof 操作

编译错误 Expression expected

1
2
3
4
5
6
7
public T getKey() { 
// 不能使用 instanceof 操作
if (T instanceof String) {
return key;
}
return key;
}

泛型接口

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中

简单的泛型接口

接口

1
2
3
public interface GenericsIntf<T> {
public T get();
}

实现类,未声明泛型实参,依赖创建对象时声明的泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即:class GenericsIntfImpl<T> implements GenericsIntf<T>{
* 如果不声明泛型,如:class GenericsIntfImpl implements GenericsIntf<T>,编译器会报错:"Unknown class"
*/
public class GenericsIntfImpl<T> implements GenericsIntf<T> {
private final T t;

public GenericsIntfImpl(final T t) {
this.t = t;
}

@Override
public T get() {
return t;
}
}

测试

1
2
final GenericsIntfImpl<String> impl = new GenericsIntfImpl<String>("张三");
final String s = impl.get();

也可以在实现类中向泛型接口声明固定的泛型实参

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 传入泛型实参时:
* 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口 GenericsIntf<T>
* 但是我们可以为T传入无数个实参,形成无数种类型的 GenericsIntf 接口。
* 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
* 即:GenericsIntf<T>,public T get();中的的T都要替换成传入的 String 类型。
*/
public class GenericsIntfImpl2 implements GenericsIntf<String> {
@Override
public String get() {
return "Hello World";
}
}

测试

1
2
3
final GenericsIntfImpl2 impl2 = new GenericsIntfImpl2();
final String s = impl2.get();
Assert.assertEquals(s, "Hello World");

泛型方法

泛型类,是在实例化类的时候指明泛型的具体类型

而泛型方法,是在调用方法的时候指明泛型的具体类型

简单使用

泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class GenericsMethod {

/**
* 泛型方法的基本介绍
*
* @param tClass 传入的泛型实参
* @return T 返回值为T类型
* 说明:
* 1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
* 2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
* 3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
* 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
*/
public <T> T genericsMethod(final Class<T> tClass) throws InstantiationException, IllegalAccessException {
return tClass.newInstance();
}

}

测试

1
2
3
4
5
final GenericsMethod genericsMethod = new GenericsMethod();

// 不同的入参有不同的返回值
final Student student = genericsMethod.genericsMethod(Student.class);
final Dog dog = genericsMethod.genericsMethod(Dog.class);

泛型方法的特征很容易混淆,定义一个复杂些的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class GenericsComplex<T> {

private final T key;

public GenericsComplex(final T key) {
this.key = key;
}

/*
* 此处并不是泛型方法
* 只是因为返回值的泛型是类中已经声明过的泛型,所以可以直接使用
*/
public T getKey() {
return key;
}

/*
* 这才是一个真正的泛型方法。
* 首先在 public 与返回值之间的 <T> 必不可少,这表明这是一个泛型方法,并且声明了一个泛型 T
* 这个 T 可以出现在这个泛型方法的任意位置.
* 泛型的数量也可以为任意多个
* 如:public <T,K> K showKeyName(Generic<T> container){...}
*/
public <T> T getKeyName(final GenericsClass<T> generics){
System.out.println("container key :" + generics.getKey());
//当然这个例子举的不太合适,只是为了说明泛型方法的特性
final T test = generics.getKey();
return test;
}

/*
* 这不是一个泛型方法,是一个普通的方法
* 只是使用了 Generic<Number> 这个泛型类做形参而已
*/
public void get(final GenericsClass<Number> obj) {
System.out.println(obj.getKey());
}
}

类中泛型方法

定义一个泛型类,其中包含普通方法使用泛型类声明的泛型泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class GenericsMethod2<T> {

public void show1(final T t) {
System.out.println(t.toString());
}

// 在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
// 由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
public <E> void show2(final E t) {
System.out.println(t.toString());
}

// 在泛型类中声明了一个泛型方法,使用泛型T
// 注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
public <T> void show3(final T t) {
System.out.println(t.toString());
}

}

测试

1
2
3
4
5
6
7
8
9
10
11
// 创建一个对象,声明泛型为 String
final GenericsMethod2<String> method2 = new GenericsMethod2<>();

// show1() 不是泛型方法,使用的是泛型类的泛型声明,所以只能传 String 类型的参数
method2.show1("Hello World");

// show2() 是泛型方法,泛型和类中声明的泛型无关
method2.show2(new Dog("旺财", 3));

// show3() 也是泛型方法,虽然也使用 <T> 作为泛型参数,但和类中的 T 没有关系,只和参数的类型you
method2.show3(new Student("李四", 20));

可变参数

泛型方法可以使用在可变参数上

定义方法

1
2
3
4
5
private <T> void printMsg(final T... args) {
for (final T t : args) {
System.out.print(t + " ");
}
}

测试

1
2
// 张三 200 4 李四 
this.printMsg("张三", 200, 4L, "李四");

静态方法

静态方法无法直接使用定义在类上的泛型,因为泛型在对象实例化时传入,静态方法无法获取类上的泛型信息

所以如果静态方法想使用泛型,就必须定义成泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class StaticGenericsMethod<T extends Animal> {

// 同理,这里的 T 只针对该方法,和类上的 T 不是一个东西
public static <T extends Animal> void staticShow(final T obj) {
System.out.println("static");
System.out.println(obj.toString());
}

public void show(final T obj) {
System.out.println("param");
System.out.println(obj.toString());
}

}

调用

1
2
3
4
5
6
7
final Dog dog = new Dog("旺财", 5);

// 静态方法,方法参数要求传入 Animal 的子类
StaticGenericsMethod.staticShow(dog);
// 成员方法,实例对象泛型要求传入 Animal 的子类
final StaticGenericsMethod<Dog> method = new StaticGenericsMethod<>();
method.show(dog);

需要注意,静态泛型方法会导致类型转换异常

正常的类型转换,都会在编译阶段提示错误

Inconvertible types; cannot cast 'java.lang.Integer' to 'java.lang.String'

1
2
3
// 将 Integer 类型转换为 String 类型,明显是错误的
final Integer integer = new Integer(10);
final String s = (String)integer;

构造一个静态泛型方法

1
2
3
private static <T> T convert(final Object obj) {
return (T)obj;
}

调用方法后再进行转换,则只会在运行时抛出 java.lang.ClassCastException

1
2
3
final Integer integer = new Integer(10);
final Object convert = convert(integer);
final String s = (String)convert;

原因是因为 Java 的泛型方法属于伪泛型,在编译的时候将进行类型擦除

普通的泛型方法在类构建时已经明确制定了对应的类型,而在静态泛型方法中,类型是无法直接推测的,缺少了明确的类型,最终造成类型转化异常

编译后的 convert 方法

1
2
3
private static <T extends String> T convert(final Object obj) {
return (T)obj;
}

特点

泛型通配符

子类和父类的泛型不能被认为是父类的同一种泛型

定义一个方法,参数要求 GenericsClass 对象且泛型为 Number

1
2
3
private void get(final GenericsClass<Number> num) {
System.out.println(num.getKey());
}

创建一个泛型为 Integer 类型的对象,则无法调用该方法

1
2
3
4
final GenericsClass<Integer> genericsClass = new GenericsClass(10);
// Required type: GenericsClass <Number>
// Provided: GenericsClass <Integer>
this.get(genericsClass);

虽然 IntegerNumber 的子类,但明显 GenericsClass <Integer> 不能被看作 GenericsClass<Number>

同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的

可以使用泛型通配符 ?extendssuper 解决此类问题

注意:? 是实参,并不是形参,它代表所有 Object 类

修改上面的代码,GenericsClass<? extends Number> 表示对象的泛型可以是继承 Number 的任何类型

1
2
3
private void get(final GenericsClass<? extends Number> num) {
System.out.println(num.getKey());
}

extendssuper 区别:

  • extends 规定了上界,即规定了泛型实参的最底层父类
  • super 规定了下界,即规定了泛型实参最上层的子类(实现类)

Java API

ParameterizedType

当泛型为类上泛型时,通过 Class 对象的 getGenericSuperclass 方法将会获取到 ParameterizedTypeType 接口对象,即为类上定义的泛型信息

可以使用 getActualTypeArguments 获取真实的泛型类型参数

定义泛型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class GenericClass<T> {

private T obj;

public void save(T obj) {
this.obj = obj;
}

public T get() {
return obj;
}

public void showGenericInfo() {
Type genericSuperclass = this.getClass().getGenericSuperclass();
System.out.println(genericSuperclass.getClass());

// real type
Type type = ((ParameterizedType)genericSuperclass).getActualTypeArguments()[0];
System.out.println(type);
}
}

实现类

1
2
3
4
5
6
public class Run extends GenericClass<Student> {
public static void main(String[] args) {
Run run = new Run();
run.showGenericInfo();
}
}
1
2
class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
java.util.List<generic.Student>

getGenericSuperclass 获取到的对象是 ParameterizedTypeImpl,即 ParameterizedType 的实现类

从中还可以获取到真实的泛型信息 Student

如果是一个嵌套的泛型结构呢

修改下 showGenericInfo 方法

1
2
3
4
5
6
7
8
9
10
11
12
public void showGenericInfo() {
Type genericSuperclass = this.getClass().getGenericSuperclass();
System.out.println(genericSuperclass.getClass());

System.out.println("--------------------------");
// real type
while (genericSuperclass instanceof ParameterizedType) {
Type actualTypeArgument = ((ParameterizedType)genericSuperclass).getActualTypeArguments()[0];
System.out.println(actualTypeArgument);
genericSuperclass = actualTypeArgument;
}
}
1
2
3
4
5
6
public class Run extends GenericClass<List<Student>> {
public static void main(String[] args) {
Run run = new Run();
run.showGenericInfo();
}
}
1
2
3
4
class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
--------------------------
java.util.List<generic.Student>
class generic.Student

可以看出泛型 List<Student>,第一次获取到的 ParameterizedTypeList 信息,它依然是一个 ParameterizedType 对象,而后还可以获取到下面定义的 Student

getGenericInterfaces

泛型接口同理,不过需要通过 getGenericInterfaces 方法进行获取

public Type[] getGenericInterfaces() 因为接口是一对多,所以这里获取到的是一个数组,也是 ParameterizedType

定义泛型接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface GenericInterface<T> {

default void showGenericInfo() {
Type genericSuperclass = this.getClass().getGenericInterfaces()[0];
System.out.println(genericSuperclass.getClass());

System.out.println("--------------------------");
// real type
while (genericSuperclass instanceof ParameterizedType) {
Type actualTypeArgument = ((ParameterizedType)genericSuperclass).getActualTypeArguments()[0];
System.out.println(actualTypeArgument);
genericSuperclass = actualTypeArgument;
}
}
}

实现

1
2
3
4
5
6
public class Run implements GenericInterface<List<Student>> {
public static void main(String[] args) {
Run run = new Run();
run.showGenericInfo();
}
}
1
2
3
4
class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
--------------------------
java.util.List<generic.Student>
class generic.Student

TypeVariable

当使用泛型方法时,其参数或者返回值拿到的泛型信息为 TypeVariable

这时只能拿到泛型定义时的边界信息,如果没有边界信息则会擦除为 Object

定义泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
public class GenericMethod {

@SneakyThrows
public <T> void method(T obj) {
Method method = this.getClass().getMethods()[0];
Type genericParameterType = method.getGenericParameterTypes()[0];

TypeVariable typeVariable = (TypeVariable)genericParameterType;
System.out.println(typeVariable.getName());
System.out.println(Arrays.toString(typeVariable.getBounds()));
}
}

调用

1
2
3
4
5
6
public class Run {
public static void main(String[] args) {
GenericMethod genericMethod = new GenericMethod();
genericMethod.method(new ArrayList<Student>());
}
}
1
2
T
[class java.lang.Object]

如果规定了边界

1
public <T extends String> void method(T obj)
1
2
T
[class java.lang.String]

应用

很多 JSON 工具都定义了类似名为 TypeReference 的类,其作用就是通过泛型类来传递需要反序列化的泛型信息,避免泛型擦除

如 fastjson 中的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class TypeReference<T> {
static ConcurrentMap<Type, Type> classTypeCache
= new ConcurrentHashMap<Type, Type>(16, 0.75f, 1);

protected final Type type;

/**
* Constructs a new type literal. Derives represented class from type
* parameter.
*
* <p>Clients create an empty anonymous subclass. Doing so embeds the type
* parameter in the anonymous class's type hierarchy so we can reconstitute it
* at runtime despite erasure.
*/
protected TypeReference() {
Type superClass = getClass().getGenericSuperclass();

Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];

Type cachedType = classTypeCache.get(type);
if (cachedType == null) {
classTypeCache.putIfAbsent(type, type);
cachedType = classTypeCache.get(type);
}

this.type = cachedType;
}

...

}

这样我们就可以通过 TypeReference 对象保存需要反序列化的泛型信息

1
2
3
4
5
6
7
8
9
10
public class Run {
public static void main(String[] args) {
Student student1 = new Student("张三", 20);
Student student2 = new Student("李四", 21);
String jsonStr = JSON.toJSONString(Arrays.asList(student1, student2));

List<Student> list = JSON.parseObject(jsonStr, new TypeReference<List<Student>>() {});
System.out.println(list);
}
}
1
[Student(name=张三, age=20), Student(name=李四, age=21)]

参考

java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一_VieLei的博客-CSDN博客_java泛型

Java泛型 | Jackson TypeReference获取泛型类型信息 - 知乎 (zhihu.com)