在将ArrayList等Collection转为数组时,函数的返回值并不是泛型类型的数组,而是Object[]。刚好最近翻了一遍《java核心技术》,以及参考《Think in Java》,写写为什么没有直接返回对应类型的数组,以及Java泛型中类型擦除的处理方式。

主要涉及:

ArrayList的toArray函数使用

为什么不直接定义函数 T[] toArray()

泛型数组的创建的两种常用方法

在泛型中创建具体的类实例

(部分代码没有运行过)

ArrayList的toArray函数使用

将ArrayList转为数组,提供了两个函数

Object[] toArray();

T[] toArray(T[] a);

// 后面考虑一个Integer类型的ArrayListArrayList aa = new ArrayList<>();

aa.add(1);

aa.add(3);

Object[] toArray();

第一个函数是直接将ArrayList转换成Object的数组,可以用Object[] bb = aa.toArray(),在具体使用时对每个对象进行强制类型转换,如System.out.println((Integer)bb[1])。(java不支持数组之间的强制类型转换)

T[] toArray(T[] a);

第二个函数能够直接得到T类型的数组,当传入的T[] a能放下ArrayList时,会将ArrayList中的内容复制到a中(a的size较大时会a[size]=null)。否则,将构建一个新的数组并返回。具体实现如下:

public T[] toArray(T[] a) {

if (a.length < size)

// Make a new array of a's runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass());

System.arraycopy(elementData, 0, a, 0, size);

if (a.length > size)

a[size] = null;

return a;

}

对于第二个函数,可以考虑将一个大小一致的T[]数组传入toArray()函数(为了数组复用),或者直接Integer[] ArrayAA = aa.toArray(new Integer[0]);。

为什么不直接定义函数 T[] toArray();

通常,直观上更直接的返回数组的方式应该是T[] toArray(),为什么JDK定义了一个不怎么好用的返回Object数组的函数。

数组之间虽然占用空间大小相同,但是不能强制改变类型(由于数组也是类,而数组类之间没有继承关系)。以object[] a; ...; (Integer[])a强制转换一个数组类型时,会在编译器产生警告,运行时抛出异常。因此对于泛型数组,无法以(T[]) array的形式,将擦除Object类型的数组强转为T[]类型。

主要和jdk向前兼容以及泛型的类型擦除有关,个人认为主要应该还是由于类型擦除机制导致了返回T[] toArray()的实现困难。

泛型的类型擦除

泛型是从SE 5才开始引入,为了不破坏现有的类型机制,用了一种类型擦除的机制,相比C++使类型擦除时的考虑更为复杂。

虚拟机并不支持泛型,而是将泛型类编译成了一个类型擦除(erased)的类,将类型变量转换成一个原始类型(raw type)。原始类型在默认类型变量时会被转换成Object,在类型变量有限定时(如 )会被转换成限定的类。在运行时获取到的T类型都是擦除后的类型。

public class Pair {

private T first;

private T second;

public Pair(T first, T second){ this.first = first; this.second = second; }

}

// 会被替换成public class Pair {

private Object first;

private Object second;

public Pair(Object first, Object second){

this.first = first;

this.second = second;

System.out.println(this.first.getClass()); // 不管T类型如何,得到的都是Object }

}

//当类型为Pair时,T会被替换为Comparable

这和C++的处理方式很不一样,C++中每个模板的实例化都会产生不同的具体类型,相当于对与每一种类型都会编译出一套独立的代码,会有“模板代码膨胀”。而在java中,使用了模板的类作为一个通用类进行了编译,传入不同的泛型参数也只会运行在同一个类上,模板的类型使用擦除后的类型进行编译。

在使用到具体的对象时,编译器会添加一个强制类型的转换指定,将Object或限定的类型强转为具体的类型。如对于类成员函数 public T getFirst(),由于类型擦除后函数会变为public Object getFirst(),当泛型T为整型时,编译器调用 Int a = pair1.getFirst()会添加一个强制类型转换指令给虚拟机。而在没有具体类型时,一直使用擦除后的类型进行处理。

泛型方法不涉及类型擦除

public void f(T x){

System.out.println(x.getClass().getName());

}

