java(程序员眼中的类):
import java.sql.Timestamp;
import java.util.Date;
public class TestGenerics <T extends Date> {
T t;
public T getT() {
return t;
}
public static void main(String[] args) {
}
public void test() {
final TestGenerics<Timestamp> dateSub = new TestGenerics<>();
final Timestamp t = dateSub.getT();
}
}
ByteCode(虚拟机眼中的类):
// class version 52.0 (52)
// access flags 0x21
// signature Ljava/lang/Object;
// declaration: TestGenerics
public class TestGenerics {
// compiled from: TestGenerics.java
// access flags 0x0
// signature TT;
// declaration: t extends T
Ljava/util/Date; t
// access flags 0x1
public <init>()V
L0
LINENUMBER 4 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this LTestGenerics; L0 L1 0
// signature LTestGenerics;
// declaration: this extends TestGenerics
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
// signature ()TT;
// declaration: T getT()
public getT()Ljava/util/Date;
L0
LINENUMBER 8 L0
ALOAD 0
GETFIELD TestGenerics.t : Ljava/util/Date;
ARETURN
L1
LOCALVARIABLE this LTestGenerics; L0 L1 0
// signature LTestGenerics;
// declaration: this extends TestGenerics
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 11 L0
RETURN
L1
LOCALVARIABLE args [Ljava/lang/String; L0 L1 0
MAXSTACK = 0
MAXLOCALS = 1
// access flags 0x1
public test()V
L0
LINENUMBER 14 L0
NEW TestGenerics
DUP
INVOKESPECIAL TestGenerics.<init> ()V
ASTORE 1
L1
LINENUMBER 15 L1
ALOAD 1
INVOKEVIRTUAL TestGenerics.getT ()Ljava/util/Date;
CHECKCAST java/sql/Timestamp
ASTORE 2
L2
LINENUMBER 16 L2
RETURN
L3
LOCALVARIABLE this LTestGenerics; L0 L3 0
// signature LTestGenerics;
// declaration: this extends TestGenerics
LOCALVARIABLE dateSub LTestGenerics; L1 L3 1
// signature LTestGenerics;
// declaration: dateSub extends TestGenerics
LOCALVARIABLE t Ljava/sql/Timestamp; L2 L3 2
MAXSTACK = 2
MAXLOCALS = 3
}
1.虚拟机眼中没有泛型, 只有一个类TestGenerics (这就是兼容旧版本)
所以java的泛型是伪泛型, 是一个非运行期现象
2. 这里, 编译器把类中用到限定类型的地方做了向上转型, 这里是转为了Date
就是擦除了泛型, 把类结构变成非泛型类也就是虚拟机认识的样子
3. 再看CHECKCAST java/sql/Timestamp, 说明编译器根据传入的具体类型, 再使用到泛型的地方, 加了一条指令, 即检验并强制向左侧类型强转
4. 左侧类型如果乱写呢, 如何检查出来: signature/declaration是编译器记下的左右两侧的信息
// signature LTestGenerics
// declaration: dateSub extends
补充
5. 擦除的时候, 不是说向上转型, 比如 T extends A & B …多个
那就擦成A. 此时泛型类定义的那些调用了T.方法的地方, 只要不是A的方法, 也会有强转,调用的是B方法就会强转成B再调用
6. 所以擦除后需要在泛型类中强转(擦除后类没那个方法), 也需要在调用的地方强转(因为方法返回值也被擦了). 前者意味着泛型类
7. 为什么不能数组泛型, 想象下有一泛型类中有两个成员, 以及getter
假设T是Date
List t …;
t[] a …;
泛型擦除后, List本身也是泛型, 也要擦除, 擦为List, 兼容旧版本:
即List t; 还是List, 对于虚拟机来说, getT() 都不用转. 等到你getT().get(0)再转也不迟
a 为Object[]; getA 编译器想要返回 Date[], 不可能的, 因为擦除后, 相当于直接声明了一个Object[], 和随意申明了Object[]然后再想强转成Date[], 这虚拟机是不允许的.虽然有一些写法可以规避, 但是不推荐. 因为有坑, 编译器也提示不了你
所以差别的在于, 包装泛型的数据结构, List是一个类, 隐去泛型之后就是一个List, 擦除后声明的是List再转List虚拟机无所谓. 数组并不是一个类, 编译器擦成Object, 声明了一个标记为Object的对象, 已经无法强转回来了.因为声明和想转的已兼容
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)