toArray方法涉及java的泛型,反射,数组的协变,jvm等知识。

Java标准库中Collection接口定义了toArray方法,如果传入参数为空,则返回Object[]数组,如果传入参数为T[],则返回参数为传入参数的运行时类型。以下是ArrayList的实现:

// 略去无关内容
public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable{@SuppressWarnings("unchecked")public <T> 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;}public Object[] toArray() {return Arrays.copyOf(elementData, size);}
}

1. 传入的静态参数T为编译器提供了编译器检查,如果类型不匹配,则编译不通过。

如test1所示,Byd[] 不能接受静态返回类型Brand[],除非作类型强转,才可以编译通过,但是会报运行时类型转换异常。这个异常非常有意思,虽然方法返回的数组类型为Brand[],并且其中的每个元素都为Byd,但是Brand[]类型不能强转为Byd[]类型。根本原因是JVM的限制,即不能对运行时数组类型进行强转。如test3所示,cmps的静态类型是Comparable[],运行时类型是Integer[],运行时类型不能转化。

类型转换中向上转型是支持的(转型为父类或接口),向下转型必须进行类型强转,可能报运行时异常。java中数组支持协变,即Byd[]是Brand[]的子类,可以用Brand[]类型接收Byd[]对象,如test4所示。

test6也很有意思,虽然List<Brand>转化为了Brand[],编译通过,但是由于传入的Byd[]为brands1的运行时类型,在往实际的Byd[]中存放Brand的过程中,会报ArrayStoreException异常,由于Java支持数组协变,这种运行时异常无法在编译期检查出来。最简单的例子见如下源码注释:

此时在运行时抛出了数组存储异常,因为数组的实际类型为String[],虚拟机运行时进行类型检查发现类型不匹配就抛出此异常。

数组对象的底层数据存储如上图所示,对象头中Mark Word存储hashCode和内存回收、并发相关信息,Klass Word为类型指针,存储类型不匹配抛出ArrayStoreException,array length为数组长度,数组越界抛出ArrayIndexOutOfBoundsException。

interface Brand {int getId();String getBrandName();
}@Data
@AllArgsConstructor
class Byd implements Brand {int id;String brandName;Integer speed;
}@Data
@AllArgsConstructor
class Tesla implements Brand {int id;String brandName;
}public class ArraysTest {@Testpublic void test1() {List<Byd> byds = new ArrayList<>();byds.add(new Byd(1, "唐", 100));byds.add(new Byd(2, "宋", 120));// Byd[] byds1 = byds.toArray(new Brand[0]);// 传入参数类型Brand[]和返回参数类型不匹配, 不能编译通过Byd[] byds1 = (Byd[]) byds.toArray(new Brand[0]);// 编译通过,报运行时异常:// java.lang.ClassCastException: [Lcom.dahua.Brand; cannot be cast to [Lcom.dahua.Byd;}@Testpublic void test2() {List<Byd> byds = new ArrayList<>();byds.add(new Byd(1, "唐", 100));byds.add(new Byd(2, "宋", 120));Brand[] brands = byds.toArray(new Byd[0]);System.out.println(Arrays.toString(brands));// 输出:// [Byd(id=1, brandName=唐, speed=100), Byd(id=2, brandName=宋, speed=120)]}@Testpublic void test3() {Integer[] ints = new Integer[3];Arrays.setAll(ints, x -> x+1);Comparable[] cmps = (Comparable[]) ints;System.out.println(cmps.getClass().getComponentType());// 输出:// class java.lang.Integer}@Testpublic void test4() {List<Byd> byds = new ArrayList<>();byds.add(new Byd(1, "唐", 100));byds.add(new Byd(2, "宋", 120));Brand[] brands = byds.toArray(new Byd[0]);System.out.println(brands.getClass().getComponentType());// 输出:// class com.dahua.Byd}@Testpublic void test5() {List<Brand> brands = new ArrayList<>();brands.add(new Byd(1, "唐", 100));brands.add(new Byd(2, "宋", 120));Brand[] brands1 = brands.toArray(new Byd[0]);System.out.println(brands1.getClass().getComponentType());// 输出:// class com.dahua.Byd}@Testpublic void test6() {List<Brand> brands = new ArrayList<>();brands.add(new Byd(1, "唐", 100));brands.add(new Byd(2, "宋", 120));brands.add(new Tesla(3, "model S"));Brand[] brands1 = brands.toArray(new Byd[0]);// 编译通过, 但是运行时异常:// java.lang.ArrayStoreException}
}

2. 如果toArray方法传入的数组长度大于等于list的size,只将size后一个位置置空。由于ArrayList::toArray方法通常传入长度为0的数组,调用了Arrys::copyOf方法,下面来看此方法。

public class Arrays {    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {@SuppressWarnings("unchecked")T[] copy = ((Object)newType == (Object)Object[].class)? (T[]) new Object[newLength]: (T[]) Array.newInstance(newType.getComponentType(), newLength);System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));return copy;}
}