f.(""); // java.lang.Stringf.(1); // java.lang.Integer

对于泛型方法,使用的是类型推断机制,当调用方法时,通过参数判断T的类型,而非擦除为Object。

T[] toArray(T[] a); 函数就是通过这一方式,在调用toArray函数时通过参数类型得到泛型的类型,然后通过反射创建数组。

类型擦除导致的结果

由于类型的擦除,在使用时需要一直注意类型变量的类型并非T,编译期无法得到关于T类型的具体信息,在运行时的类型并不会替换为具体的类型,而是在需要的地方执行强制类型转换。 在运行时会出现下面的情况:

类型List和List的类型在擦除后相同。

同上 instanceOf 也无法使用。

T a = new T();编译器会报错,因为类型在编译期不存在,而且编译阶段无法确定在T中是否存在默认的无参构造函数。

同上,无法使用 T[] a = new T[10]。

外加数组类之间无继承关系导致无法将Object[]的数组强转为T[]。

因此,java中直接设计T[] toArray()类型的函数需要额外的传入类型。

泛型数组的创建的两种常用方法

虽然无法直接创建T类型的对象,但可以利用反射机制间接的创建T类型的对象。对于创建泛型数组,一般的方案是使用ArrayList。如果某些情况下需要自己实现,可以使用和ArrayList类似的方式。

1、JDK通过创建Object[]的数组放对象,在取对象时进行类型转换,此时toArray函数通过泛型函数的参数获取类型。

// 数组仍使用Object类型private Object[] array = new Object[size];

// 在get函数中强制类型转换public T get(int index){

return (T)array[index];

}

// 转换成数组public T[] toArray(T[] a){

// 此处a只用于获取类型 // 更严谨的实现参考上面的JDK代码 return (T[]) Arrays.copyOf(elementData, size, a.getClass());

}

2、或者传入具体的类型,由于传入的具体类型可以创建具体类型数组,因此可以直接实现T[] toArray()。可能是传入类型的方式不太优雅,JDK并没有使用这种形式。

class GenericArray{

private T[] array;

// 构造函数直接传入类型,数组的强制类型转换会产生编译警告,此处直接用标签忽略 @SuppressWarnings("unchecked")

public GenericArray(Class type, int size){

array = (T[]) Array.newInstance(type, size);

}

public T[] toArray(){

return array;

}

}

在泛型中创建具体的类实例

和上面的情况类似,要想在泛型类中创建具体的类型,也就是需要在类中能够得到T.class,通常需要使用两种方式:

将T.class通过函数或其它方式传入类中,通过反射机制创建。

泛型函数能够从参数的类型中获取T.class。

后面简单介绍构造函数包装后传入的方式。

通过构造函数传入类型后创建类实例

对于T a = new T();,由于类型擦除无法创建,但可以通过在运行时传入类变量来实现创建,将类型通过构造函数传入。在有类型后,通过反射机制(newInstance)构建新的类。

public class ClassAsFactory{

Class kind;

public ClassAsFactory(Class kind){ this.kind = kind; }

// 构建时传入 String.class public static void main(String[] argvs){

ClassAsFactory gClass = new ClassAsFactory(String.class);

}

}

但是对于这段代码,编译器无法检查构造函数是否存在等问题,一般更建议使用显示类型工厂,在构造函数中传入new过具体类型的工厂类:

Interface FactoryI{

T create();

}

// 在工厂类中传入具体的对象Class IntegerFactory implements FactoryI{

public Integer Create() { return new Interger(0);}

}

Class Foo2 {

private T x;

// 类型F用来限制参数为工厂类 public > Foo2(F factory){

x = factory.create();

}

public static void main(String[] argvs){

new Foo2(new IntegerFactory());

}

此时,具体工厂类由于针对具体的类型,编译期间可以对创建过程进行检查。

《Think in Java》里还提到一种模板方法设计模式,没有太大的本质上的区别。

