解析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中的泛型,其实是基于类型擦除机制来实现的。所以其实即使我们创建的泛型集合,也不是真正的泛型集合。第二点是由于数组的一个协变性,它的设计理念就与泛型相悖,所以泛型数组的安全性是不完全的。

相关推荐
程序员张329 分钟前
Maven编译和打包插件
java·spring boot·maven
ybq195133454311 小时前
Redis-主从复制-分布式系统
java·数据库·redis
weixin_472339462 小时前
高效处理大体积Excel文件的Java技术方案解析
java·开发语言·excel
小毛驴8502 小时前
Linux 后台启动java jar 程序 nohup java -jar
java·linux·jar
DKPT3 小时前
Java桥接模式实现方式与测试方法
java·笔记·学习·设计模式·桥接模式
好奇的菜鸟4 小时前
如何在IntelliJ IDEA中设置数据库连接全局共享
java·数据库·intellij-idea
DuelCode5 小时前
Windows VMWare Centos Docker部署Springboot 应用实现文件上传返回文件http链接
java·spring boot·mysql·nginx·docker·centos·mybatis
优创学社25 小时前
基于springboot的社区生鲜团购系统
java·spring boot·后端
幽络源小助理6 小时前
SpringBoot基于Mysql的商业辅助决策系统设计与实现
java·vue.js·spring boot·后端·mysql·spring
猴哥源码6 小时前
基于Java+springboot 的车险理赔信息管理系统
java·spring boot