java 泛型学习随笔

对于java 泛型 编译时处理,运行时擦除的特点理解

  • 对于编译时处理
    • 在使用泛型相关的类或方法时,如果声明时的类型和具体使用时的类型不一致则直接会编译不通过
  • 对于运行时擦除
    • 当在运行时对两个相同类型但泛型的具体类型不同时,在运行时实际都是操作的相同的类型,而不会体现出来泛型的的具体类型; 

    

public static void main(String[] args){
    
      List a = Collections.emptyList();
        List b = Collections.emptyList();
        // true , 对于不包含泛型初始化的list实际都是使用的相同的实例数据
        LOGGER.info(String.valueOf(a == b));

        List<String> a1 = Collections.emptyList();
        boolean v = a == a1;
        // true, 对于 a 和 a1 两个参数的类型实际是不同的,但在运行时实际是对于a和a1的类型实际都是相同的List类型; 可以通过 javap -v 类名 来查看编译后的class文件
        LOGGER.info(String.valueOf(v));

        List<Integer> b1 = Collections.emptyList();
        // 由于a1和b1的泛型的具体类型不一致,因此在编译时不会通过
//        boolean v1 = a1 == b1
}

  对于泛型的显式限定和隐式限定区别

 public static void castQuestion() {
        // 在执行实例化操作时,实际已经隐式限定了当前对象的类型
        // 在执行具体操作时,虽然根据变量的限定符显式定义,但在实际使用中就会抛出错误
        Container<StringBuilder> stringContainer = new Container("1");
        StringBuilder element = stringContainer.getElement();

    }

    // 自定义内部容器类,类型为泛型
    // 单界限操作: E extends CharSequence,这里限定了泛型的类型只能为CharSequence的子级
    public static class Container<E extends Serializable> {
        private E element;

        public Container(E element) {
            this.element = element;
            // 可以看到当前元素的实际类型
            System.out.println(element.getClass().getTypeName());
        }

        // 方法
        public E getElement() {
            return element;
        }

        public void setElement(E element) {
            this.element = element;
        }
    }

  可以看到在指定 new 时未显式指定对象元素类型,但通过调用有参构造方法实际已限定了当前对象的element元素类型;

  虽然对象变量显式限定了当前变量的泛型,对于操作方法实际是根据调用者的具体泛型类型进行限制,因此可以看到 "StringBuilder element = stringContainer.getElement();" 返回值类型为 StringBuilder;

  而由于对象中的实际类型为String类型,当将String类型强制赋值为Integer类型数据时,就会抛出ClassCastException

 

由于泛型存在编译时校验,运行时擦写的特点,因此为了保证运行时也提供泛型类型校验, 在Collections中提供了 checked*的工具类,在执行操作时保证了运行时的类型校验

    public void collectionGenericType() {
        List<Integer> integers = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
        // 由于泛型存在编译时校验,运行时擦写
        List noGenericTypeList = integers;
        System.out.println(noGenericTypeList == integers);
        // 虽然 noGenericTypeList 引用了 integers
        // 运行时泛型擦写  List<Integer> -> List<Object> -> List
        // 因此可以写入任意类型的数据
        noGenericTypeList.add("A");
        // 由于数据读取时需要进行类型转换(转换为泛型的指定类型)因此会抛出ClassCastException
//        integers.forEach(System.out::println);
        // 而对于noGenericTypeList由于没有泛型的约束,因此读取数据是都是按照Object类型处理
        noGenericTypeList.forEach(System.out::println);
        // 在转换时并没有执行类型检查因此支持直接转换
        List<Integer> castList = new ArrayList<>(noGenericTypeList);
        // 因此为了避免类型擦写导致的异常,因此需要使用包装类型工具类
        // 当转换为checkedList时并不会进行类型校验
        /**
         * Wrapper(装饰器)模式的使用
         * Collections.checked*接口弥补了 泛型运行时擦写的不足
         * 强类型: 编译时泛型强制类型检查,运行时利用Collections.checked*强类型检查
         */
        List<Integer> checkedList = Collections.checkedList(castList, Integer.TYPE);
        // 会生成新的数据
        System.out.println(checkedList == castList);

        noGenericTypeList = checkedList;
        // 对于checkedList在执行添加时,会执行类型校验,因此会直接抛出错误
        noGenericTypeList.add("B");
    }

  

// 当对一个类执行具体化泛型时
public class CustomStringList extends ArrayList<String> {
 public static void main(String[] args){
        
  /**
         * 泛型擦写指代的是运行时擦写,对于运行时存储的数据jvm都认为是Object对象,而非具体的类型
         * 对于 类似 class CustomStringList extends ArrayList<String> 这种定义方式,在定义类时就已经确定了具体的泛型,因此对于相应的定义在编译为class文件时就已经保存在文件中了
         */
        String typeName = CustomStringList.class.getGenericSuperclass().getTypeName();
        log.info("{}", typeName);// java.util.ArrayList<java.lang.String>
}
}

 

 

关于java 泛型定义中 extends 和 super 的区别

首先根据 extends 字面含义 继承 来理解, 其表示了当前泛型类型的上限类型;

而对于super 代表的含义为当前泛型类型的下限类型;

 

对于 extends 和 super 其真实的作用并不在运行时,而是编译时的语法校验; 

由于在运行时泛型擦除的特性,对于运行时泛型的类型统一都被替换为Object

 

/*
 * Copyright (c) 2020, guoxing, Co,. Ltd. All Rights Reserved
 */
package com.xingguo.generic;

import java.util.ArrayList;

/**
 * GenericTypeDemo
 *
 * @author guoxing
 * @date 2020/12/20 2:49 PM
 * @since
 */
public class GenericTypeDemo {
    public static void main(String[] args) {
        ArrayList<Fruit> fruits = new ArrayList<>();
        // 当前情况成功
        fruits.add(new Apple());

        ArrayList<Apple> apples = new ArrayList<>();
        apples.add(new Apple());
        fruits.addAll(apples);
        fruits.forEach(System.out::println);
        
        //由于编译时的语法限制,当前代码会抛出错误
//        fruits.addAll(new ArrayList<Object>());
    }
}

 

posted @ 2020-09-17 12:42  郭星  阅读(151)  评论(2)    收藏  举报