Java 泛型总结(二):泛型与数组
发布时间 - 2026-01-11 00:16:55 点击率:次简介

上一篇文章介绍了泛型的基本用法以及类型擦除的问题,现在来看看泛型和数组的关系。数组相比于Java 类库中的容器类是比较特殊的,主要体现在三个方面:
- 数组创建后大小便固定,但效率更高
- 数组能追踪它内部保存的元素的具体类型,插入的元素类型会在编译期得到检查
- 数组可以持有原始类型 ( int,float等 ),不过有了自动装箱,容器类看上去也能持有原始类型了
那么当数组遇到泛型会怎样? 能否创建泛型数组呢?这是这篇文章的主要内容。
这个系列的另外两篇文章:
- Java 泛型总结(一):基本用法与类型擦除
- Java 泛型总结(三):通配符的使用
泛型数组
如何创建泛型数组
如果有一个类如下:
class Generic<T> {
}
如果要创建一个泛型数组,应该是这样: Generic<Integer> ga = new Generic<Integer>[] 不过行代码会报错,也就是说不能直接创建泛型数组。
那么如果要使用泛型数组怎么办?一种方案是使用 ArrayList,比如下面的例子:
public class ListOfGenerics<T> {
private List<T> array = new ArrayList<T>();
public void add(T item) { array.add(item); }
public T get(int index) { return array.get(index); }
}
如何创建真正的泛型数组呢?我们不能直接创建,但可以定义泛型数组的引用。比如:
public class ArrayOfGenericReference {
static Generic<Integer>[] gia;
}
gia 是一个指向泛型数组的引用,这段代码可以通过编译。但是,我们并不能创建这个确切类型的数组,也就是不能使用 new Generic<Integer>[] 具体参见下面的例子:
public class ArrayOfGeneric {
static final int SIZE = 100;
static Generic<Integer>[] gia;
@SuppressWarnings("unchecked")
public static void main(String[] args) {
// Compiles; produces ClassCastException:
//! gia = (Generic<Integer>[])new Object[SIZE];
// Runtime type is the raw (erased) type:
gia = (Generic<Integer>[])new Generic[SIZE];
System.out.println(gia.getClass().getSimpleName());
gia[0] = new Generic<Integer>();
//! gia[1] = new Object(); // Compile-time error
// Discovers type mismatch at compile time:
//! gia[2] = new Generic<Double>();
Generic<Integer> g = gia[0];
}
} /*输出:
Generic[]
*///:~
数组能追踪元素的实际类型,这个类型是在数组创建的时候建立的。上面被注释掉的一行代码: gia = (Generic<Integer>[])new Object[SIZE],数组在创建的时候是一个 Object 数组,如果转型便会报错。成功创建泛型数组的唯一方式是创建一个类型擦除的数组,然后转型,如代码: gia = (Generic<Integer>[])new Generic[SIZE],gia 的 Class 对象输出的名字是 Generic[]。
我个人的理解是:由于类型擦除,所以 Generic<Integer> 相当于初始类型 Generic,那么 gia = (Generic<Integer>[])new Generic[SIZE] 中的转型其实还是转型为 Generic[],看上去像没转,但是多了编译器对参数的检查和自动转型,向数组插入 new Object()和 new Generic<Double>()均会报错,而 gia[0] 取出给 Generic<Integer> 也不需要我们手动转型。
使用 T[] array
上面的例子中,元素的类型是泛型类。下面看一个元素本身类型是泛型参数的例子:
public class GenericArray<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArray(int sz) {
array = (T[])new Object[sz]; // 创建泛型数组
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) { return array[index]; }
// Method that exposes the underlying representation:
public T[] rep() { return array; } //返回数组 会报错
public static void main(String[] args) {
GenericArray<Integer> gai =
new GenericArray<Integer>(10);
// This causes a ClassCastException:
//! Integer[] ia = gai.rep();
// This is OK:
Object[] oa = gai.rep();
}
}
在上面的代码中,泛型数组的创建是创建一个 Object 数组,然后转型为 T[]。但数组实际的类型还是 Object[]。在调用 rep()方法的时候,就报 ClassCastException 异常了,因为 Object[] 无法转型为 Integer[]。
那创建泛型数组的代码 array = (T[])new Object[sz] 为什么不会报错呢?我的理解和前面介绍的类似,由于类型擦除,相当于转型为 Object[],看上去就是没转,但是多了编译器的参数检查和自动转型。而如果把泛型参数改成 <T extends Integer> ,那么因为类型是擦除到第一个边界,所以 array = (T[])new Object[sz] 中相当于转型为 Integer[],这应该会报错。下面是实验的代码:
public class GenericArray<T extends Integer> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArray(int sz) {
array = (T[])new Object[sz]; // 创建泛型数组
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) { return array[index]; }
// Method that exposes the underlying representation:
public T[] rep() { return array; } //返回数组 会报错
public static void main(String[] args) {
GenericArray<Integer> gai =
new GenericArray<Integer>(10);
// This causes a ClassCastException:
//! Integer[] ia = gai.rep();
// This is OK:
Object[] oa = gai.rep();
}
}
相比于原始的版本,上面的代码只修改了第一行,把 <T> 改成了 <T extends Integer> 那么不用调用 rep(),在创建泛型数组的时候就会报错。下面是运行结果:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; at GenericArray.<init>(GenericArray.java:15)
使用 Object[] array
由于擦除,运行期的数组类型只能是 Object[],如果我们立即把它转型为 T[],那么在编译期就失去了数组的实际类型,编译器也许无法发现潜在的错误。因此,更好的办法是在内部最好使用 Object[] 数组,在取出元素的时候再转型。看下面的例子:
public class GenericArray2<T> {
private Object[] array;
public GenericArray2(int sz) {
array = new Object[sz];
}
public void put(int index, T item) {
array[index] = item;
}
@SuppressWarnings("unchecked")
public T get(int index) { return (T)array[index]; }
@SuppressWarnings("unchecked")
public T[] rep() {
return (T[])array; // Warning: unchecked cast
}
public static void main(String[] args) {
GenericArray2<Integer> gai =
new GenericArray2<Integer>(10);
for(int i = 0; i < 10; i ++)
gai.put(i, i);
for(int i = 0; i < 10; i ++)
System.out.print(gai.get(i) + " ");
System.out.println();
try {
Integer[] ia = gai.rep();
} catch(Exception e) { System.out.println(e); }
}
} /* Output: (Sample)
0 1 2 3 4 5 6 7 8 9
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
*///:~
现在内部数组的呈现不是 T[] 而是 Object[],当 get() 被调用的时候数组的元素被转型为 T,这正是元素的实际类型。不过调用 rep() 还是会报错, 因为数组的实际类型依然是Object[],终究不能转换为其它类型。使用 Object[] 代替 T[] 的好处是让我们不会忘记数组运行期的实际类型,以至于不小心引入错误。
使用类型标识
其实使用 Class 对象作为类型标识是更好的设计:
public class GenericArrayWithTypeToken<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArrayWithTypeToken(Class<T> type, int sz) {
array = (T[])Array.newInstance(type, sz);
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) { return array[index]; }
// Expose the underlying representation:
public T[] rep() { return array; }
public static void main(String[] args) {
GenericArrayWithTypeToken<Integer> gai =
new GenericArrayWithTypeToken<Integer>(
Integer.class, 10);
// This now works:
Integer[] ia = gai.rep();
}
}
在构造器中传入了 Class<T> 对象,通过 Array.newInstance(type, sz) 创建一个数组,这个方法会用参数中的 Class 对象作为数组元素的组件类型。这样创建出的数组的元素类型便不再是 Object,而是 T。这个方法返回 Object 对象,需要把它转型为数组。不过其他操作都不需要转型了,包括 rep() 方法,因为数组的实际类型与 T[] 是一致的。这是比较推荐的创建泛型数组的方法。
总结
数组与泛型的关系还是有点复杂的,Java 中不允许直接创建泛型数组。本文分析了其中原因并且总结了一些创建泛型数组的方式。其中有部分个人的理解,如果错误希望大家指正。下一篇会总结通配符的使用。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!
# java
# 泛型数组
# 创建泛型数组
# 泛型与数组
# Java封装数组实现包含、搜索和删除元素操作详解
# Java封装数组实现在数组中查询元素和修改元素操作示例
# Java封装数组之添加元素操作实例分析
# 使用java数组 封装自己的数组操作示例
# java数组、泛型、集合在多态中的使用及对比
# java 用泛型参数类型构造数组详解及实例
# JAVA得到数组中最大值和最小值的简单实例
# JavaScrip数组删除特定元素的几种方法总结
# Java中高效的判断数组中某个元素是否存在详解
# java中数组的定义及使用方法(推荐)
# Java封装数组之改进为泛型数组操作详解
# 报错
# 擦除
# 创建一个
# 是一个
# 这是
# 是在
# 把它
# 就会
# 也不
# 都不
# 让我们
# 是这样
# 也能
# 会在
# 这段
# 上一
# 可以通过
# 更高
# 来看看
# 希望大家
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
高端建站如何打造兼具美学与转化的品牌官网?
Laravel怎么实现搜索高亮功能_Laravel结合Scout与Algolia全文检索【实战】
Laravel如何使用Passport实现OAuth2?(完整配置步骤)
微信小程序 配置文件详细介绍
如何用JavaScript实现文本编辑器_光标和选区怎么处理
JavaScript如何实现错误处理_try...catch如何捕获异常?
制作网站软件推荐手机版,如何制作属于自己的手机网站app应用?
php嵌入式断网后怎么恢复_php检测网络重连并恢复硬件控制【操作】
电视网站制作tvbox接口,云海电视怎样自定义添加电视源?
中山网站推广排名,中山信息港登录入口?
Laravel怎么使用Blade模板引擎_Laravel模板继承与Component组件复用【手册】
如何在Windows 2008云服务器安全搭建网站?
Python文件操作最佳实践_稳定性说明【指导】
Laravel如何优雅地处理服务层_在Laravel中使用Service层和Repository层
小视频制作网站有哪些,有什么看国内小视频的网站,求推荐?
如何在阿里云香港服务器快速搭建网站?
Laravel如何使用Contracts(契约)进行编程_Laravel契约接口与依赖反转
如何基于云服务器快速搭建网站及云盘系统?
Laravel怎么实现验证码(Captcha)功能
企业在线网站设计制作流程,想建设一个属于自己的企业网站,该如何去做?
Laravel如何升级到最新的版本_Laravel版本升级流程与兼容性处理
网站建设要注意的标准 促进网站用户好感度!
Laravel中Service Container是做什么的_Laravel服务容器与依赖注入核心概念解析
Laravel如何实现登录错误次数限制_Laravel自带LoginThrottles限流配置【方法】
详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)
*服务器网站为何频现安全漏洞?
如何在IIS服务器上快速部署高效网站?
如何在企业微信快速生成手机电脑官网?
Edge浏览器如何截图和滚动截图_微软Edge网页捕获功能使用教程【技巧】
Laravel如何实现多表关联模型定义_Laravel多对多关系及中间表数据存取【方法】
如何为不同团队 ID 动态生成多个独立按钮
高防服务器租用指南:配置选择与快速部署攻略
Edge浏览器怎么启用睡眠标签页_节省电脑内存占用优化技巧
如何用AI帮你把自己的生活经历写成一个有趣的故事?
如何快速搭建高效WAP手机网站?
Laravel集合Collection怎么用_Laravel集合常用函数详解
Laravel API路由如何设计_Laravel构建RESTful API的路由最佳实践
东莞专业网站制作公司有哪些,东莞招聘网站哪个好?
青岛网站建设如何选择本地服务器?
EditPlus中的正则表达式 实战(4)
Laravel怎么在Blade中安全地输出原始HTML内容
如何实现建站之星域名转发设置?
ai格式如何转html_将AI设计稿转换为HTML页面流程【页面】
浅述节点的创建及常见功能的实现
laravel怎么用DB facade执行原生SQL查询_laravel DB facade原生SQL执行方法
Laravel如何升级到最新版本?(升级指南和步骤)
如何正确选择百度移动适配建站域名?
phpredis提高消息队列的实时性方法(推荐)
如何续费美橙建站之星域名及服务?
Laravel任务队列怎么用_Laravel Queues异步处理任务提升应用性能

