Java泛型擦除与兼容

Java泛型擦除与兼容,第1张

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的对象, 已经无法强转回来了.因为声明和想转的已兼容

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/757117.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-30
下一篇 2022-04-30

发表评论

登录后才能评论

评论列表(0条)

保存