解析Java的数组不支持泛型的原因

前言

  在 Java 中,泛型提供了一种在编译时期检查和强制执行类型安全的机制,以增加代码的可读性和重用性。

  但是在使用泛型中我们会发现 Java 数组不支持创建泛型数组,如下代码创建一个泛型数组

Java 复制代码
List<String>[] strList = new ArrayList<String>[10];//编译错误,非法创建

编译器会报错提示: Cannot create a generic array of ArrayList

  接下来分析一下为什么 Java 数组为什么不支持泛型。

1、什么是泛型?

  Java 泛型(generics)是 JDK 5 中引入的一个新特性。好处是在编译时能够检查类型安全性,提高代码的重用性。看下面的 demo:

Java 复制代码
    // 1、在编译时能够检查类型安全性。
    ArrayList list1 = new ArrayList();
        list1.add("abcdefg");
    list1.add(123456);
    ArrayList<String> list2 = new ArrayList<>();
    list2.add("abcdefg");
    list2.add(123456);//编译错误
    //2、提高重用性
    private static float add(float a, float b) {
        return a + b;
    }
    private static double add(double a, double b) {
        return a + b;
    }
    //泛型只需要一个方法
    private static <T extends Number> double add(T a, T b) {
        return a.doubleValue() + b.doubleValue();
    }

  泛型的本质是参数化类型,即给类型指定一个参数,然后在使用时再指定此参数具体的值,那样这个类型就可以在使用时决定了。

2、泛型数组的类型安全

Java 复制代码
List<String>[] strList = new ArrayList<String>[10];//编译错误,非法创建
List<String>[] strList2 = new ArrayList[10];//ok
strList2[0] = new ArrayList<String>();//ok
strList2[1] = "";//编译错误
strList2[2] = new ArrayList<Integer>();//编译错误

  从上面代码可以发现,编译器不允许创建带泛型的数组实例,但允许数组引用类型中出现泛型

  我们尝试往数组strList2赋值一些元素,第一个我们new ArrayList<String>,把它放进数组没有问题。第二和第三个会提示编译错误,编译器会告诉我们它所期望的类型是List<String>。所以在这个数组里元素只能是List,并且它的泛型是String这样的元素才能够通过编译。

  所以我们可以发现,对于数组的引用类型,也就是等号左边上的范型来说,作用是在编译期去保证数组的类型安全。

数组的协变关系

scala 复制代码
    class Fruit{ }
    class Apple extends Fruit{ }
    class Orange extends Fruit{ }

    Fruit[] array = new Apple[10];
        if (array instanceof Apple[]) {//true
        System.out.println("array instanceof Apple[]");
    }
        if (array instanceof Orange[]) {//false
        System.out.println("array instanceof Orange[]");
    }
        if (array instanceof Fruit[]) {//true
        System.out.println("array instanceof Fruit[]");
    }
        if (array instanceof Object[]) {//true
        System.out.println("array instanceof Object[]");
    }

  上述代码我们的引用是 Fruit 类型的数组 array,但是我们创建的却是 Apple 类型的数组。但是编译器是能够通过编译的。说明 Fruit 如果是 Apple 的父类的话,那么 Fruit 类型的数组就同样是 Apple 类型数组的父类型。

  运行上述代码我们可以发现,除了 Orange 类型的数组以外,其他的都是能够返回 true。

  这是因为 Java 中的数组是协变的,Java 中的协变是指一个数据类型能够自动转换为其子类型,这意味着数组类型的继承关系与其元素类型的继承关系是一致的。

3、泛型列表的类型安全

Java 复制代码
ArrayList<Fruit> fruits = new ArrayList<Apple>();//编译
    
ArrayList<? extends Fruit> fruits2 = new ArrayList<Apple>();//支持协变的形式 ok

  对于泛型集合来说,我们以ArrayList为例,当我们把一个Apple类型的ArrayList赋值给一个Fruit类型的引用的时候,会发现编译器是不允许我们这么做。

  这就是泛型的一个设计原则。在泛型里面,Apple类型的ArrayList 和 Fruit类型的ArrayList,本质上是没有任何关系的独立的两个类型。

  只有像下面那种写法,把泛型改成这种支持协变的形式,编译器才能够通过编译。所以泛型本身其实默认是具有不变性的。这样做是就是为了类型安全。

  假设说泛型列表也是协变的,就像我们下面这段代码一样。假设这段代码是能够通过编译的,那么就会发生我们能够把Orange放入到一个ArrayList<Apple>中的这种情况发生。这种情况会导致类型安全问题发生。

Java 复制代码
//假设
ArrayList<Fruit> fruits = new ArrayList<Apple>();
fruits.add (new Orange());

4、破坏数组的类型安全

Java 复制代码
List<String>[] strList2 = new ArrayList[10];
//破坏类型安全
Object[] objects = strList2;//String[]是Object[]的子类
objects[1] = "";

  Object数组是所有数组的父类型,我们可以把任意的数组上转成 Object\[\]。我们就可以把任意类型的值存入到这个数组中了。这段代码从语法的角度上来说是没有任何问题的。编译期不会报错,直到运行期才会抛出一个 java.lang.ArrayStoreException,表示我们往数组里放入了一个错误的类型。对于泛型列表来说,由于泛型默认是不变的,所以在编译期就能杜绝这样的问题发生。

  这个问题的起因是因为Java早期设计数组,后来又引入了泛型。这两套东西在设计理念上是不兼容的。在没有泛型的时代,Java中不管是数组还是集合,其实都不是类型安全的。所以数组当初被设计成协变是没有问题的。但是到了泛型的时代,协变就变成了数组的一个安全的隐患。所以即使数组能够完全支持泛型,但是由于它的协变性在编译期也无法完全的去保证它的类型安全。这也是java无法去创建泛型数组的一个最重要的一个原因。

总结

  Java的数组为什么不支持泛型?首先这个说法是不准确的。

  泛型能在编译期一定程度的去保证数组的类型安全。Java中的所谓的不支持,只是不支持去创建泛型类型的数组。我们知道其实java中的泛型,其实是基于类型擦除机制来实现的。所以其实即使我们创建的泛型集合,也不是真正的泛型集合。第二点是由于数组的一个协变性,它的设计理念就与泛型相悖,所以泛型数组的安全性是不完全的。

相关推荐
程序猿_极客3 分钟前
【免费】分享一套优质的基于SSM的校园失物招领系统的设计与实现,源码+文档+视频详解(讲解)
java·mysql·ssm·课程设计·失物招领系统
白远山39 分钟前
上海24小时自助健身房解决方案实战指南与经验分享
java·数据库·架构·需求分析
码兄科技1 小时前
24小时自助健身房系统软件开发实战指南:功能设计与部署方案
java·spring boot·uni-app
程序猿_极客1 小时前
【免费】分享一套优质的基于SpringBoot的扶贫助农管理系统(详解)
java·springboot·课程设计·计算机项目
2603_965148111 小时前
AI+API选品:下一代智能商务助手雏形已现
java·大数据·数据库·人工智能·数据挖掘
wuminyu1 小时前
HotSpot的轻量锁与膨胀后的ObjectMonitor状态重建原理
java·linux·c语言·jvm·c++
CoderYanger1 小时前
A.每日一题:835. 图像重叠
java·开发语言·程序人生·leetcode·面试·职场和发展·学习方法
北冥有鱼被烹1 小时前
perftest 之 ib_write_bw --use-null-mr 参数详解:用 NULL MR 做 RDMA 带宽测试
java
2601_963870211 小时前
基于SSM的特产代购系统
java·前端