本质

java泛型(generics)是JDK5中引入的一个新特性,提供了编译时类型安全检测机制。该机制允许程序员在编译时检测到非法的类型。

泛型的本质是参数化类型,即给类型制定一个参数,然后再使用时再指定此参数具体的值,那样这个类型就可以在使用时决定了,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

1
ArrayList<String> stringValues = new ArrayList<String>();

为什么使用泛型

保证了类型的安全性

泛型的作用就是在编译的时候就会检查类型安全,并且所有的强制转换都是自动和隐式的。

1
2
3
4
5
6
7
8
ArrayList<Cat> stringValues = new ArrayList<Cat>();
stringValues.add(new Cat());
stringValues.add(new Dog());//编译不通过

//不使用,则可能会在运行时出错
ArrayList stringValues = new ArrayList();
stringValues.add(new Cat());
stringValues.add(new Dog());

相当于告诉了编译器每个集合接受的对象类型是什么,编译器在编译期就会做类型检查,告知是否插入了错误类型的对象,使得程序更加安全,增强了程序的健壮性。

消除强制转换

消除源代码中的许多强制类型转换,这使得代码更加可读,并且减少了出错机会。

1
2
3
4
5
6
7
8
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0);

List list = new ArrayList();
list.add("Hello");
//需要强制转换
String s = (String) list.get(0);

避免了不必要的装箱,拆箱操作

将简单类型作为Object传递时会引起Boxing和Unboxing操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing的操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中。

1
2
3
4
5
6
7
8
Object a = 1;//由于是object类型,会自动进行装箱操作
int b = (int) a;//强制转换,拆箱操作,这样一去一来,当次数多了以后会影响程序的运行效率。


public static <T> T getvalue(T t) {
return t;
}
int b = getvalue(1);

如何使用泛型

泛型类

1
public class 类名<泛型类型1, ...>{}

定义泛型类,在类后添加一对尖括号,并在尖括号中填写类型参数,参数可以有多个,过个参数使用逗号分隔

1
2
3
4
T:任意类型 type
E:集合中元素的类型 element
K:key-value形式 key
V:key-value形式 value
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class GenericClass<T> {
private T value;

public GenericClass(T value) {
this.value = value;
}

public T getValue() {
return value;
}

public void setValue(T value) {
this.value = value;
}
}

泛型接口

public <泛型类型> 返回类型 方法名(泛型类型 变量名) {}

方法声明中定义的形参只能在方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。当调用fun()方法时,根据传入的实际对象,编译器就会判断出类型形参T所代表的实际类型。

1
2
3
4
5
6
7
8
9
10
11
public interface GenericInterface<T> {
void show(T value);
}

pulic class StringShowImpl implements GenericInterface<String> {

@Override
public void show(String value) {
System.out.println(value);
}
}

泛型方法

1
修饰符<代表泛型的变量>返回值类型 方法名(参数){}
1
2
3
4
5
public <T> T genercMethod(T t){
System.out.println(t.getClass());
System.out.println(t);
return t;
}

这里可以看出,泛型方法随着我们的传入参数类型不同,他得到的类型也不同。泛型方法能使方法独立于类而产生变化。

泛型通配符

Java泛型的通配符是用于解决泛型之间引用传递问题的特殊语法,主要有以下三类

<?> :无边界的通配符

<? extends E>:固定上边界的通配符

<? super E>:固定下边界的通配符

1
2
3
4
5
6
7
8
//表示类型参数可以是任何类型
public class Apple<?>{}

//表示类型参数必须是A或者是A的子类
public class Apple<T extends A>{}

//表示类型参数必须是A或者是A的超类型
public class Apple<T super A>{}

泛型擦除

Java的泛型只在编译时有效,到了运行时这个泛型类型就会被擦除掉,即List和List在运行时其实都是List类型。

为什么选择这种实现机制?在Java诞生10年后,才实现类似C++模板的概念,即泛型。Java的类库是Java生态中非常宝贵的财富,所以需要保证向后兼容,和迁移兼容,基于上面的背景和考虑,Java设计者采取了类型擦除这种折中的实现方式。正是这样,令我们无法在运行期间随心所欲的获取到泛型参数的具体类型。

TypeToken使用

Gson在反序列化时需要定义一个TypeToken类型

1
private Type type = new TypeToken<List<Map<String, Foo>>>(){}.getType();

为什么要用TypeToken来定义反序列化的类型?正如上面所说,如果直接把List<Map<String, Foo>>的类型传过去,但是因为运行泛型被擦除了,所以得到的其实是List,那么后面的Gson就不知道要转成Map<String, Foo>类型了,这是Gson会默认转成LinkedTreeMap类型。

后面的大括号,在Java语法中,在这个语境,{}是用来定义匿名类,这个匿名类是继承了TypeToken类,它是TypeToken的子类。

为什么要通过子类来获取泛型的类型?这是TypeToken能够获取到泛型类型的关键,这是一个巧妙的方法,这个想法是这样子的,既然像List这样中的泛型会被擦除掉,那么我用一个子类SubList extends List这样的话,在JVM内部中会不会把父类泛型的类型给保存下来呢?这个子类需要继承的父类的泛型都是已经确定了的,JVM确实是有保存这部分信息的,它是保存在子类Class信息中,我们可以通过下述方法获取,https://stackoverflow.com/questions/937933/where-are-generic-types-stored-in-java-class-files

1
2
Type mySuperClass = foo.getClass().getGenericSuperClass();
Type type = ((ParameterizedType)mySuperClass).getActualTypeArguments()[0];

Class类的getGenericSuperClass()方法的注释是

概括来说就是对于带有泛型的class,返回一个ParameterizedType对象,对于Object、接口和原始类型返回null,对于数 组class则是返回Object.class。ParameterizedType是表示带有泛型参数的类型的Java类型,JDK1.5引入了泛型之 后,Java中所有的Class都实现了Type接口,ParameterizedType则是继承了Type接口,所有包含泛型的Class类都会实现 这个接口。

DEMO

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
/**
* @author 232604
*/
public class Person <T1, T2>{

}


/**
* @author 232604
*/
public class Student extends Person<Integer, Boolean>{

public static void main(String[] args) {
Student student = new Student();
Class clazz = student.getClass();
System.out.println(clazz.getSuperclass());

Type type = clazz.getGenericSuperclass();
System.out.println(type);

//这里获取参数化类型,及泛型
ParameterizedType p = (ParameterizedType)type;

//获取具体的泛型
Class class1 = (Class)p.getActualTypeArguments()[0];
System.out.println(class1);
Class class2 = (Class)p.getActualTypeArguments()[1];
System.out.println(class2);
}
}

运行结果:
class Person
Person<java.lang.Integer, java.lang.Boolean>
class java.lang.Integer
class java.lang.Boolean

引用

https://blog.csdn.net/qq_41701956/article/details/123473592

https://blog.csdn.net/weixin_49527334/article/details/115334014

https://blog.csdn.net/qq_26424655/article/details/71515741