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

相关推荐
gopher95113 分钟前
final,finally,finalize的区别
java·开发语言·jvm
Jason-河山11 分钟前
利用 Python 爬虫采集 1688商品详情
java·http
计算机源码社11 分钟前
分享一个餐饮连锁店点餐系统 餐馆食材采购系统Java、python、php三个版本(源码、调试、LW、开题、PPT)
java·python·php·毕业设计项目·计算机课程设计·计算机毕业设计源码·计算机毕业设计选题
Zww089115 分钟前
idea插件市场安装没反应
java·ide·intellij-idea
夜雨翦春韭17 分钟前
【代码随想录Day31】贪心算法Part05
java·数据结构·算法·leetcode·贪心算法
计算机学姐17 分钟前
基于微信小程序的调查问卷管理系统
java·vue.js·spring boot·mysql·微信小程序·小程序·mybatis
problc28 分钟前
Android 组件化利器:WMRouter 与 DRouter 的选择与实践
android·java
程序员南飞2 小时前
ps aux | grep smart_webrtc这条指令代表什么意思
java·linux·ubuntu·webrtc
弥琉撒到我2 小时前
微服务swagger解析部署使用全流程
java·微服务·架构·swagger
一颗花生米。3 小时前
深入理解JavaScript 的原型继承
java·开发语言·javascript·原型模式