java泛型的类型擦除,Java 类型擦除

  java泛型的类型擦除,Java 类型擦除

  00-1010 1.什么是类型擦除2。什么是原3型。类型擦除引起的问题及解决方案3.1与多态冲突3.2静态类和方法中的问题不能被instanceof3.3使用。

  

目录

Java泛型是JDK 5中引入的新功能。泛型提供了编译时类型安全检测机制,允许程序员在编译时检测非法类型。泛型的本质是参数化类型,即被操作的数据类型被指定为参数。

 

  Java的泛型是伪泛型。Java的泛型基本都是在编译器层面实现的,生成的字节码不包含泛型的类型信息。使用泛型时,会添加类型参数,这些参数将在编译器编译时被移除。这个过程被称为类型擦除。

  比如代码中定义的ListObject、ListString等类型,编译后都会变成List,JVM只会看到List,而泛型附加的类型信息JVM是看不到的。Java编译器在编译过程中会尽力寻找可能的错误,但在运行时还是找不到类型转换的异常。类型擦除也是Java泛型和C模板机制的一个重要区别。

  示例1:

  public static void main(String[]args){ ListString String list=new ArrayList();string list . add( string );list integer integer list=new ArrayList();integer list . add(1);system . out . println(string list . getclass()==integer list . getclass());}输出真

  这里我们定义了两个ArrayList,它们的类型分别是String和Integer,并通过。getClass()。结果是相等的,表明类型已经被擦除。

  示例2

  公共静态void main(String[] args)引发异常{ ListString String list=new ArrayList();string list . add( string );//string list . add(1);//在这里,如果直接添加int类型的参数,会得到一个错误:stringlist.getclass()。getmethod (add ,object.class)。invoke (stringlist,1);system . out . println(string list . tostring());}输出[string,1]

  这里我们定义了一个String类型的数组列表。如果我们直接调用add()方法添加一个整数参数,就会报错。我们使用反射调用方法add()添加一个整数类型参数,表示字符串类型在编译后被擦除。

  00-1010原始类型是删除通用信息后字节码中类型变量的真实类型。每当定义泛型时,将自动提供相应的原始类型,类型变量将被删除,并使用其限定类型(无限变量将被Object替换)。

  class PairT {私有T值;public T getValue() {返回值;} public void setValue(T value){ this . value=value;} } pair的原始类型是

  类对{私有对象值;公共对象getValue() {返回值;} public void setValue(对象值){ this.value=value}}在PairT中,T是一个无限类型变量,所以用Object代替,结果就是一个普通的类,就像Java语言中没有加入泛型之前已经实现的一样。您可以在程序中包含不同类型的线对,例如线对字符串。

  g>或Pair<Integer>,但是擦除类型后他们的就成为原始的Pair类型了,原始类型都是Object。

  如果类型变量有限定,那么原始类型就用第一个边界的类型变量类替换。

  如:

  

public class Pair<T extends Comparable> {}

那么原始类型就是Comparable

 

  在调用泛型方法时,可以指定泛型,也可以不指定泛型。

  在不指定泛型的情况下,泛型变量的类型为该方法中的几种类型的同一父类的最小级,直到Object在指定泛型的情况下,该方法的几种类型必须是该泛型的实例的类型或者其子类

  

public static void main(String[] args) {          /**不指定泛型的时候*/          int i = Test.add(1, 2); //这两个参数都是Integer,所以T为Integer类型          Number f = Test.add(1, 1.2); //这两个参数一个是Integer,一个是Float,所以取同一父类的最小级,为Number          Object o = Test.add(1, "asd"); //这两个参数一个是Integer,一个是Float,所以取同一父类的最小级,为Object          /**指定泛型的时候*/          int a = Test.<Integer>add(1, 2); //指定了Integer,所以只能为Integer类型或者其子类          int b = Test.<Integer>add(1, 2.2); //编译错误,指定了Integer,不能为Float          Number c = Test.<Number>add(1, 2.2); //指定为Number,所以可以为Integer和Float      }      //这是一个简单的泛型方法      public static <T> T add(T x,T y){          return y;      }

 

  

3. 类型擦除带来的问题和解决方法

 

  

3.1 和多态的冲突

有这样一个泛型类

 

  

class Pair<T> {    private T value;    public T getValue() {        return value;    }    public void setValue(T value) {        this.value = value;    }}

一个子类继承它

 

  

class DateSon extends Pair<Date> { @Override public void setValue(Date value) { super.setValue(value); } @Override public Date getValue() { return super.getValue(); }}

在子类中,我们设定泛型类型为Date,那么父类的两个方法参数类型都是Date

 

  

public Date getValue() {   return value;}public void setValue(Date value) {   this.value = value;}

我们从代码编译来看,@Override重写父类方法,没有任何问题。

 

  但是,类型擦除后,父类的泛型类型都变为了Object,编译后为

  

class Pair {      private Object value;      public Object getValue() {          return value;      }      public void setValue(Object  value) {          this.value = value;      }  }

子类的重写方法

 

  

@Overridepublic void setValue(Date value) { super.setValue(value);}@Overridepublic Date getValue() { return super.getValue();}

参数类型不一样,参数名一样… 根据Java语言特性,这应该是重载,而不是重写啊

 

  写一个方法测试一下

  

public static void main(String[] args) { DateSon dateSon = new DateSon(); dateSon.setValue(new Date()); dateSon.setValue(new Object()); // 编译报错 }

如果是重载,第一个和第二个setValue都应该编译通过,但是发现并没有继承父类setValue参数类型是Object的方法,所以说确实是重写

 

  我们本意是通过设置泛型类型为Date,实现重写,但是这和泛型的类型擦除,显然冲突了。

  针对这种冲突,JVM采取了一种特殊的方法,桥方法

  我们用javap -c className的方式反编译下DateSon子类的字节码,结果如下:

  

class com.java.generic.DateSon extends com.java.generic.Pair<java.util.Date> {  com.java.generic.DateSon();    Code:       0: aload_0       1: invokespecial #1                  // Method com/java/generic/Pair."<init>":()V       4: return  public void setValue(java.util.Date);    Code:       0: aload_0       1: aload_1       2: invokespecial #2                  // Method com/java/generic/Pair.setValue:(Ljava/lang/Object;)V       5: return  public java.util.Date getValue();    Code:       0: aload_0       1: invokespecial #3                  // Method com/java/generic/Pair.getValue:()Ljava/lang/Object;       4: checkcast     #4                  // class java/util/Date       7: areturn  public void setValue(java.lang.Object);    Code:       0: aload_0       1: aload_1       2: checkcast     #4                  // class java/util/Date       5: invokevirtual #5                  // Method setValue:(Ljava/util/Date;)V       8: return  public java.lang.Object getValue();    Code:       0: aload_0       1: invokevirtual #6                  // Method getValue:()Ljava/util/Date;       4: areturn}

最后的两个方法,就是编译器自己生成的桥方法。可以看到桥方法的参数类型都是Object,也就是说,子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue和getValue方法上面的@Oveerride只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。

 

  所以,虚拟机巧妙的使用了桥方法,来解决了类型擦除和多态的冲突。

  

 

  

3.2 不能使用instanceof

List<String> stringList = new ArrayList<>();System.out.println(stringList instanceof ArrayList<String>);

类型擦除后String类型不存在了,所以不能使用instanceof判断

 

  

 

  

3.3 在静态类和静态方法中的问题

public class Test2<T> { public static T one; //编译错误 public static T show(T one){ //编译错误 return null; } }

因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。

 

  但是注意:

  

public class Test2<T> {        public static <T> T show(T one){ //这是正确的            return null;        }    }

因为这是一个泛型方法,在泛型方法中使用的T是自己在方法中定义的T,而不是泛型类中的T

 

  到此这篇关于Java泛型类型擦除的文章就介绍到这了,更多相关Java泛型擦除内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: