Java 泛型 五:泛型与数组
简介
上一篇文章介绍了泛型的基本用法以及类型擦除的问题,现在来看看泛型和数组的关系。数组相比于Java 类库中的容器类是比较特殊的,主要体现在三个方面:
数组创建后大小便固定,但效率更高
数组能追踪它内部保存的元素的具体类型,插入的元素类型会在编译期得到检查
数组可以持有原始类型 ( int,float等 ),不过有了自动装箱,容器类看上去也能持有原始类型了
那么当数组遇到泛型会怎样? 能否创建泛型数组呢?这是这篇文章的主要内容。
这个系列的另外两篇文章:
泛型数组
如何创建泛型数组
如果有一个类如下:
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中创建泛型数组 使用泛型时,我想很多人肯定尝试过如下的代码,去创建一个泛型数组 T[] array = new T[]; 当我们写出这样的代码时编译器会报Cannot create a gen ...
- Java开发知识之Java中的泛型
Java开发知识之Java中的泛型 一丶简介什么是泛型. 泛型就是指泛指任何数据类型. 就是把数据类型用泛型替代了. 这样是可以的. 二丶Java中的泛型 Java中,所有类的父类都是Object类. ...
- Java核心技术梳理-泛型
一.引言 在学习集合的时候我们会发现一个问题,将一个对象丢到集合中后,集合并不记住对象的类型,统统都当做Object处理,这样我们取出来再使用时就得强制转换类型,导致代码臃肿,而且加入集合时都是以Ob ...
- JAVA学习之泛型
ArrayList<E>类定义和ArrayList<Integer>类引用中涉及的术语:1.整个ArrayList<E>称为泛型类型 2.ArrayList< ...
- 深入理解什么是Java泛型?泛型怎么使用?【纯转】
本篇文章给大家带来的内容是介绍深入理解什么是Java泛型?泛型怎么使用?有一定的参考价值,有需要的朋友可以参考一下,希望对你们有所助. 一.什么是泛型 “泛型” 意味着编写的代码可以被不同类型的对象所 ...
- 深入分析Java反射(三)-泛型
前提 Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行 ...
- 从一知半解到揭晓Java高级语法—泛型
目录 前言 探讨 泛型解决了什么问题? 扩展 引入泛型 什么是泛型? 泛型类 泛型接口 泛型方法 类型擦除 擦除的问题 边界 通配符 上界通配符 下界通配符 通配符和向上转型 泛型约束 实践总结 泛型 ...
- Java中的泛型 (上) - 基本概念和原理
本节我们主要来介绍泛型的基本概念和原理 后续章节我们会介绍各种容器类,容器类可以说是日常程序开发中天天用到的,没有容器类,难以想象能开发什么真正有用的程序.而容器类是基于泛型的,不理解泛型,我们就难以 ...
- java中的泛型的使用与理解
什么是泛型? 泛型是程序设计语言的一种特性.允许程序员在强类型程序设计语言中编写 体验泛型代码时定义一些可变部份,那些部份在使用前必须作出指明.各种程序设计语言和其编译器.运行环境对泛型的支持均不一样 ...
随机推荐
- DAG模型(矩形嵌套)
推荐在线例题:http://acm.nyist.net/JudgeOnline/problem.php?pid=16 题摘: 矩形嵌套 时间限制:3000 ms | 内存限制:65535 KB 难 ...
- 洛谷 P2285 BZOJ 1207 [HNOI2004]打鼹鼠
题目描述 鼹鼠是一种很喜欢挖洞的动物,但每过一定的时间,它还是喜欢把头探出到地面上来透透气的.根据这个特点阿牛编写了一个打鼹鼠的游戏:在一个n*n的网格中,在某些时刻鼹鼠会在某一个网格探出头来透透气. ...
- <<编程之美>> -- 队列中取最大值操作的问题
不得不说编程之美是一本好书,虽然很多题目在做acm中的过程中遇到过,不过还是有很多值得思考的地方 这是今天在编程之美上看到的一个问题,对于栈转化成队列的一个思考 平时都太过依赖c++内函数库中的栈和队 ...
- android 上AES解密是报错javax.crypto.BadPaddingException: pad block corrupted
网上看到两种方法: 1.SecretKeySpec skeySpec = new SecretKeySpec(getRawKey(key), "AES"); private sta ...
- nyoj_205_求余数_201404271630
求余数 时间限制:1000 ms | 内存限制:65535 KB 难度:3 描述 现在给你一个自然数n,它的位数小于等于一百万,现在你要做的就是求出这个数除10003之后的余数 输入 第一 ...
- Core java for impatient 笔记 ch8 流
流stream 使用了数据视图,让你可以在比集合更高的概念上指定操作使用流,你只需要将操作的调度留给实现,例如,假设你要计算某个属性的平均值,你只需要指定数据源和属性,然后流类库会优化计算,比如使用多 ...
- csu1364 Interview
对拍了一波才找到的错误,此题我用的是二分答案加倍增查询,实际上query那里我觉得仍然有缺陷,因为每一次我的查找还是在循环找到一个k使得x+2^k <= y,而错的地方也正在此地,一开始没有判断 ...
- c内存分配(转)
图示 C内存分配 程序代码区 存放函数体的二进制代码 全局数据区 全局变量和静态变量的存储是放在一起的.初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域. ...
- Ubuntu 16.04安装Markdown编辑器MarkMyWords
继上一篇文章http://www.cnblogs.com/EasonJim/p/7119345.html中使用Sublime Text 3进行Markdown的编辑,总觉得操作上比较繁琐,现在推荐使用 ...
- 在 Oracle Linux 上使用 DTrace
作者:Richard Friedman 简要介绍适用于 Oracle Linux 的 DTrace 探测器和提供程序,以及与 Oracle Solaris 中 DTrace 探测器和提供程序的区别.还 ...