Java泛型

 


泛型原理

什么是泛型&为什么引入泛型

public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(521);//添加 Integer 类型元素
        list.add("wan");//添加 String 类型元素
        list.add(true);//添加 Boolean 类型元素
        list.add('a');//添加 Character 类型元素
        Object item1 = list.get(0);//只能用 Object 接受元素
        list.forEach(item -> {
            //使用 item,这里的 item 类型是 Object,由于不知道 item 的确切类型,我们需要判断之后强转
            if (item instanceof Integer) {
                //执行业务...
            } else if (item instanceof String) {
                //执行业务...
            } else if (item instanceof Boolean) {
                //执行业务...
            } //继续判断类型...
        });
    }

JDK5 之前没有泛型时,声明的 List 集合默认是可以存储任意类型元素。开发的时候由于不知道集合中元素的确切类型,遍历的时候我们拿到的 item 其实是 Object 类型,如果要使用就必须强转,强转就必须得判断当前元素的具体类型,否则直接使用强转很可能会发生类型转换异常。这样就会让开发很不方便,每次都要额外做判断工作。

有了泛型的指定,我们声明的 list 就只能存储规定类型 String ,当存储其他类型的元素时编辑器就会直接给我们报错(可以在 IDEA 开发环境中看 add 方法提示参数类型就是 String),这样类型不匹配的问题就在编译时候就能检查出来,而不会在运行时才抛出异常。而且当我们进行遍历、获取元素等操作时,get 方法返回值就是 String 类型的。

总结泛型的好处

  • 编译期类型安全
  • 避免了强制类型转换运行时异常
  • 同一个类可以操作多种类型数据,代码复用

泛型类

ArrayList 是最典型的泛型类例子。 ArrayList 在定义的时候指定了一个泛型 ,并且下面的添加元素、获取元素等方法也都是对这个 E 进行操作。也可以在一个类中定义多个泛型参数,比如 HashMap。

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>

泛型方法

有时候开发中我们会有这样一种需求,根据方法传入的参数类型不同,返回不同的返回值类型。

public static <E, T> List<T> convertListToList(List<E> input, Class<T> clzz) {
        List<T> output = Lists.newArrayList();
        if (CollectionUtils.isNotEmpty(input)) {
            input.forEach(source -> {
                T target = BeanUtils.instantiate(clzz);
                BeanUtils.copyProperties(source, target);
                output.add(target);
            });
        }
        return output;
    }

无界泛型通配符

"?" 代表不确定的类型。泛型的上界下界:

IPage<? extends AppOrderResponse> //表示泛型最高类型是 AppOrderResponse,只能是它或它的子类
IPage<? super AppOrderResponse> //表示泛型最低类型是 AppOrderResponse。只能是它或它的父类

上面都是把无界通配符用在返回值,无界通配符也是可以用在方法参数上的。

使用上界参数

 public void test1(List<? extends AppOrderResponse> list){
        list.add(new AppOrderDispatchedResponse());//添加元素报错,因为我们传入的可能是 List<AppOrderWaitPaidResponse>
        list.add(new AppOrderResponse());//添加元素报错,因为我们传入的可能是 List<AppOrderWaitPaidResponse>
        list.add(null);//这是唯一可以添加的元素 null
        AppOrderResponse appOrderResponse = list.get(0);//接受元素类型为 AppOrderResponse
}

泛型上界参数在方法内只能读取,不能写入。

使用下界参数

public void test2(List<? super AppOrderResponse> list){
    list.add(new AppOrderResponse());//可以添加
    list.add(new AppOrderDispatchedResponse());//可以添加
    Object object = list.get(0);//接受元素的类型为 Object
}

泛型下界参数在方法内只能写入,不能读取。

泛型通配符 "?" 和 T、E、R、K、V 的区别

  • T、E、R、K、V 对于程序运行时没有区别,只是名字不同
  • ? 表示不确定的泛型类型
  • T (type) 表示具体的一个泛型类型
  • K V (key value) 分别代表 Map 中的键值 Key Value
  • E (element) 代表元素,例如 ArrayList 中的元素
  • T 用于定义泛型类和泛型方法
  • "?" 用于方法形参

泛型擦除

所谓的泛型擦除其实很简单,简单来说就是泛型只在编译时起作用,运行时泛型还是被当成 Object 来处理,示例代码

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("wan");//add 方法形参类型为 String
        String s = list.get(0);//get方法返回值类型为 String
        ArrayList<Integer> list2 = new ArrayList<>();
        System.out.println("list 和 list2 类型相同吗:" + (list.getClass() == list2.getClass()));//true 两个 ArrayList 是同一个类型的
        Method[] methods = list.getClass().getMethods();
        for (Method method : methods) {
            method.setAccessible(true);
            if (method.getName().equals("add")) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
                    for (Class<?> parameterType : parameterTypes) {
                        System.out.println("add(E e) 形参 E 的类型为:" + parameterType.getName());//泛型的参数 E 运行时是 Object 类型
                    }
                }
            } else if (method.getName().equals("get")) {
                Class<?> returnType = method.getReturnType();
                System.out.println("E get(int index) 的返回值 E 的类型为:" + returnType.getName());
            }
        }
    }

可以看到我们实例化 ArrayList 时虽然传入不同的泛型,但其实它们仍然还是同一个类型。对于 add 方法的形参和 get 方法的返回值,按道理说我们指定的泛型是 String 那么打印出来应该是 String 才对,但是这里运行时得到的却都是 Object,所以这就足以证明了,泛型在编译期起作用,运行时一律被擦除当做 Object 看待,这就是泛型擦除。

参考资料

  1. 一文带你搞懂 Java 泛型 - 知乎 (zhihu.com)
  2. Java 泛型擦除 - hongdada - 博客园 (cnblogs.com)
posted @   Yeahchen  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示

目录导航