数组与List的互转及原理分析

2021-01-29 10:55:26 浏览数 (1)

一:首先我们将数组转为List。

方法有两种:

1、使用jdk提供的类:Arrays.asList()

2、使用:Collections.addAll()

我们先看第一种方法;

API里的说法是这样的。

代码语言:javascript复制
public static <T> List<T> asList(T... a)
返回一个受指定数组支持的固定大小的列表。(对返回列表的更改会“直接写”到数组。)此方法同 Collection.toArray() 一起,充当了基于数组的 API 与基于 collection 的 API 之间的桥梁。返回的列表是可序列化的,并且实现了 RandomAccess。 
此方法还提供了一个创建固定长度的列表的便捷方法,该列表被初始化为包含多个元素: 
     List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");

使用asList得到的是一个固定程度的一个list,对新的list做元素的修改时可以的,但是,如果更改了大小就会报异常.

这有个很重要的东西,就是,使用asList()方法是可以得到List,但是如果不保证你的数组是对象类型的数组的话(除了基本数据类型的类型),转换完成后你会发现,转完后的list大小是1,而list[0] --> 指向的是你的数组,不止这一个方法,数组与list的互转都存在这个问题。也就是你list的第一个元素是一个数组。在下面Collections方法里有列出一个例子进行对比。

代码语言:javascript复制
//数组转集合
		//方法一:使用Arrays.asList(),但是要注意转换后的list不能做大小和结构性更改
		Integer[] a = {2,5,7,8,9,6};
		List list = new ArrayList();
		list = Arrays.asList(a);//这个对转换后的集合大小和结构不能更改
		list.add(6);//做增加操作更改数组大小
		System.out.println(list.get(0));

会报这样一个异常:

先看看源码:

代码语言:javascript复制
 public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

从源码来看,它放回了一个ArrayList,貌似没毛病

如果你往下找的话,你会发现这样一个东西:

代码语言:javascript复制
private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);//这里就是最终找到地方,
        }
代码语言:javascript复制
 public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }

发现我们传入的数组在这里就是array,然后返回给了a,而 a 在这个类里被定义为了final,只要有点基础的小伙伴肯定知道被这个修饰的变量不能被改变,而引用类型的变量,其地址不能被改变,内容是可以被修改的,被修饰的数组,长度是不能改变的,内容是可以修改的。 所以这个方法返回的list是不能被修改大小的。

这样做数据修改:list.set(2, 1);得到的结果就是:[2, 5, 1, 8, 9, 6],正常运行。而使用:list.add(6);和list.remove(2);就会报错。这样的使用的确是有很大限制,但对于不做大小方面修改的操作已经可以了。

如果你抱怨道不行呀,还不够我使用呀,仅仅只是变为list,不能做list的操作那还有什么意义。 我们还有另一种解决办法:在转换的同时把他加入到ArrayList中,这样就可实现转为list后并能使用list的功能了。

代码语言:javascript复制
//数组转集合
		//方法一:使用Arrays.asList(T...a),但是要注意转换后的list不能做大小和结构性更改
		Integer[] a = {2,5,7,8,9,6};
		List list = new ArrayList();
		//list = Arrays.asList(a);//这个对转换后的集合大小和结构不能更改
		list = new ArrayList(Arrays.asList(a));
		list.add(6);//做增加操作更改数组大小
		System.out.println(list.toString());

结果:[2, 5, 7, 8, 9, 6, 6] 第二种方法:使用Collections.addAll() 这个方法得到list不像上一个方法里说的list,可以做list的各种操作。但有一点是相同的,如果你的数组是基本类型的,那么第一个元素是一个数组。

代码语言:javascript复制
//方法二:使用Collections.addAll()
		list = new ArrayList ();
		Collections.addAll(list, a);
		System.out.println(list);
		list.add(8);
		System.out.println(list);

数组是:Integer[] a = {2,5,7,8,9,6};

则结果是:

[2, 5, 7, 8, 9, 6] [2, 5, 7, 8, 9, 6, 8] 数组是:int[] a = {2,5,7,8,9,6};

