• Java泛型理解


    什么是泛型?

    我们都知道 Java 中有形参和实参之分,形参是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数,其本身没有确定的值。在调用函数时,实参将赋值给形参。

    而泛型是一种参数化的类型(可以理解为类型形参),它允许在定义类、接口时通过一个「标识」表示类中某个属性的类型或者是某个方法的返回值或参数的类型。这个类型参数将在使用时(例如,继承或实现这个接口、创建对象或调用方法时)确定(即实际传入的类型参数,也称为类型实参)。好处是增强代码的安全性。

    以 Java 中的集合为例,如果不指定泛型,那么传入集合中的元素就可以是任意类型的,这就可能带来一些问题,比如我取出集合中的某个元素后,可能要进行「类型转换」,但是如果不指定泛型,那么我是无法知道集合中的元素的什么类型的(默认是 Object 类型),就可能带来 ClassCastException 异常。

    泛型的使用场景?

    • 自定义通用返回结果 CommonResult 通过参数 T 可根据具体的返回类型动态指定结果的数据类型
    • DAO 层、自定义的通用泛型方法
    • 比较器(Comparable、Comparator)

    泛型的使用方式有哪几种?

    泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。

    泛型方法的一个例子:

    // 创建不同类型的数组:  
    Integer[] intArray = {1, 2, 3};  
    String[] stringArray = {"hello", "world"};  
    printArray(intArray);  
    printArray(stringArray);
    
    public static <E> void printArray(E[] inputArray) {  
            for (E element : inputArray) {  
                System.out.printf("%s ", element);  
            }  
            System.out.println();  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    个人认为使用泛型方法的好处有二:

    1. 允许方法传入多种类型的参数,提高了方法的可扩展性。
    2. 允许方法使用和类不同的泛型参数,比如上面的泛型方法使用的是 E,而该方法所在的类使用的可以是 T。

    什么是泛型擦除?为什么要擦除?

    泛型擦除就是指 Java 程序在编译期间所有的泛型信息都会被擦除,也就是「类型擦除」。

    类型擦除的主要过程如下:1.将所有的泛型参数用其最顶级的父类类型替换。2.移除所有的类型参数。

    比如编译器会在编译期间将泛型 T 擦除为 Object 类型,或将 T extends xxx 擦除为其限定类型 xxx。

    总结:泛型本质上还是「编译时行为」。为了保证引入泛型机制但不创建新的类型,减少 JVM 的运行开销,编译器会通过擦除将泛型类转换为一般类。

    举个例子来证明泛型是「编译时行为」:

    List<Integer> list = new ArrayList<>();  
    list.add(2);  
    // 1. 编译期间直接添加与泛型参数类型不同元素会报错,因为有「泛型校验」
    list.add("aa");  
    // 2. 运行期间通过反射添加却是可以的
    Class<? extends List> clazz = list.getClass();  
    Method add = clazz.getDeclaredMethod("add", Object.class);  
    add.invoke(list, "kl");  
    System.out.println(list);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    也正是由于泛型擦除的问题,下面的「方法重载会报错」:

    public static void test(List<String> list1) {  
        System.out.println(1);  
    }  
      
    public static void test(List<Integer> list1) {  
        System.out.println(1);  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    因为擦除后大家都是 List 了,默认里面存的就是 Object 类型的元素。

    通配符

    由于如果泛型类型只能是固定的,在某些场景下使用起来不够灵活。

    使用通配符可以解决泛型无法协变的问题。

    协变指的就是如果 Child 是 Parent 的子类,那么List 也应该是List 的子类,但是泛型是不支持的。

    无界通配符

    ListList 有区别吗?当然有!

    List list 表示 list 是持有某种特定类型的 List,但是不知道具体是哪种类型。因此,我们添加元素进去的时候会报错。

    List list 表示 list 是持有的元素的类型是 0bject,因此可以添加任何类型的对象,只不过编译器会有警告信息。

    List<?> list = new ArrayList<>( );
    list.add("sss";//报错
    List list2 = new ArrayList<>();
    list2.add("sss";//警告信息
    
    • 1
    • 2
    • 3
    • 4

    上/下边界通配符

    在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。

    上边界通配符 extends 要求传入的类型实参必须是指定类型或其子类。

    举个例子:

    // 限制必须是 Person 类或其子类
    <? extends Person>
    
    • 1
    • 2

    下边界通配符 super 则要求传入的类型实参必须是指定类型或其父类。

    // 限制必须是 Person 类或其父类
    <? super Person>
    
    • 1
    • 2

    使用 需要注意的点

    G 和 G 没有继承关系。

    image.png

    这块可能不太好理解,具体可以看宋红康老师 JavaSE 的视频

    泛型中的坑

    1.当泛型遇到 catch

    泛型的类型参数不能用在 Java 异常处理的 catch 语句中。因为异常处理是由 JVM 在运行时刻来进行的。由于类型信息被擦除,JVM 是无法区分两个异常类型MyExceptionMyException

    2.当泛型类内包含静态变量

    public class StaticTest{
        public static void main(String[] args){
            GT<Integer> gti = new GT<Integer>();
            gti.var=1;
            GT<String> gts = new GT<String>();
            gts.var=2;
            System.out.println(gti.var);
        }
    }
    class GT<T>{
        public static int var=0;
        public void nothing(T x){}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    以上代码输出结果为:2!

    由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,它们的静态变量是共享的。

  • 相关阅读:
    Java池化技术
    【GD32】05 - PWM 脉冲宽度调制
    async与await
    MySQL数据库 —— 常用语句
    dubbo漫谈(一)
    我要写整个中文互联网界最牛逼的JVM系列教程 | 「类加载子系统」章节:为什么需要用户自定义类加载器以及其具体实现
    通达OA的开发模式
    禁止ios和android用户选中文字
    IntelliJ IDEA 常用快捷键一览表
    【Linux学习】高并发服务器框架 线程池介绍+线程池封装
  • 原文地址:https://blog.csdn.net/weixin_43987408/article/details/133531635