Java泛型擦除是边界处的动作(四)

一、编译期的泛型

’java泛型可能表示着没有任何意义的事物。
比如:下面的ArrayMaker 中的属性kind,事实上会被存储为 Class,没有任何参数。因此,
Array.newInstance() 并没有kind 蕴含的类型信息,不会产生具体的结果,会生成 unchecked call 的警告

1、数组泛型
public class ArrayMaker<T> {

   private Class<T> kind;

   public ArrayMaker(Class<T> kind) {
       this.kind = kind;
   }

   /**
    * 强转时提示了  unchecked call!
    *
    * 即使我们利用kind 指定了类型为  String,但运行时,kind 仍然只是被存储为Class ,没有任何参数。
    * Array.newInstance()也并没有 kind 拥有的 类型信息,不会产生具体的结果。
    *
    * Array.newInstance() 只是产生了一个具有 长度的数组,但是元素全部为null。
    *
    * 注意: 假如要在泛型里创建数组, Array.newInstance()是比较推荐的方式!!!
    */
   @SuppressWarnings("unchecked")
   public T[] create(int size) {
       return (T[]) Array.newInstance(kind, size);
   }

   public static void main(String[] args) {
       ArrayMaker<String> maker = new ArrayMaker<>(String.class);
       System.out.println(Arrays.toString(maker.create(10))); //[null, null, null, null, null, null, null, null, null, null]

       String[] strings = (String[]) (Array.newInstance(String.class, 2));
       System.out.println(Arrays.toString(strings));  // [null, null]
   }
}

2、容器泛型
class ListMaker<T> {

   List<T> create() {
       return new ArrayList<>();
   }

   public static void main(String[] args) {
       List<String> list1 = new ListMaker<String>().create(); // 无unchecked 警告
       List<String> list2 = new ListMaker().create();  // 有unchecked 警告
   }
}


class FilledListMaker<T> {

   List<T> create(T t, int n) {
       // 编译器其其实不知道create()中T的类型信息,但是在编译器可以确保放到list中的对象都是T类型
       List<T> list = new ArrayList<>();
       IntStream.range(0, n).forEach(e -> list.add(t));
       return list;
   }

   public static void main(String[] args) {
       FilledListMaker<String> filledListMaker = new FilledListMaker<>();
       List<String> list = filledListMaker.create("hello", 10);
       System.out.println(list);
   }
}

2、3其实说明:
即使编译器不知道有关 create() 中T的信息,但是仍旧可以在编译期确保容器中对象具有正确的类型。因此,即使擦除在方法或类内部移除了有关实际类型的信息,编译器可以确保在方法或类中使用的类型的内部一致性。换言之:泛型,将原本在运行期才暴露的错误提前到了编译期暴露出来。

二、泛型边界

方法体中移除了类型信息,在运行时对象进入、离开的地点就是边界–> 编译器在编译期执行类型检查并插入转型代码的地点

public class SimpleHolder {

 private Object object;

 public Object getObject() {
     return object;
 }

 public void setObject(Object object) {
     this.object = object;
 }

 public static void main(String[] args) {
     SimpleHolder simpleHolder = new SimpleHolder();
     simpleHolder.setObject("yes");
     String yes = (String) simpleHolder.getObject(); // getXX需要强转,从字节码角度看,就是在此接受检查的
     System.out.println(yes);
 }
}

class GenericHolder<T> {
 private T obj;

 public T getObj() {
     return obj;
 }

 public void setObj(T obj) {
     this.obj = obj;
 }

 public static void main(String[] args) {
     GenericHolder<String> holder = new GenericHolder<>();
     holder.setObj("no");
     String no = holder.getObj(); // 产生的字节码显示,这里也是有强转检查的
     System.out.println(no);
 }
}

实际上,这里两个类型产生的字节码是一致的。对进入set()的检查是不需要的,编译器会进行。但从get()返回的值的强转却是需要的,只不过:在 SimpleHolder,强转需要手动进行,而在GenericHolder中 强转是由编译器自动插入的。

可见:泛型中所有动作都是发生在边界处—–对传进来的值进行额外编译期检查,并插入对传递出去的值的转型。这里的边界就是指的发生 “传进来”(set)、 “传出去“”(get)动作的地方。注意:不要泛型的 继承 混淆。

猜你喜欢

转载自blog.csdn.net/qq_30118563/article/details/82121127
今日推荐