toArray方法总结

toArray方法总结toArray方法涉及java的泛型,反射,数组的协变,jvm等知识。Java标准库中Collection接口定义了toArray方法,如果传入参数为空,则返回Object[]数组,如果传入参数为T[],则返回参数为传入参数的运行时类型。以下是ArrayList的实现:

大家好,又见面了,我是你们的朋友全栈君。

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支持数组协变,这种运行时异常无法在编译期检查出来。最简单的例子见如下源码注释:

toArray方法总结

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

toArray方法总结

 数组对象的底层数据存储如上图所示,对象头中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 {
    @Test
    public 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;
    }

    @Test
    public 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)]
    }

    @Test
    public 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
    }

    @Test
    public 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
    }

    @Test
    public 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
    }

    @Test
    public 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方法,在内存中对数组进行复制,效率更高。
    @Test
    public 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和数组类型进行校验,所以便有如下测试结果。

    // 可以通过编译,运行通过
    @Test
    public void test9() {
        List<Integer> list = new LinkedList<>();
        Double[] list2 = list.toArray(new Double[0]);
        for (Double d : list2) {
            System.out.println(d);
        }
    }

    // 编译通过,运行异常
    @Test
    public void test10() {
        List<Integer> list = new LinkedList<>();
        list.add(1);
        Double[] list2 = list.toArray(new Double[0]);
        // java.lang.ArrayStoreException: java.lang.Integer
        for (Double d : list2) {
            System.out.println(d);
        }
    }

总结:

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/146471.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • qxdm无法安装问题闪一下_Qualcomm QXDM

    qxdm无法安装问题闪一下_Qualcomm QXDM似乎很难看见这样不错的软件了,大概是因为功能确实切中了很多用户的期待,相类似功能的软件在这一点上做的就感觉一般般吧,大概就是全靠同行衬托了。是一种实时数据采集和诊断记录工具专业显示统计和诊断信息,使用户能够读写非易失性存储器LOG获取,LOG文件转换,测试机状态获取把程序里面的一些Message或者数据包输出到QXDM的图形接口确合理的使用可以为我们测试提供便捷的定位手段QualcommQXDM…

  • http请求过程图解_性能优化的方法

    http请求过程图解_性能优化的方法之前有整理过一部分知识点,一直没有发布,因为都是有关CSS方面的零散内容;现在想想无论做什么都需要慢慢积累,所以还是决定将之前整理的相关内容验证之后慢慢分享给你们,现在看到感觉还挺有意思。好了废话不多说,直接上代码以及图例(为了让大家方便阅读,都有自己验证过程的一些图片作为分享)。1.什么是Web前端1.Web前端本质上是一种GUI软件,可以直接借鉴其它GUI软件系统架构设计方法。1.什么是GUI软件?1.图形化用户界面:

  • ORA-01017: invalid username/password; logon denied Oracle数据库报错解决方案一[通俗易懂]

    ORA-01017: invalid username/password; logon denied Oracle数据库报错解决方案一[通俗易懂]ORA-01017:invalidusername/password;logondenied错误(程序中的用户和密码无法登录,登录被拒)。Oracle11g版本初次安装使用报错:解决方法1创建新用户:打开sqlplus以系统身份登录:指令如下sys/managerassysdba;创建新用户:语法:createuser用户名identifiedb…

  • 优惠券设计及流程_优惠券怎么设计

    优惠券设计及流程_优惠券怎么设计在整个APP开发产品发展的整个周期中,运营活动必不可少,而发放优惠券已成为运营活动的一种基本形式,而关于优惠券设计的整体流程尤为重要。接下来,分享一下自己的经验,希望对大家有帮助,感谢支持!整体架构

  • Jmeter 之正则表达式提取器应用「建议收藏」

    Jmeter 之正则表达式提取器应用「建议收藏」目录前言一、获取单个数据二、获取多个数据个人觉得非常非常细节的地方注意一下:前言说到Jmeter正则表达式提取器的应用,就不得不说到关联。所谓关联,就是把应用中动态变化返回的数据获取到,把它保存为一个参数,提供给后面需要用到的地方进行使用。Jmeter中关联可以通过“添加—后置处理器—正则表达式提取器”来获取数据,且注意正则表达式提取器附在需要获取数据的某个采样器之下,数据一般从“查看结果树“的响应数据获取。Jmeter中关联可以通过“添加—后置处理器—正则表达式提.

  • 树莓派3b+串口配置

    树莓派3b+串口配置前言树莓派从大的方向来说一共出了3代,每一代的CPU外设基本相同,但内核不同,外设里面一共包含两个串口,一个称之为硬件串口(/dev/ttyAMA0),一个称之为mini串口(/dev/ttyS0)。硬件串口由硬件实现,有单独的波特率时钟源,性能高、可靠,mini串口性能低,功能也简单,并且没有波特率专用的时钟源而是由CPU内核时钟提供,因此mini串口有个致命的弱点是:波特率受到内核时钟的影响…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号