Java泛型之类型擦除

类型擦除

学过C++模板的,在使用Java泛型的时候,会感觉到有点不疑问,例如:(1)无法定义一个泛型数组、无法调用泛型参数对象中对应的方法(当然,通过extends关键字是可以做到,只是比较麻烦);(2)ArrayList<Integer>和ArrayList<String>在运行时的类型是相同的。Java中的泛型有这些问题,是它的实现机制决定的,即“类型擦除”。

  1. 类型擦除的定义:编译通过后,准备进入JVM运行时,就不再有类型参数的概念,换句话说:每定义一个泛型类型,JVM会自动提供一个对应的原生类;
public class Holder4<T> {

private T a;
private T b;
private T c;

public Holder4(T a, T b, T c) {
    this.a = a;
    this.b = b;
    this.c = c;
}

public T getA() {
    return a;
}

public T getB() {
    return b;
}

public T getC() {
    return c;
}

public void setA(T a) {
    this.a = a;
}

public void setB(T b) {
    this.b = b;
}

public void setC(T c) {
    this.c = c;
}

   public static void main(String[] args) {
       Holder4<Automobile> holder4 = new Holder4<>(new Automobile(),new Automobile(), new Automobile());

       Automobile a = holder4.getA(); //编译器帮忙转型,不需要显式转型
       Automobile b = holder4.getB();
       Automobile c = holder4.getC();
   }
}

在Java中,每定义一个泛型类型,就会自动提供一个对应的原始类型,例如:

public class Holder4Raw {

       private Object a;
       private Object b;
       private Object c;

       public Holder4Raw(Object a, Object b, Object c) {
           this.a = a;
           this.b = b;
           this.c = c;
       }

       public Object getA() {
           return a;
       }

       public Object getB() {
           return b;
       }

       public Object getC() {
           return c;
       }

       public void setA(Object a) {
           this.a = a;
       }

       public void setB(Object b) {
           this.b = b;
       }

       public void setC(Object c) {
           this.c = c;
       }

       public static void main(String[] args) {
           Holder4Raw holder4Raw = new Holder4Raw(new Automobile(),new Automobile(), new Automobile());

           Automobile a = (Automobile) holder4Raw.getA();  //显示的转型
           Automobile b = (Automobile) holder4Raw.getB();
           Automobile c = (Automobile) holder4Raw.getC();
       }
   }
  1. 为什么选择这种实现机制?
  • 在Java诞生10年后,才想实现类似于C++模板的概念,即泛型;
  • Java的类库是Java生态中非常宝贵的财富,必须保证向后兼容(即现有的代码和类文件依旧合法)和迁移兼容(泛化的代码和非泛化的代码可互相调用)基于上面这两个背景和考虑,Java设计者采取了“类型擦除”这种折中的实现方式。
  1. Java泛型依赖编译器实现,只存在于编译期,JVM中没有泛型的概念;那么,编译器做了什么工作呢?(1)set方法是编译期检查;(2)get方法的返回值进行转型,编译器插入了一个checkcast语句。

我们通过字节码进行观察,可以看出:(1)Holder4和Holder4Raw两个类的字节码完全相同;(2)在main函数的33、41和49行就是编译器插入的checkcast语句;

public class org.java.learn.generics.Holder4<T> {
  public org.java.learn.generics.Holder4(T, T, T);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: aload_1
       6: putfield      #2                  // Field a:Ljava/lang/Object;
       9: aload_0
      10: aload_2
      11: putfield      #3                  // Field b:Ljava/lang/Object;
      14: aload_0
      15: aload_3
      16: putfield      #4                  // Field c:Ljava/lang/Object;
      19: return

  public T getA();
    Code:
       0: aload_0
       1: getfield      #2                  // Field a:Ljava/lang/Object;
       4: areturn

  public T getB();
    Code:
       0: aload_0
       1: getfield      #3                  // Field b:Ljava/lang/Object;
       4: areturn

  public T getC();
    Code:
       0: aload_0
       1: getfield      #4                  // Field c:Ljava/lang/Object;
       4: areturn

  public void setA(T);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #2                  // Field a:Ljava/lang/Object;
       5: return

  public void setB(T);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #3                  // Field b:Ljava/lang/Object;
       5: return

  public void setC(T);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #4                  // Field c:Ljava/lang/Object;
       5: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #5                  // class org/java/learn/generics/Holder4
       3: dup
       4: new           #6                  // class org/java/learn/generics/Automobile
       7: dup
       8: invokespecial #7                  // Method org/java/learn/generics/Automobile."<init>":()V
      11: new           #6                  // class org/java/learn/generics/Automobile
      14: dup
      15: invokespecial #7                  // Method org/java/learn/generics/Automobile."<init>":()V
      18: new           #6                  // class org/java/learn/generics/Automobile
      21: dup
      22: invokespecial #7                  // Method org/java/learn/generics/Automobile."<init>":()V
      25: invokespecial #8                  // Method "<init>":(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
      28: astore_1
      29: aload_1
      30: invokevirtual #9                  // Method getA:()Ljava/lang/Object;
      33: checkcast     #6                  // class org/java/learn/generics/Automobile,get方法的转型
      36: astore_2
      37: aload_1
      38: invokevirtual #10                 // Method getB:()Ljava/lang/Object;
      41: checkcast     #6                  // class org/java/learn/generics/Automobile,get方法的转型
      44: astore_3
      45: aload_1
      46: invokevirtual #11                 // Method getC:()Ljava/lang/Object;
      49: checkcast     #6                  // class org/java/learn/generics/Automobile,get方法的转型
      52: astore        4
      54: return
}

