在Java开发或刷题时能够遇到List<int[]> ans = new ArrayList<>()
这行代码能够完美通过编译并成功运行,但稍作修改,写成List<int> ans = new ArrayList<>()编译器就会报错。
同样是类型,int[]可以作为泛型参数,而int却不行,
一、核心:Java中万物皆对象,除了基本类型
Java泛型有一条铁律,泛型参数必须是引用类型,绝不能是基本数据类型。
Java的数据类型分两种:
- 基本数据类型
- 引用类型:类、接口、数组等,存储在堆内存中,变量保存的是指向堆内存的引用
数组本质上是一个对象,声明int[]时,虽然里面装的是基本类型int,但int[]本身是一个继承自Object的引用类型,拥有length属性,并重写了clone()等方法。
因此List<int[]>中的int[]完全符合对引用类型的要求,编译器自然会放行。
二、语法糖的魅力:钻石操作符<>与类型判断
new ArrayList<>()使用了Java7引入的钻石操作符。
在没有这个语法之前,需要写成冗长的List<int[]> ans = new ArrayList<int[]>();。
引入<>之后,Java编译器展现出了强大的类型判断能力,当编译器看到等号左侧的List<int[]>时,会自动推断出右侧的ArrayList的泛型参数也必须是int[]。
不仅让代码变得简洁,还避免了前后泛型类型不一致导致的潜在错误。
三、面向对象基石:多态与接口契约
体现了Java面向对象编程中的多态原则。
左侧面向接口编程,List是一个接口,
右侧是具体的实现类,ArrayList实现了List接口,提供了基于动态数组的底层实现。
在Java中,父类/接口类型的引用可以指向子类/实现类的对象,因此用List接收ArrayList的实例,是极其标准且推荐的编码规范。
底层探秘:ArrayList是如何存储int\[\]的
JDK中ArrayList的源码,发现:
java
// ArrayList的核心成员变量
transient Object[] elementData;
可以发现无论泛型写的是, , int\[\],底层用来存储数据的数组永远是Object[]
这是怎么做到的,归功于Java泛型的类型擦除机制。在编译阶段,泛型<int[]>会提供严格的类型检查;但在编译后的字节码中,泛型信息会被擦除,elementData退化成了普通的Object[],因为所有引用类型都可以向上转型为Object,所以ArrayList能够毫无障碍地将int[]存入底层的Object[]数组中。
为什么不能写List
因为int是基本类型,无法向上转型为Object,如果Java允许List<int>,在底层Object[]数组中就无法存储int值,会破坏Java类型系统的根基。
所以有包装类,Integer对象,List<Integer> nums = new ArrayList<>()