java object toarray_java从toArray返回Object[]到泛型的类型擦除相关推荐

  1. Java基础篇:泛型与类型擦除

    一.什么是泛型: 泛型的本质是 参数化类型,也就是说 将所操作的数据类型 指定为一个参数,在不创建新类的情况下,通过参数来指定所要操作的具体类型(类似于方法中的变量参数,此时类型也定义成参数形式),也 ...

  2. 描述java泛型引入原则_Java/泛型的类型擦除/README.md · oslo/LearningNotes - Gitee.com

    前言 Java 泛型(Generic)的引入加强了参数类型的安全性,减少了类型的转换,但有一点需要注意:Java 的泛型在编译器有效,在运行期被删除,也就是说所有泛型参数类型在编译后都会被清除掉,看下 ...

  3. Java泛型:类型擦除

    前情回顾 Java泛型:泛型类.泛型接口和泛型方法 类型擦除 代码片段一 1 2 3 4 5 6 7 Class c1 = new ArrayList<Integer>().getClas ...

  4. java 泛型和类型擦除_关于Java泛型和擦除

    java 泛型和类型擦除 "编译期间擦除泛型"是常识(好吧,类型参数和实参实际上是被擦除的). 这是由于"类型擦除"而发生的. 但这是错误的,正如许多开发人员所 ...

  5. java 泛型 擦除_Java泛型和类型擦除

    一 前言:初识泛型 废话不说,先来看一段代码: public class Holder { private Object data; public Holder(Object data ){ this ...

  6. Java泛型的类型擦除

    写在前面:最近在看泛型,研究泛型的过程中,发现了一个比较令我意外的情况,Java中的泛型基本上都是在编译器这个层次来实现的.在生成的Java字节代码中是不包含泛型中的类型信息的.使用泛型的时候加上的类 ...

  7. Java泛型与类型擦除--ImportNew

    本文由 ImportNew - 伍翀 翻译自 On Java Generics and Erasure.欢迎加入翻译小组.转载请参见文章末尾的要求. "编译器会进行泛型擦除"是一个 ...

  8. java vector toarray_java Vector.toArray 与强制类型转换 | 学步园

    今天写程序的时候碰到这样的问题: public String[] getPlatformIDList() { Vector result = new Vector(); try { Statement ...

  9. java vector toarray_java Vector.toArray 与强制类型转换

    今天写程序的时候碰到这样的问题: public String[] getPlatformIDList() { Vector result = new Vector(); try { Statement ...

最新文章

  1. 如何参与一个顶级开源项目
  2. 打算近期去深圳找工作
  3. 知识点二、PHP简单的分页过程与原理
  4. Android架构分析之Android消息处理机制(一)
  5. PHP对抗web扫描器的脚本技巧
  6. 软件设计期末考试重点内容
  7. 易语言动画框和动画物体通过代码载入外部图片数据不显示!
  8. Python笔记-多线程爬虫实例
  9. 如何用Pygame写游戏(七)
  10. php如何把图片铺满,PHP如何裁剪图片成固定大小
  11. python字典占内存_Python:减少字典的内存使用
  12. 使用pyinstaller讲python文件打包成exe文件运行时,弹出命令行界面
  13. C++ 操作PDFlib实例
  14. hping 详解_hping3 使用详解
  15. 关于如何关闭Windows错误报告
  16. qml鼠标拖动_Arcgis for qml - 鼠标拖拽移动
  17. 【软件工具】之下载微软官方正版 windows 系统
  18. 【华人学者风采】周昆 浙江大学
  19. Android判断手机是否是小米MIUI系统
  20. WHQL认证资讯:关于Windows硬件开发者仪表盘

热门文章

  1. 计算机网络——网络协议和网络体系结构
  2. datetime格式转化为年月日的数字。使用CAST函数
  3. 数位 dp 最低位最高位之差绝对值大于2_王者荣耀 各战区 全英雄(无艾琳)省与市 最低战力查询分享 (评论有表格链接)...
  4. 适用于G Suite用户的Google App Maker低代码工具首次亮相
  5. T31快启图像效果优化
  6. DNF原有的专业赛事体系将得到全面优化
  7. 渗透测试中waf命令执行的绕过经验技巧
  8. 怎样才算是无线网络扩展的正确姿势?
  9. java程序顶层显示图片_如何让图片以半透明的效果显示在全部程序或窗口的最上层?...
  10. 华为nova系列用户挺近2亿大关,华为做对了什么?