Java的类型擦除

类型擦除

  • 概念:泛型是Java 1.5版本才引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前版本的代码兼容。那是因为,泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,我们称之为--类型擦除。

package com.heima.demo;
import java.util.ArrayList;
/**
*泛型擦除
*/
public class Test11{
   public static void main(String[] args){
       ArrayList<Integer> intList = new ArrayList<>();
       ArrayList<String> strList = new ArrayList<>();
       
       System.out.println(intList.getClass().getSimpleName());
        System.out.println(strList.getClass().getSimpleName());
       system.out.println(intList.getClass() == strList.getClass());
  }
}

  • 无限制类型擦除


public class Erasure<T>{
   public T key;
   public T getKey(){
       return key;
  }
   public void setKey(T key){
       this.key = key;
  }
}
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓无限制类型擦除
public class Erasure{
   public Object key;
   public Object getKey(){
       return key;
  }
   public void setKey(Object key){
       this.key = key;
  }
}

  • 实际用例

package com.heima.demo;
public class Erasure<T>{
   private T key;
   
   public T getKey(){
       return key;
  }
   
   public void setKey(T key){
       this.key = key;
  }
   
   public static void main(String[] args){
  Erasure<Integer> erasure = new Erasure<>();
  //利用反射获取Erasure类的字节码文件的Class类对象
  class<? extends Erasure> clz = erasure.getClass();
  //获取所有的成员变量
  Field[] declareFields = clz.getDeclareFields();
  for (Field declaredField : declaredFields){
      //打印成员变量的名称和类型
      System.out.println(declaredField.getName() + ":" + declaredField.getType().getSimpleName());
  }
}
}

  • 有限制类型擦除

public class Erasure<T extends Number>{
   public T key;
   public T getKey(){
       return key;
  }
   public void setKey(T key){
       this.key = key;
  }
}
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓有限制类型擦除
public class Erasure{
   public Number key;
   public Number getKey(){
       return key;
  }
   public void setKey(Number key){
       this.key = key;
  }
}

  • 实际用例


package com.heima.demo;
public class Erasure<T extends Number>{
   private T key;
   
   public T getKey(){
       return key;
  }
   
   public void setKey(T key){
       this.key = key;
  }
   public static void main(String[] args){
  Erasure<Integer> erasure = new Erasure<>();
  //利用反射获取Erasure类的字节码文件的Class类对象
  class<? extends Erasure> clz = erasure.getClass();
  //获取所有的成员变量
  Field[] declaredFields = clz.getDeclaredFields();
  for (Field declaredField : declaredFields){
      //打印成员变量的名称和类型
      System.out.println(declaredField.getName() + ":" + declaredField.getType().getSimpleName());
  }
}
}
  • 有限制的类型擦除会按照上限类型来做类型擦除


  • 擦除方法中类型定义的参数

public <T extends Number> T getValue(T value){
   return value;
}
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓类定义类型擦除
public Number getValue(Number value){
   return value;
}

package com.heima.demo;
public class Erasure<T extends Number>{
   private T key;
   
   public T getKey(){
       return key;
  }
   
   public void setKey(T key){
       this.key = key;
  }
   
   /**
    *泛型方法
    *@param t
    *@param <T>
    *@return
    */
   //泛型方法区别于普通方法就是在于它在public和返回值类型之间多了一个泛型列表
   public <T extends List> T show(T t){
       return t;
  }
   
   public static void main(String[] args){
  Erasure<Integer> erasure = new Erasure<>();
  //利用反射获取Erasure类的字节码文件的Class类对象
  class<? extends Erasure> clz = erasure.getClass();
  //获取所有的成员变量
  Field[] declaredFields = clz.getDeclaredFields();
  for (Field declaredField : declaredFields){
      //打印成员变量的名称和类型
      System.out.println(declaredField.getName() + ":" + declaredField.getType().getSimpleName());
  }
       System.out.println("*************************");
       //获取Erasure下所有的方法
       Method[] declaredMethods = clz.getDeclaredMethods();
       for(Method declaredMethod : declaredMethods){
           //遍历方法,打印方法名和方法的返回值类型。
           System.out.println(declaredMethod.getName() + ":" + declaredMethod.getReturnType().getSimpleName());
      }
}
}

桥接方法

public interface Info<T>{
   T info(T var);
}

public class InfoImpl implements Infor<Integer>{
   @Override
   public Integer info(Integer var){
       return var;
  }
}
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓类型擦除之桥接方法
public interface Info{
   Object info(Object var);
}

//一方面,保证接口原有类型。
public class InfoImpl implements Info{
   public Integer info(Integer var){
       return var;
  }
   //另一方面 → 桥接方法:额外生成保持接口和类的实现关系
   @Override
   public Object info(Object var){
       return info((Integer)var);
  }
}

  • 具体实例


package com.heima.demo;
/**
*泛型接口
*@param <T>
*/
public interface Info<T>{
   T info(T t);
}