参考资料

  1. 《Java编程思想》
  2. 《Effective Java》
  3. 《Java核心技术》

原文作者:duqicauc
转载地址:Java泛型之类型擦除 | Spring For All

from: https://zhuanlan.zhihu.com/p/31741402

原文地址:https://www.cnblogs.com/GarfieldEr007/p/10197906.html

时间: 2024-10-13 05:49:09

Java泛型之类型擦除的相关文章

Java泛型:类型擦除

类型擦除 代码片段一 Class c1 = new ArrayList<Integer>().getClass(); Class c2 = new ArrayList<String>().getClass(); System.out.println(c1 == c2); /* Output true */ 显然在平时使用中,ArrayList<Integer>()和new ArrayList<String>()是完全不同的类型,但是在这里,程序却的的确确会输

转:有关Java泛型的类型擦除(type erasing)

转载自:拈花微笑 自从Java 5引入泛型之后,Java与C++对于泛型不同的实现的优劣便一直是饭后的谈资.在我之前的很多training中,当讲到Java泛型时总是会和C++的实现比较,一般得出的结论是 Java使用类型擦除(type erasing),泛型信息只在编译时供javac作类型检查用,在编译后便被javac擦除,因此无法被反射 C++使用代码模板实现泛型,即在预处理时会生成类似?list_int?,?list_char?等的泛型类,虽然解决Java的运行时伪泛型的问题,但是会导致编

获取java泛型参数类型

1.Type和Class的区别 简单来说,Class实现了Type接口. Type源码定义: package java.lang.reflect; /**  * Type is the common superinterface for all types in the Java  * programming language. These include raw types, parameterized types,  * array types, type variables and pri

Java泛型之类型未被擦除

大家都知道Java源码在编译之后会擦除泛型信息,现在来看一个泛型未被擦除的情况,见ConcurrentHashMap.comparableClassFor方法. ParameterizedType.getActualTypeArguments能获取类所实现的接口中未被擦除的泛型信息.实验如下: public class Parameterized { public static void main(String[] args) { Type[] types = Integer.class.get

泛型 Generic 类型擦除引起的问题及解决方法

参考:http://blog.csdn.net/lonelyroamer/article/details/7868820#comments 因为种种原因,Java不能实现真正的泛型,只能使用类型擦除来实现伪泛型,这样虽然不会有类型膨胀的问题,但是也引起了许多新的问题.所以,Sun对这些问题作出了许多限制,避免我们犯各种错误. 1.先检查,再编译,以及检查编译的对象和引用传递的问题 既然说类型变量会在编译的时候擦除掉,那为什么我们往ArrayList<String> arrayList=new

[运行时获取模板类类型] Java 反射机制 + 类型擦除机制

给定一个带模板参数的类 class A<T> { } 如何在运行时获取 T的类型? 在C#中,这个很简单,CLR的反射机制是解释器支持的,大概代码为: namespace TestReflect { class Program<T> { public Type getTClass() { Type type= this.GetType(); Type[] tts = type.GetGenericArguments(); return tts[0]; } } } 可是在Java中,

Java中的类型擦除与桥方法

类型擦除 Java在语法中虽然存在泛型的概念,但是在虚拟机中却没有泛型的概念,虚拟机中所有的类型都是普通类.无论何时定义一个泛型类型,编译后类型会被都被自动转换成一个相应的原始类型. 比如这个类 public class Parent<T> { public void sayHello(T value) { System.out.println("This is Parent Class, value is " + value); } } 在编译后就变成了 public c

ParameterizedType获取java泛型参数类型

https://blog.csdn.net/qq_18242391/article/details/54251947 前言 这两天在看以前写的ssh项目时,遇到一个问题就是封装的BaseDaoImpl抽象类,构造方法里面是这样写的. Class<T> clazz; public BaseDaoImpl() { ParameterizedType pt = (ParameterizedType)getClass().getGenericSuperclass(); clazz = (Class&l

java泛型(二)、泛型的内部原理:类型擦除以及类型擦除带来的问题

java泛型(二).泛型的内部原理:类型擦除以及类型擦除带来的问题 参考:java核心技术 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首要前提是理解类型擦出(type erasure). Java中的泛型基本上都是在编译器这个层次来实现的.在生成的Java字节码中是不包含泛型中的类型信息的.使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉.这个过程就称为类型