首先创建copy对象,其类型只能在运行时指定,Array::newInstance可以在运行时创建数组,动态指定类型。

  1. 泛型T U可以用作静态类型检查,如test7所示,传入的Double[].class,其返回值可以由Comparable[]接收,只要newType代表的静态类型为T[]的子类即可,Double[]是Comparable[]的子类,虽然静态类型检查通过,但是运行期异常报出。
  2. 如果传入raw类型(不带泛型的Class),依然可以通过编译,这是java为了前向兼容非泛型类型。此时返回类型为Object[],实际上此方法的字节码返回的也是Object[]。建议使用带泛型的形式。
  3. 对于不安全的类型转换编译器会报unchecked 警告,表示编译器无法做类型检查。对于确定安全的方法,使用@SuppressWarnings关闭。对于编译器报unchecked警告需要提高警惕,此处极有可能遇到bug问题。
  4. System.arraycopy为native方法,在内存中对数组进行复制,效率更高。
    @Testpublic void test7() {Integer[] ints = new Integer[10];Arrays.setAll(ints, x -> x+1);
//        Comparable[] comparables = Arrays.copyOf(ints, 5, Double[].class);// 编译通过,运行时异常Class clazz = Object[].class;System.out.println(clazz);Object[] arr = Arrays.copyOf(ints, 4, clazz);System.out.println(Arrays.toString(arr));//        clazz = Number[].class;
//        Number[] objects = Arrays.copyOf(ints, 5, clazz);// 无法编译,类型不匹配}

通过以上分析,对于LinkedList的分析就简单了。

// 遍历复制
public Object[] toArray() {Object[] result = new Object[size];int i = 0;for (Node<E> x = first; x != null; x = x.next)result[i++] = x.item;return result;
}// 运行时创建数组,遍历复制@SuppressWarnings("unchecked")public <T> T[] toArray(T[] a) {if (a.length < size)a = (T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);int i = 0;Object[] result = a;for (Node<E> x = first; x != null; x = x.next)result[i++] = x.item;if (a.length > size)a[size] = null;return a;}// 注意:toArray方法没有对List<E>泛型类型E和数组类型进行校验,所以便有如下测试结果。// 可以通过编译,运行通过@Testpublic void test9() {List<Integer> list = new LinkedList<>();Double[] list2 = list.toArray(new Double[0]);for (Double d : list2) {System.out.println(d);}}// 编译通过,运行异常@Testpublic void test10() {List<Integer> list = new LinkedList<>();list.add(1);Double[] list2 = list.toArray(new Double[0]);// java.lang.ArrayStoreException: java.lang.Integerfor (Double d : list2) {System.out.println(d);}}

总结:

1. 由于数组编译时需要“物化”类型,而泛型在Java运行时已经擦除为Object、上界(extends)、上界(extends),不能确定具体的类型,编译器不通过。如果需要在运行时确定数组类型并创建数组,需要调用Array.newInstance方法,传入泛型类型参数。

2. 使用时尽量带上泛型参数,便于编译期检查类型。

3. 注意@SuppressWarnings("unchecked")方法使用时可能会有运行时异常,应该小心使用此类方法。

toArray方法总结相关推荐

  1. list子类使用toarray方法实现集合向数组转换,下列哪些说法是正确的

    单选 list子类使用toarray方法实现集合向数组转换,下列哪些说法是正确的:答案在文末 A. Vector无法使用toArray转换成数组. B. 优先推荐:调用集合类的方法toArray(T[ ...

  2. java中List的toArray方法

    把List转换成某种类型的数组,就拿String类型来做例子吧,有以下两种方式: //方法1,使用不带参数的toArray方法 String[] arr1=new String[list.size() ...

  3. 关于Arrays类中toArray方法的总结

    学了差不多两年的Java,数组类Arrays应该是我最常用到的类之一了,在整个学习的过程中,会更常用到一个方法,那就是toArray方法,因为将Collections类型的集合转化为数组后,将会更容易 ...

  4. List转数组toArray方法

    一.List.toArray方法 List提供了一个将List转为数组的一个非常方便的方法toArray.toArray有两个重载的方法:  Object[] toArray()            ...

  5. 有关ArrayList的toArray()方法的一些探究

    ArrayList的创建与toArray()方法 先上两段代码,大家自己感受下 List<String> list1 = Arrays.asList("abc");// ...

  6. c# Dictionary 中Keys.ToArray方法的细节测试

    /// <summary>/// dic.Keys.ToArray<>方法生成数组的顺序和dic中的顺序相同/// </summary>public static ...

  7. List的toArray方法强制转换

    List的toArray方法强制转换 List容器类中有一个toArray()的方法,该方法是用来把List转化为数组的. 这个方法有一个特点就是转化出来的数组是复制了原数据的一个副本而不只是原数据的 ...

  8. 集合类的toArray方法

    功能:将对象的元素值返回为数组 jdk1.8的文档中共有两种toArray方法: 一.无参的toArray()方法 直接将对象转成一个数组用Object数组来接收 代码 import java.uti ...

  9. toArray()方法

    集合的toArray()方法返回一个Object数组,它就是Object不是其他类:但是我们如果需要获得对应特定类型的数组时:通常都是在取得数组元素时进行强制类型转换: 因为直接的转换是错的: Lis ...

最新文章

  1. Vue.js 自定义指令
  2. 数组排序c语言函数_C语言如何写出返回数组最大最小值之差的函数(C primer plus 10-5)...
  3. 联想小新电脑dns服务器未响应,Lenovo Quick Fix 联想智能解决工具
  4. java tostring格式化日期_java日期格式化SimpleDateFormat的使用详解
  5. 信息学奥赛一本通(1038:苹果和虫子)
  6. WSGI Server/Gateway
  7. 5 多数据save_[Python] 通过采集两万条数据,对无名之辈影评分析
  8. EXT3-fs error和EXT4-fs error小总结
  9. linux 一键网克,MaxDOS 8.0
  10. FAT12文件系统详解
  11. 小白刷LeeCode(算法篇):4
  12. 微信内置浏览器支付流程
  13. JAVA修改运行内存
  14. 汉语拼音容易弄混的4个字母
  15. .so是什么文件_Linux的so文件到底是干嘛的?浅析Linux的动态链接库
  16. java Serializable
  17. Vivado综合设置之-gated_clock_conversion
  18. Linux 0.00简单多任务内核head.s超详注释
  19. android studio 看不到 manifest,Android Studio: Android Manifest doesn't exist
  20. 用于大型问题的一阶原始双锥求解器SCS

热门文章

  1. CentOS如何设置DHCP为静态IP地址
  2. x86 linux 根文件系统6,Linux系列六之根文件系统
  3. Java游戏源码大礼包
  4. 关于阿里iconfont字库 引用到Vscode失败
  5. java automapper_实现AutoMapper(1.0版本)
  6. PPT学习(二)制作一个海报
  7. win11关闭蓝牙耳机音质差的handsfree模式
  8. 重庆高职单招计算机试题及答案,重庆高职单招模拟试卷答案
  9. 散片便宜300元!但还是劝你买盒装CPU
  10. 2020年中职组“网络空间安全”赛项天津市选拔赛竞赛任务书