则结果是: [[I@15db9742] [[I@15db9742, 8]

怀疑的话,可以这样加一句显示:System.out.println(Arrays.toString((int[])list.get(0)));

显示:[2, 5, 7, 8, 9, 6]

二、List 转数组

使用集合里的方法toArray()

还是先看API,有两个方法可以实现

代码语言:javascript复制
public Object[] toArray()
按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组。 
由于此列表不维护对返回数组的任何引用,,因而它将是“安全的”。(换句话说,此方法必须分配一个新的数组)。因此,调用者可以自由地修改返回的数组。 
代码语言:javascript复制
public <T> T[] toArray(T[] a)
按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。如果指定的数组能容纳列表,则将该列表返回此处。否则,将分配一个具有指定数组的运行时类型和此列表大小的新数组。 
如果指定的数组能容纳队列,并有剩余的空间(即数组的元素比队列多),那么会将数组中紧接 collection 尾部的元素设置为 null。(仅 在调用者知道列表中不包含任何 null 元素时才能用此方法确定列表长度)。

同样的是不建议使用基本数据类型转换的。

第一个无参数的方法是返回Object[] ,不明所以的小伙伴们一开始就这样写:

代码语言:javascript复制
Integer[] a2 = new Integer[list.size()];//开辟list大小的数组
		a2 = (Integer[]) list.toArray(a2);

然后莫名的被控制台的ERROR吓懵逼了,我刚开始就被吓懵逼了。

我们这里要讲讲Object 和 Object[] 的关系了,我们都知道Object是所有类的超类,所有类都属于Object,但是Object[]是用于存储Object元素的数组,好像到这里大家都不明白,那我们将Object看成一个对象,所有的对象都属于Object这个对象,那么,Object[]也看成一个对象,数组本身不属于Object类,同理也没有一个对象属于它,所以Object[]这个对象跟Object没有关系。大家懂了之后我们再来看代码:Integer[] a2 通过list.toArray()返回一个Object[]的对象,而我们用Integer[]对象来接收,这两者没有关系,所以不能相互转化,所以才报的异常。

现在再来看看源码:

代码语言:javascript复制
public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
代码语言:javascript复制
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;
    }

1、先看第一个方法:toArray(),返回为一个Object[]的对象,Arrays.copyOf( )

代码语言:javascript复制
public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

在往下看底层的实现:

代码语言:javascript复制
 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;
    }

三目运算符哪里:将我们传入的数组对象(Object)newType和(Object)Object[]这个对象比较,是否相等,相等则返回new Object[newLength]是一个new出来的新的数组,不等则会进入Array.newInstance,再往下走会发现抛出了一个异常。所以必须要准备一数组容器来接收,而且必须是一个Object[]类型的,所以我们使用第一个方法的话会得到一个Object[]数组,想要使用,就有要将数组里的元素遍历在强制转换为我们的类型,这个相对来说麻烦了些,实现如下:

代码语言:javascript复制
for(int i=0 ; i < aa.length ; i  ) {
					aa[i] = ((Integer)aa[i]).intValue();
				}

2、第二个方法:toArray(T...a)

参数类型是可变参数,就是参数个数不确定,可放任意个,底层实现是数组实现的,有兴趣的小伙伴可深入了解。

源码:

代码语言:javascript复制
 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;
    }

这个不难看出当a.length < size时,return,返回的是第一个方法的底层实现,就不提了;然后使用系统提供的数组复制system.arraycopy(原数组,原始数组开始索引,目的数组,目的数组开始索引,大小),这里目的数组是 a (也就是我们传入的数组),然后到后面返回的 是 数组 a ,这时候 a 已经是复制好的数组了。所以我们使用时也可以不用再准备容器去接收它,

代码语言:javascript复制
				Integer[] a2 = new Integer[list.size()];//开辟list大小的数组
				Object[] aa = new Object[list.size()];
				list.toArray(a2);
				System.out.println(" " Arrays.toString(a2));

结果: [2, 5, 7, 8, 9, 6]

总结:我们介绍了数组转list的两种方法:第一是asList:list = Arrays.asList(a) list = new ArrayList(Arrays.asList(a));两种的使用及区别,一个是固定长度,不可改变,不然会报异常,一个是拥有list的所以东西可增可删;第二个是Collections.addAll(),有它有两个参数,第一个是集合对象,第二个是数组对象,使用后内部实现转换,简单方便,然后是list转数组:toArray(),重载了两种,一个是无参返回Object[] ,一个是有参返回所以类型数组,第一个转换成指定类型数组比较麻烦不太好用,第二个简单方便好用。

代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制
代码语言:javascript复制

0 人点赞