简介

上一篇文章介绍了泛型的基本用法以及类型擦除的问题,现在来看看泛型和数组的关系。数组相比于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 中不允许直接创建泛型数组。本文分析了其中原因并且总结了一些创建泛型数组的方式。其中有部分个人的理解,如果错误希望大家指正。下一篇会总结通配符的使用。

最新文章

  1. IOS 2D游戏开发框架 SpriteKit--&gt;续(创建敌对精灵)
  2. UI--UIPickerView和UIDatePicker的总结
  3. IOS编程 图片缓存模块设计
  4. Canvas绘画功能(待补充)
  5. Shell教程3-Shell特殊变量
  6. 解决android锁屏或解锁后activity重启的问题
  7. Android----二维码开发
  8. C#处理四舍五入的问题
  9. CSS3 新增属性
  10. Hadoop的基本命令【转载】
  11. asp.net web编程开发将model键值对化
  12. Codeforces 339E
  13. mysqldump 使用说明
  14. Ceph和Openstack的cinder模块对接方法
  15. SQL 杂活
  16. HFS 轻量化 的文件服务器
  17. tp5阿里云短信发送
  18. python 关于文件夹的操作
  19. zookeeper开发
  20. POJ 1904 King&#39;s Quest(SCC的巧妙应用,思维题!!!,经典题)

热门文章

  1. PHP:车牌照合法性
  2. 第十八节:Scrapy爬虫框架之settings文件详解
  3. python 用 PIL 模块 画验证码
  4. xtu read problem training 4 B - Multiplication Puzzle
  5. 动态规划之最长递增子序列(LIS)
  6. 567. Permutation in String
  7. 找宝箱 (bfs)
  8. pandas中计算总体标准差
  9. Ubuntu 16.04安装基于nethogs衍生的网络监控软件(应用实时网速监控)
  10. 条款一:仔细区别pointers 和 reference