本文是本人学习《JavaSE学习笔记 - 泛型进阶》这篇文章的笔记,记录自己学习理解的过程,主要是作为备忘。建议大家直接看《JavaSE学习笔记 - 泛型进阶》这篇文章原文后再来看本文。
使用泛型的时候不指定类型实参,创建出来的类型就是原生类型
ArrayList rawList = new ArrayList();
原生类型与Object
参数化类型的区别
List list = new ArrayList();
list = new ArrayList();
List
你会发现无法用new T()来创建一个对象
public class GenericHolder {
// Cannot create a generic array of T
private T[] array = new T[10];
}
由以下代码可得出结论使用多种类型实参参数化同一泛型创建出对应的参数化类型时,这些参数化类型实际上都是同一种类型,即是它们共享同一份字节码
public class ErasedTypeEquivalence {
public static void main(String[] args) {
Class> c1 = new ArrayList().getClass();
Class> c2 = new ArrayList().getClass();
System.out.println(c1 == c2);
System.out.println(c1.getName());
System.out.println(c2.getName());
}
}
// 输出结果
true
java.util.ArrayList
java.util.ArrayList
Java泛型是伪泛型,类型实参的类型信息在编译的过程中会被擦除掉,而这个过程就是类型擦除,在编译后的字节码文件中,所有泛型类型都已经替换为对应的原生类型,并在相应的地方插入了强制转换。所以在运行时,所有泛型的类型信息对于JVM是不可见的。更通俗直白地讲,类型擦除就是将泛型代码转换为非泛型代码的过程。
如下一个普通泛型类
public class Holder {
private T object;
public void set(T object) {
this.object = object;
}
public T get() {
return object;
}
public static void main(String[] args) {
Holder holder = new Holder();
holder.set("MakwaN");
String value = holder.get();
System.out.println("value = " + value);
}
}
命令行中运行该命令javap -c -v -p Holder>bytecode.txt查看字节码,我是用Android Studio的ASM Bytecode Viewer插件查看的
main()方法字节码
字节码语法不懂没关系,只要看这些关键的地方大概就会明白,所有的泛型都被擦除成Object,只是在使用的地方转换成具体的类型
准确的说,应该说泛型的类型形参将擦除到它被限制的第一个边界所代表的类型,因为未设置边界,所以默认为Object
// class version 51.0 (51)
// access flags 0x21
// signature Ljava/lang/Object;
// declaration: com/himmy/java/test/Holder
public class com/himmy/java/test/Holder {
// compiled from: Holder.java
// access flags 0x2
// signature TT;
// declaration: object extends T
private Ljava/lang/Object; object
// access flags 0x1
public ()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object. ()V
RETURN
L1
LOCALVARIABLE this Lcom/himmy/java/test/Holder; L0 L1 0
// signature Lcom/himmy/java/test/Holder;
// declaration: this extends com.himmy.java.test.Holder
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
// signature (TT;)V
// declaration: void set(T)
public set(Ljava/lang/Object;)V
L0
LINENUMBER 7 L0
ALOAD 0
ALOAD 1
PUTFIELD com/himmy/java/test/Holder.object : Ljava/lang/Object;
L1
LINENUMBER 8 L1
RETURN
L2
LOCALVARIABLE this Lcom/himmy/java/test/Holder; L0 L2 0
// signature Lcom/himmy/java/test/Holder;
// declaration: this extends com.himmy.java.test.Holder
LOCALVARIABLE object Ljava/lang/Object; L0 L2 1
// signature TT;
// declaration: object extends T
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1
// signature ()TT;
// declaration: T get()
public get()Ljava/lang/Object;
L0
LINENUMBER 11 L0
ALOAD 0
GETFIELD com/himmy/java/test/Holder.object : Ljava/lang/Object;
ARETURN
L1
LOCALVARIABLE this Lcom/himmy/java/test/Holder; L0 L1 0
// signature Lcom/himmy/java/test/Holder;
// declaration: this extends com.himmy.java.test.Holder
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 15 L0
NEW com/himmy/java/test/Holder
DUP
INVOKESPECIAL com/himmy/java/test/Holder. ()V
ASTORE 1
L1
LINENUMBER 16 L1
ALOAD 1
LDC "MakwaN"
INVOKEVIRTUAL com/himmy/java/test/Holder.set (Ljava/lang/Object;)V
L2
LINENUMBER 17 L2
ALOAD 1
INVOKEVIRTUAL com/himmy/java/test/Holder.get ()Ljava/lang/Object;
CHECKCAST java/lang/String
ASTORE 2
L3
LINENUMBER 18 L3
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder. ()V
LDC "value = "
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 2
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L4
LINENUMBER 19 L4
RETURN
L5
LOCALVARIABLE args [Ljava/lang/String; L0 L5 0
LOCALVARIABLE holder Lcom/himmy/java/test/Holder; L1 L5 1
// signature Lcom/himmy/java/test/Holder;
// declaration: holder extends com.himmy.java.test.Holder
LOCALVARIABLE value Ljava/lang/String; L3 L5 2
MAXSTACK = 3
MAXLOCALS = 3
}
如果非要在泛型中创建实例呢?可以通过Class
public class Holder {
private T object;
private Class clz;
public Holder(Class clz) {
this.clz = clz;
}
public T get() {
try {
object = clz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return object;
}
public static void main(String[] args) {
Holder holder = new Holder<>(String.class);
String s = holder.get();
System.out.println(s.getClass().getSimpleName());
}
}
下面这种写法是行不通的
T[] array = new T[10];
还是需要Class
T[] array = (T[]) Array.newInstance(class, length);
与常见泛型对象或者泛型数组一样,还是要依靠Class
这样是不行哒
obj instanceof T;
这样子是ok哒
clz.isInstance(obj);
设置边界的语法为
对于单边界,泛型实参的范围为边界类型或者其子类型
对于多边界,泛型实参分为所有边界类型及所有边界类型的共同子集。比如泛型类所限制的边界是T extends A & B & C
,那么类型实参必须同时为类型A
、B
、C
的子类型才行。
泛型的边界可以限制泛型形参为某个类型的子集,并且在泛型类内可以使用边界类的方法,如下代码可以直接使用length()方法,因为泛型形参的范围是String或者String的子类
public class Holder {
private T t;
public int length() {
return t.length();
}
}
默认边界,对于
关于多边界情况下的类型擦除,有如下结论,可结合字节码验证结论
还需要注意的是泛型边界中只能用extends,super关键字是在通配符上下限中使用的,后面会介绍
泛型边界是用在泛型形参上,而泛型统配符?的上下限extends、super是用在泛型实参上,这点需要注意下
当然,泛型边界也可以应用再泛型方法上
static void fun(T t) {
System.out.println(t.length());
}