public class infoImpl implements Info<Integer>{
   @Override
   public Integer info(Integer value){
       return value;
  }
   
   //通过反射看infoImpl下是不是只有一个函数,会不会像样板的那样会多生成一个函数。
   public static void main(String[] args){
       //拿infoImpl字节码的文件类对象
       Class<InfoImpl> infoClass = InfoImpl.class;
       //获取所有方法
       Method[] infoImplMethods = infoClass.getDeclaredMethods();
       for(Method method : infoImplMethods){
           System.out.println(method.getName() + ":" + method.getRetrunType().getSimpleName());
      }
  }
}

泛型数组

泛型数组的创建

  • 可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象。

  • 可以通过java.lang.reflect.Array的newInstance(Class<T>, int)创建 T[] 数组。


package com.heima.demo;
import java.util.ArrayList;
/**
*泛型与数组
*/
public class Test10{
   public static void main(String[] args){
       ArrayList<String>[] ListArr; //可以声明带泛型的数组引用
       ArrayList<String>[] ListArr = new ArrayList<>[5]; //不能直接创建带泛型的数组对象,所以这句是错的。
       
       //真正创建一个泛型数组,可以给它赋值一个原生数据类型的ArraysList数组。
       ArrayList[] list = new ArrayList[5];//原生非泛型ArrayList集合。
       ArrayList<String>[] ListArr = list; //把list集合对象赋给泛型的数组引用,这种方式的弊端如下:
       
       ArryaList<Integer> intList = new ArrayList<>();//这里的ArrayList不是一个数组
       intList.add(100);
       list[0] = intList;
       String s = listArr[0].get(0);
       System.out.println(s); //报错ClassCastException
       
       //真要用只能通过以下的方法
       ArrayList<String>[] listArr = new ArrayList[5];
       ArrayList<String> strList = new ArrayList<>();
       strList.add("abc");
       listArr[0] = strList;
       String s = listArr[0].get(0);
       System.out.println(s);
  }
}

  • 一般使用泛型数组时都是拿着泛型类型去创建一个数组引用,而对象就不能采用泛型的了要采用原生的数组类型去创建数组对象赋给引用,操作的时候就可以拿引用去操作,原因很简单:泛型在编译器会有类型擦除,但数组在整个编译器都存在,二者在设计上就是有冲突的。


package com.heima.demo;

import java.lang.reflect.Array;
public class Fruit<T>{
   private T[] array = new T[3]; //报错,连T是什么类型的都不知道是没有办法创建出对象的。
   
   //怎么样去创建一个T类型的数组呢,如下:
   private T[] array; //先只声明一个这样的成员,在构造方法里去new它。
   public Fruit(Class<T> clz, int length){ //具体数据在创建Fruit对象的时候再指定
       //通过 Array.newInstance 创建泛型数组
       array = (T[])Array.newInstance(clz, length);
  }
   
   //封装一些方法对泛型数组进行使用,index指定哪个位置填充,item指定填充内容。
   public void put(int index, T item){
       array[index] = item; //item类型和泛型标识一致
  }
   
   //获取填充的数组元素
   public T get(int index){
       return array[index];
  }
   //拿到数组所有元素
   public T[] getArray(){
       return array;
  }
   
   public static void main(String[] args){
       Fruit<String> fruit = new Fruit<>(String.class, 3);
       fruit.put(0, "apple");
       fruit.put(0, "banana");
       fruit.put(0, "peach");
       
       System.out.println(Arrays.toString(fruit.getArray())); //Array有一个toString方法可以把Array类型对象的所有成员给转换成一个固定格式。
       String s1 = fruit.get(2);
       System.out.println(s1);
  }
}

  • 我们创建Fruit对象时必然会去调用构造方法,创建对象指定的是String类型,所以调用构造的时候就会传一个String类型的Class类文件和数组长度3,再由Array.newInstance创建一个String.class类型的数组。

  • 通过 T 动态的创建了一个具体类型的数组,Fruit这个类在代码的灵活性和共用性上更为强大。

  • 在开发的时候尽量不要使用泛型数组,使用泛型集合去代替泛型数组。

泛型和反射

  • 反射常用的泛型类

    • Class<T>

    • Constructor<T>


package com.heima.demo;

public class Person{
   private String name;
   
   public String getName(){
       return name;
  }
   
   public void setName(String name){
       this.name = name;
  }
}

package com.heima.demo;
import java.lang.reflect.InvocationTargetException;

/**
*泛型与反射
*/
public class Test11{
   public static void main(String[] args) throws Exception{
       //Class<Person> personClass = Person.class;
       //Constructor<Person> constructor = personClass.getConstructor();
       //Person person = constructor.newInstance();
       Class personClass = Person.class;
       Constructor constructor = personClass.getConstructor();
       Object o = constructor.newInstance();
  }
}

  • 不用泛型时,第二种方式在后续使用的时候还需要进行数据类型的转化,就不方便了。

posted on 2021-12-21 16:21  愿将过往均储藏  阅读(403)  评论(0)    收藏  举报

导航