简介

上一篇文章介绍了泛型的基本用法以及类型擦除的问题,现在来看看泛型和数组的关系。数组相比于Java 类库中的容器类是比较特殊的,主要体现在三个方面:

  1. 数组创建后大小便固定,但效率更高

  2. 数组能追踪它内部保存的元素的具体类型,插入的元素类型会在编译期得到检查

  3. 数组可以持有原始类型 ( 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 泛型 五:泛型与数组的更多相关文章

  1. Java基础之十五 泛型

    第十五章 泛型 一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义类型.如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大. 在面对对象编程语言中,多态算是一种泛化机 ...

  2. Java中创建泛型数组

    Java中创建泛型数组 使用泛型时,我想很多人肯定尝试过如下的代码,去创建一个泛型数组 T[] array = new T[]; 当我们写出这样的代码时编译器会报Cannot create a gen ...

  3. Java开发知识之Java中的泛型

    Java开发知识之Java中的泛型 一丶简介什么是泛型. 泛型就是指泛指任何数据类型. 就是把数据类型用泛型替代了. 这样是可以的. 二丶Java中的泛型 Java中,所有类的父类都是Object类. ...

  4. Java核心技术梳理-泛型

    一.引言 在学习集合的时候我们会发现一个问题,将一个对象丢到集合中后,集合并不记住对象的类型,统统都当做Object处理,这样我们取出来再使用时就得强制转换类型,导致代码臃肿,而且加入集合时都是以Ob ...

  5. JAVA学习之泛型

    ArrayList<E>类定义和ArrayList<Integer>类引用中涉及的术语:1.整个ArrayList<E>称为泛型类型 2.ArrayList< ...

  6. 深入理解什么是Java泛型?泛型怎么使用?【纯转】

    本篇文章给大家带来的内容是介绍深入理解什么是Java泛型?泛型怎么使用?有一定的参考价值,有需要的朋友可以参考一下,希望对你们有所助. 一.什么是泛型 “泛型” 意味着编写的代码可以被不同类型的对象所 ...

  7. 深入分析Java反射(三)-泛型

    前提 Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行 ...

  8. 从一知半解到揭晓Java高级语法—泛型

    目录 前言 探讨 泛型解决了什么问题? 扩展 引入泛型 什么是泛型? 泛型类 泛型接口 泛型方法 类型擦除 擦除的问题 边界 通配符 上界通配符 下界通配符 通配符和向上转型 泛型约束 实践总结 泛型 ...

  9. Java中的泛型 (上) - 基本概念和原理

    本节我们主要来介绍泛型的基本概念和原理 后续章节我们会介绍各种容器类,容器类可以说是日常程序开发中天天用到的,没有容器类,难以想象能开发什么真正有用的程序.而容器类是基于泛型的,不理解泛型,我们就难以 ...

  10. java中的泛型的使用与理解

    什么是泛型? 泛型是程序设计语言的一种特性.允许程序员在强类型程序设计语言中编写 体验泛型代码时定义一些可变部份,那些部份在使用前必须作出指明.各种程序设计语言和其编译器.运行环境对泛型的支持均不一样 ...

随机推荐

  1. Python:os模块 time模块

    1.os os.popen('cmd').read() 执行某个系统命令,:输出命令结果 os.getcwd()  获取当前操作目录 os.makedirs(r"C:\a\b\c" ...

  2. 2018/08/23 cstring中memset()函数的运用

    好多东西其实以前已经查过了,然后当时理解的还行,可是过段时间没用有些又会忘记,然后又去找资料又查,浪费了不少的时间和精力,所以,我,曾国强,今天起,要好好做笔记了! 今天复习第一个知识点,为什么要叫复 ...

  3. CSU1217

    就跟数字出现奇数次道理是一样的,将一个数转化为2进制后找出现奇数次个1的位置,最后将其输出来便是出现奇数次的数 #include <cstdio> int main() { int n,a ...

  4. 新vim配置文件

    "******************************************************特殊设置************************************ ...

  5. Qmake 工具编译调试

    Qmake 工具编译调试 2015年4月9日星期四 18:38:06 1. 确定qmaek 路径 [root@roger ~]# which qmake /usr/lib/qt-3.3/bin/qma ...

  6. nyoj_176_整数划分(二)_201404261715

    整数划分(二) 时间限制:1000 ms  |  内存限制:65535 KB 难度:3   描述 把一个正整数m分成n个正整数的和,有多少种分法? 例:把5分成3个正正数的和,有两种分法: 1 1 3 ...

  7. codeforces 691D(数据结构)

    D. Swaps in Permutation time limit per test 5 seconds memory limit per test 256 megabytes input stan ...

  8. python类变量以及应用场景

    类变量是python 中class 的变量,区别于实例的变量.我们通过一些例子具体了解一下 先看下面的例子 >>> class Demo(object): ... v1 = 1 .. ...

  9. 模拟用户点击弹出新页面不会被浏览器拦截_javascript技巧

    原文:http://www.html5cn.com.cn/article/zxzx/3195.html 相信用过window.open的小伙伴们都遇到过被浏览器拦截导致页面无法弹出的情况:我们换下思路 ...

  10. 【转】使用Python中HTTPParser模块进行简单的html解析

    http://www.cnblogs.com/coser/archive/2012/01/09/2317076.html