1.什么是泛型
泛型指的参数化类型。类型可以当做参数传递的特性
2. 类型参数的理解
类型参数是参数,参数的特点有
- 声明
- 使用
- 赋值
//T1,T2就是声明类型参数的地方
class Pair<T1, T2> {
//使用类型参数的地方
public T1 first;
public T2 second;
//使用类型参数的地方
public Pair(T1 first, T2 second) {
this.first = first;
this.second = second;
}
}
@Test
public void one() {
//给类型参数赋值的地方
Pair<String, Integer> pair = new Pair<String,Integer>("test", 1);
}
2.1 类型参数让人困惑的地方
2.1.1 类型参数和普通参数的区别
比如说一个普通的方法,有一个String类型的参数s
那么就是参数类型是String,参数名是s
void funciont1(String s){
}
但是对于类型参数来说,它就只有一个参数名。
2.1.2 类型参数的声明和赋值
类型参数是一个参数,它就有声明,赋值,使用,声明和赋值两者要区分开,不然很容易搞混
1.声明类型参数
泛型类
//T1,T2就是类型参数
//Pair<T1,T2>是泛型类
class Pair<T1,T2>{
T[] ts;//泛型数组
}
泛型方法
//T 就是类型参数
//function1这个方法是泛型方法
fun <T> void function1(T t){
}
2.给类型参数赋值
给类型参数赋值为具体的类型值,
//String和Integer就 实际的类型值
//Pair<String, Integer> 的type是 参数化类型(ParameterizedType)
Pair<String, Integer> pair = new Paire()
3. 泛型的作用
让代码更安全可读性更高
比如说如果没有泛型,向集合中添加元素时,就可以添加任何类型的元素,
从集合中取出元素时,又必须进行类型转化,就很有可能发生类型转化异常
4.泛型擦除
4.0 类型擦除
编译时,类型参数会被替换为其限定类型,如果没有限定类型则替换为Object类型
java类
public class Pair<T1 extends String, T2> {
public T1 first;
public T2 second;
public Pair(T1 first, T2 second) {
this.first = first;
this.second = second;
}
public T1 getFirst() {
return first;
}
public void setFirst(T1 first) {
this.first = first;
}
public T2 getSecond() {
return second;
}
public void setSecond(T2 second) {
this.second = second;
}
}
使用ASMPlugin查看字节码
T1类型因为声明为String的子类型,所以T1被替换为了String类型
T2类型因为没有被限定,所以被替换为了Object类型
public class com/Pair {
// compiled from: Pair.java
// access flags 0x1
// signature TT1;
// declaration: first extends T1
public Ljava/lang/String; first
// access flags 0x1
// signature TT2;
// declaration: second extends T2
public Ljava/lang/Object; second
// access flags 0x1
// signature (TT1;TT2;)V
// declaration: void <init>(T1, T2)
public <init>(Ljava/lang/String;Ljava/lang/Object;)V
L0
LINENUMBER 7 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 8 L1
ALOAD 0
ALOAD 1
PUTFIELD com/Pair.first : Ljava/lang/String;
L2
LINENUMBER 9 L2
ALOAD 0
ALOAD 2
PUTFIELD com/Pair.second : Ljava/lang/Object;
L3
LINENUMBER 10 L3
RETURN
L4
LOCALVARIABLE this Lcom/Pair; L0 L4 0
// signature Lcom/Pair<TT1;TT2;>;
// declaration: this extends com.Pair<T1, T2>
LOCALVARIABLE first Ljava/lang/String; L0 L4 1
// signature TT1;
// declaration: first extends T1
LOCALVARIABLE second Ljava/lang/Object; L0 L4 2
// signature TT2;
// declaration: second extends T2
MAXSTACK = 2
MAXLOCALS = 3
// access flags 0x1
// signature ()TT1;
// declaration: T1 getFirst()
public getFirst()Ljava/lang/String;
L0
LINENUMBER 13 L0
ALOAD 0
GETFIELD com/Pair.first : Ljava/lang/String;
ARETURN
L1
LOCALVARIABLE this Lcom/Pair; L0 L1 0
// signature Lcom/Pair<TT1;TT2;>;
// declaration: this extends com.Pair<T1, T2>
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
// signature (TT1;)V
// declaration: void setFirst(T1)
public setFirst(Ljava/lang/String;)V
L0
LINENUMBER 17 L0
ALOAD 0
ALOAD 1
PUTFIELD com/Pair.first : Ljava/lang/String;
L1
LINENUMBER 18 L1
RETURN
L2
LOCALVARIABLE this Lcom/Pair; L0 L2 0
// signature Lcom/Pair<TT1;TT2;>;
// declaration: this extends com.Pair<T1, T2>
LOCALVARIABLE first Ljava/lang/String; L0 L2 1
// signature TT1;
// declaration: first extends T1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1
// signature ()TT2;
// declaration: T2 getSecond()
public getSecond()Ljava/lang/Object;
L0
LINENUMBER 21 L0
ALOAD 0
GETFIELD com/Pair.second : Ljava/lang/Object;
ARETURN
L1
LOCALVARIABLE this Lcom/Pair; L0 L1 0
// signature Lcom/Pair<TT1;TT2;>;
// declaration: this extends com.Pair<T1, T2>
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
// signature (TT2;)V
// declaration: void setSecond(T2)
public setSecond(Ljava/lang/Object;)V
L0
LINENUMBER 25 L0
ALOAD 0
ALOAD 1
PUTFIELD com/Pair.second : Ljava/lang/Object;
L1
LINENUMBER 26 L1
RETURN
L2
LOCALVARIABLE this Lcom/Pair; L0 L2 0
// signature Lcom/Pair<TT1;TT2;>;
// declaration: this extends com.Pair<T1, T2>
LOCALVARIABLE second Ljava/lang/Object; L0 L2 1
// signature TT2;
// declaration: second extends T2
MAXSTACK = 2
MAXLOCALS = 2
}
注意泛型T extends 类型1,类型2,那么它会被替换为离T最近的泛型1
4.1 桥方法
桥方法是用于继承泛型类时保证多态的。
使用父类引用 引用子类实例,如果没有桥方法,子类是无法覆盖父类带有类型参数的方法的
class Node<T> {
public T data;
public Node(T data) {
this.data = data; }
public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
}
class MyNode extends Node<Integer> {
public MyNode(Integer data) {
super(data); }
//Node<T> 泛型擦除后为 setData(Object data),而子类 MyNode 中并没有重写该方法,所以编译器会加入该桥方法保证多态
public void setData(Object data) {
setData((Integer) data);
}
//这个setData并没有覆盖父类的setData方法
public void setData(Integer data) {
super.setData(data);
}
}
public void main(){
Node node = new MyNode();
//如果没有桥方法,那么node.setData就调用的是父类的setData
node.setData(1)
}
4.2 泛型擦除的残留
类型参数的值被擦除后,还会保留到.class文件中,我们仍然可以通过反射获取类型参数的信息
Integer,String是赋值给Map中类型参数K,V的值,它们被擦除后会保存在.class文件中
而且这里我们也可以注意到,相关的方法叫做getXXXType(),因为我们获取的类型(Type)型信息
考点
1. 泛型的原理
泛型是从JDK5开始支持的,为了向下兼容,java虚拟机是不支持泛型的。
在编译时,会将所有的泛型信息擦除,将所有的泛型类型转化为原始类型。
2. java编译器是如何进行类型擦除的
-
将类型参数替换为限定类型
- 没有限定类替换为Object类型
- 有一个限定类替换为限定类
- 有多个限定类替换为第一个第一个限定
-
必要时进行类型转化(比如,一个类型有两个限定类,当它当做第二个限定类使用时,要进行类型转化)
-
生成桥方法保证多态性
3. 泛型的副作用
3.1 类型参数不能实例化为基本数据类型
以ArrayList为例,类型擦除后泛型参数E会被替换为Object,Object是无法引用基本数据类型的。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
public E get(int index) {
Objects.checkIndex(index, this.size);
return this.elementData(index);
}
}
3.2 不能使用instanceof进行泛型类的类型判断
其实可以获取到strings的类型信息 ParameterizedType 看它是否和 “java.util.ArrayList”相等
3.3 类型参数无法在静态成员了静态方法中使用
-
方法冲突
class ClassA<T>{ //编译后有两个equals(Object obj)方法 @Override public boolean equals(T obj) { return super.equals(obj); } } -
不能实例化类型参数
因为类型擦除后,类型参数就转化为Object.(其实也不是Object,是它的限定类)
如果允许实例化类型参数,那每次实例化得到的都是Object对象
-
不能实例化泛型数组
可能会导致类型转化异常
如果实例化泛型数组是允许的
```java
public static <T extends Comparable> T[] minmax(T... a){
T[] mm = new T[2];//实际这是不行的哈
return mm
}
```
类型擦除后
public static Comparable[] minmax(Comparable... a){
Comparable[] mm = new Comparable[2];
return mm
}
调用
String[] ss = minmax("Tom","Dick","Harry");//这里会报类型转化错误
这为什么会报错
因为String的父类是Comparable
String[]的父类是Comparable[]
有一个原则是父类引用是无法转化为子类引用的:
如下
5.通配符
5.1 作用
实例化类型参数时,不使用固定的类型, 而是限定类型参数的边界
@Test
public void one() {
//类型参数时Comparable的子类
List<? extends Comparable> comparables = new ArrayList<String>();
//类型参数时Interger的父类
List<? super Integer> integers = new ArrayList<Number>();
}
6.Type类型体系
Type类型体系是站在泛型的角度描述类的类型的
Type的类型有
- Class 类
- TypeVariable 类型参数
- 泛型数组
- 参数化类型
- 通配符类型
看下面的类型
//这个声明在类上的T是类型参数TypeVariable
class Demo0<T> {
//List的Type为Class
List list0;
//List<String>的Type为ParameterizedType,其中String为ActualTypeArguments,Type为Class
List<String> list1;
//List<? extends String>的Type为ParameterizedType,其中? extends String为ActualTypeArguments,
// ? extends String 的Type为WildCard
List<? extends String> list2;
//T[]的Type泛型数组 GenericArrayType
T[] array;
}
获取Type的方式
@Test
public void test() throws NoSuchFieldException {
TypeVariable typeVariable = Demo0.class.getTypeParameters()[0];
System.out.println(typeVariable);
Field list0Field = Demo0.class.getDeclaredField("list0");
System.out.println("list0的类型的type为:" + list0Field.getGenericType().getClass().getName());
Field list1Field = Demo0.class.getDeclaredField("list1");
Type genericTypeList1 = list1Field.getGenericType();
ParameterizedType parameterizedTypeList1 = (ParameterizedType) genericTypeList1;
Type type = parameterizedTypeList1.getActualTypeArguments()[0];
System.out.println("list1的类型的type为" + parameterizedTypeList1.getClass().getName());
System.out.println("list1的类型的typed的实际参数类型为 " + type.getClass().getName());
Field list2Field = Demo0.class.getDeclaredField("list2");
ParameterizedType parameterizedTypeList2 = (ParameterizedType) list2Field.getGenericType();
WildcardType wildcardType = (WildcardType) parameterizedTypeList2.getActualTypeArguments()[0];
System.out.println("list2的类型的type为 " + parameterizedTypeList2.getClass().getName());
System.out.println("list2的类型的type的实际参数类型为" + wildcardType.getClass().getName());
Field array = Demo0.class.getDeclaredField("array");
System.out.println("泛型数组:"+array.getGenericType().getClass().getName());
}