在实际开发过程中,为了处理的方便或者接口类型的要求,我们经常需要在集合与数组之间进行相互转换,JDK为我们提供了方便的工具类和相应的方法来完成这个工作。Arrays.asList()方法与Collection.toArray()方法一起充当了基于数组的API与基于 Collection的API之间相互转化的桥梁,但是由于不甚了解其实现的原理,很多同学在使用的过程中经常会被一些问题困扰,在此通过源码解读总结一下相关的知识。

1. 集合转数组

Collection接口中定义了两个方法用于将集合中的元素转成数组中的元素,所有实现了Collection接口的集合类型,比如ArrayList,HashSet都必须实现这两个方法,虽然各类集合的具体实现方式可能会不同,但是这两个方法的功能是确定的。下面以ArrayList<String>转String[]为例,我们可以分别查看其在JDK1.7中的源码

ArrayList.toArray()

/*** Returns an array containing all of the elements in this list* in proper sequence (from first to last element).* * ...**/
public Object[] toArray() {return Arrays.copyOf(elementData, size);
}

从上述代码可以看到,toArray()方法直接把ArrayList底层数组elementData中的元素全部拷贝到一个新数组中并返回,不管ArrayList中存储的是哪种类型的元素,该方法的返回类型都是Object[]数组,如果调用者试图将Object[]转成具体对象数组类型,比如

ArrayList<String> list = new ArrayList<>();
String[] strings = (String[]) list.toArray();

那么就会抛出ClassCastException,因为这是不支持的强转方式。那么我们如何才能得到想要的具体对象类型的数组呢?另一个重载的toArray()方法可以解决。


ArrayList.toArray(T[] a)

我们先来看看该方法的源码

/*** Returns an array containing all of the elements in this list in proper* sequence (from first to last element); the runtime type of the returned* array is that of the specified array.  If the list fits in the* specified array, it is returned therein.  Otherwise, a new array is* allocated with the runtime type of the specified array and the size of* this list.**/
@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;
}

该方法是一个泛型方法,支持任何对象类型。要求调用者传递一个指定类型的数组对象a,如果a装不下ArrayList的所有元素,那么就会新建一个长度合适的数组,数组的类型就是a的运行时类型(通过反射得到a中元素的类型);如果传入的数组a装得下所有元素,那么直接把集合中的所有元素都拷贝到数组a中,并且如果数组的长度超过了集合的size,那么拷贝完成之后要把数组中剩余空间的首元素置空,目的是用于标记集合元素的结束位置,类似于c语言中字符串的结束标志“\0”的作用。

通过对比上述两个方法,可以明显感觉到JDK 1.5引入泛型之后带来的方便之处,所以在明确知道集合中元素的类型并且想要使用泛型,那么推荐使用第二个转换方法。


2. 数组转集合

由于数组是一种特殊的类型,为了更加方便地操作它,JDK提供了一个强大的工具类Arrays,它提供了一个静态方法asList()来实现数组到集合的直接转换。

Arrays.asList()

首先看源码

/*** Returns a fixed-size list backed by the specified array.  (Changes to* the returned list "write through" to the array.)  This method acts* as bridge between array-based and collection-based APIs, in* combination with {@link Collection#toArray}.  The returned list is* serializable and implements {@link RandomAccess}.**/
@SafeVarargs
public static <T> List<T> asList(T... a) {return new ArrayList<>(a);
}

这个方法看起来非常方便好用,但是实际上使用它会有诸多限制。方法体只有一句话,就是使用传递进来的可变参数a(可以是对象类型的数组或者一个对象列表,比如字符串列表{“a”, “b”, “c”})重新new了一个ArrayList再直接返回,但是我们知道java.util.ArrayList类并没有相应版本的构造函数。通过F3键继续查看源码,我们发现这个ArrayList其实并不是我们常用的那个ArrayList,它是Arrays的一个私有内部类java.util.Arrays.ArrayList,它确实提供了一个合适的构造方法ArrayList(E[] array)

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) {if (array==null)throw new NullPointerException();a = array;}public int size() {return a.length;}public Object[] toArray() {return a.clone();}public <T> T[] toArray(T[] a) {int size = size();if (a.length < size)return Arrays.copyOf(this.a, size,(Class<? extends T[]>) a.getClass());System.arraycopy(this.a, 0, a, 0, size);if (a.length > size)a[size] = null;return a;}public E get(int index) {return a[index];}public E set(int index, E element) {E oldValue = a[index];a[index] = element;return oldValue;}public int indexOf(Object o) {if (o==null) {for (int i=0; i<a.length; i++)if (a[i]==null)return i;} else {for (int i=0; i<a.length; i++)if (o.equals(a[i]))return i;}return -1;}public boolean contains(Object o) {return indexOf(o) != -1;}
}

这样看来,似乎已经解决了从数组到集合的转化问题,实际上转化确实已经实现了,但是遗留的问题是,我们并不能对这个返回的Arrays$ArrayList的对象进行结构修改操作,当调用add或remove方法对集合做增删操作时,会抛出不支持此操作的异常,具体原因我们可以通过分析它的继承体系来探究。

从上面的源码可以看到Arrays$ArrayList继承了抽象类AbstractList,而AbstractList实现了List接口,并且实现了List接口中的大部分骨干方法。我们常用的ArrayList、Vector等集合类都继承自AbstractList,他们各自需要把AbstractList中没有实现的那些List方法根据自身的操作特点单独实现。这么做的目的其实就是为了更好地使用多态的特性!我们可以通过List引用来指向子类对象ArrayLaist或者Vector,调用List中的方法就可以通过动态绑定去调用子类中重写的方法。然而,AbstractList所实现的List骨干方法中并不包括add、remove这些操作,因为修改不同的集合类没有统一的操作方式,所以这类方法必须要留给具体的集合类自己去实现,那么AbstractList真的就没有实现add和remove方法吗?我们再来看一下AbstractList的源码

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {/*** Sole constructor.  (For invocation by subclass constructors, typically* implicit.)*/protected AbstractList() {}public boolean add(E e) {add(size(), e);return true;}abstract public E get(int index);public E set(int index, E element) {throw new UnsupportedOperationException();}public void add(int index, E element) {throw new UnsupportedOperationException();}public E remove(int index) {throw new UnsupportedOperationException();}...

可以清楚地看到,AbstractList没有给出get方法的实现,那么这就要求任何继承了他的子类必须要给出get方法的实现,除非这个子类也是抽象类,而对于set,add,remove方法,AbstractList直接抛出UnsupportedOperationException,这又是为什么呢?原因很简单,如果继承他的子类也没有重写这三个方法,那就说明该子类不支持相应的操作!如果子类实现了这三个方法,那么根据多态的特性,运行时执行的就是子类中的set,add,remove方法,所以问题全都回到了Arrays$ArrayList自身,我们再回过头去看他的源码,很明显它实现了get方法,重写了set方法,但却没有重写add和remove方法,所以当我们调用add和remove方法时,实际上执行的是其父类AbstractList中对应的方法,所以也就理所应当地抛出了UnsupportedOperationException异常。

一般而言,我们是需要对Arrays.asList()返回的集合对象做get、add等操作的,那么我们如何实现呢?答案也很简单,那就是利用这个返回的Arrays$ArrayList对象再new一个java.util.ArrayList对象:

String[] a = {"a", "b", "c"};
List<String> list = new ArrayList<>(Arrays.asList(a));

另外,工具类Collections也提供了一个方法可以实现数组到集合的拷贝

String[] s = {"a", "b", "c"};
List<String> list = new ArrayList<>();
Set<String> set = new HashSet<>();Collections.addAll(list, s);
Collections.addAll(set, s);

这个方法要求数组中的元素必须是对象类型的,那么如果是基本类型的数组,如何转换呢?


基本数据类型数组转集合

由于java的集合是用于统一组织管理多个对象的,其内部储存的只是各个对象的引用,而8种基本数据类型不是Object的子类,即不是对象类型,所以不能把它们放到集和之中。从JDK1.5开始,为了方便操作,java支持基本数据类型的autoboxing/unboxing,那么如果使用Arrays.asList()方法,能不能将int[]转成List<Integer>的集合呢? 让我们来试验一下

int[] a = { 1, 2, 3 };
String[] b = { "a", "b", "c" };List listA = Arrays.asList(a);
List listB = Arrays.asList(b);System.out.println(listA);
System.out.println(listA.get(0).getClass().getName());
System.out.println(listA.size());System.out.println(listB);
System.out.println(listB.get(0).getClass().getName());
System.out.println(listB.size());

输出结果如下

[[I@31b47bff]
[I
1
[a, b, c]
java.lang.String
3

可以看到数组b中存储的是String类型,结果符合预期,实现了对象数组到集合的转换,而对于数组a,asList()也能正确执行,但是结果却很怪异,首先listA被打印出来的字符串是[I@31b47bff,看形式就知道这就是Object中定义的toString()方法的结果,也即是getClass().getName() + ‘@’ + Integer.toHexString(hashCode()),那么[I是什么呢?其实这个符号表示的是一维数组,再看listA.size()等于1,说明listA中只有一个对象,这对象就是数组a,没错,数组本身也是一个对象类型的,asList()方法直接把int[]数组a当做一个完整的对象了,然后把这个唯一的对象放到了List集合中。在大部分情况下,这种转换并不符合我们的本意,而且不报任何警告或异常,所以在使用时要特别注意这一点。

那么我们如何实现把一个int[]数组转成集合呢?

有两种基本选择,要么先把int[]数组转成Integer[]数组,然后使用asList()方法,要么就用for循环把数组元素一个一个地添加到集合中,这个过程会自动装箱。

至此,数组与collection集合之间的相互转换的方式和细节就比较清晰了。

数组与集合的相互转换相关推荐

  1. 使用Stream流实现数组与集合的相互转换

    // 将 List 元素存储到数组中 List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5)); i ...

  2. JAVA SE学习day_11:集合的相关应用、增强型for循环、foreach方法、数组与集合的相互转换

    一.集合的相关应用 1.1 将一个集合加到另一个集合上 boolean addAll(Collection c) 将给定集合中的所有元素添加到当前集合中.调用方法之后只要c1集合发生变化就返回true ...

  3. 数组和集合的相互转换

    1.集合转换为数组 package cn;import java.util.ArrayList;/*** 集合转换为数组*/ public class ListToArrayDemo {public ...

  4. java的集合和数组_Java集合和数组的区别

    Java集合和数组的区别 集合和容器都是Java中的容器. 区别 数组特点:大小固定,只能存储相同数据类型的数据 集合特点:大小可动态扩展,可以存储各种类型的数据 转换 数组转换为集合: Arrays ...

  5. 《C#精彩实例教程》小组阅读09 -- C#数组与集合

    本微信图文详细介绍了C#的数组与集合.

  6. c#sort升序还是降序_C#中对数组或集合进行升序或降序排序

    在.net framework中,数组和集合都实现了用于排序的方法sort(),数组通过调用Array.Sort(数组名)排序,集合通过调用 集合对象.Sort()方法排序. 默认的排序是使用升序排列 ...

  7. 转载--编写高质量代码:改善Java程序的151个建议(第5章:数组和集合___建议60~64)

    阅读目录 建议60:性能考虑,数组是首选 建议61:若有必要,使用变长数组 建议62:警惕数组的浅拷贝 建议63:在明确的场景下,为集合指定初始容量 建议64:多种最值算法,适时选择 噢,它明白了,河 ...

  8. 二维数组,锯齿数组和集合 C# 一维数组、二维数组(矩形数组)、交错数组(锯齿数组)的使用 C# 数组、多维数组(矩形数组)、锯齿数组(交叉数组)...

    二维数组,锯齿数组和集合 一.二维数组 二维数组: 一维数组----豆角 二维数组----表格 定义: 1.一维数组: 数据类型[] 数组变量名 = new 数据类型[数组长度]; 数据类型[] 数组 ...

  9. 黑马程序员--数组与集合互相转变

    --------- android培训.java培训.期待与您交流! --------- 7 数组与集合的相互转变 7.1 数组转变成集合 1,为什么把数组转变成集合呢?有什么好处呢? 数组转变成集合 ...

最新文章

  1. 2.3 KNN-采用机器学习库来预测鸢尾花的分类
  2. c++的STL中的map(哈希表)与unordered_map
  3. 递归学习 斐波那契 java代码实现
  4. [转]ubuntu network is unreachable 解决记
  5. png免扣半透素材,让你轻松设计出漂亮的海报!
  6. Android数据的几种存储方式---------SharePreferences(轻量的以键值对) 的使用
  7. java log info乱码_跟光磊学Java开发-Java开发常用API的使用
  8. ubuntu下载Linux 内核,ubuntu下载linux内核源码
  9. 微信小程序地图实现展示路线路
  10. PR短视频转场预设 60个摇晃抖动效果过渡合集
  11. windows系统升级
  12. 智能电子快递面单系统
  13. IIS 环境下 PHP无法显示错误信息------500错误
  14. 理解计算:从根号2到AlphaGo 第3季 神经网络的数学模型
  15. 【ZDNS分享】广电行业(四)DHCP解决方案
  16. java 大小写匹配_大写字母的Java正则表达式
  17. conda安装 tensorflow-gpu出现错误
  18. mmdetection训练自己的数据并评估mAP
  19. 《计算几何》学习笔记
  20. 微信小程序内七牛云图片显示异常

热门文章

  1. 规格书-蓝牙模组-xmBLE-17H66B2
  2. word表格中长表格每页显示表头/取消表头显示
  3. 2020,企业能够提高自身抵抗风险的“免疫力”至关重要!
  4. Java(79):Character.isDigit() 判断输入的数据是否大于等于零整数
  5. 融合会计服务与金融服务,银账通造福千万小微企业!
  6. 前端基于vue企业微信JS-SDK语音识别功能开发(同公众号)
  7. java新闻网站制作_java+ssh+mysql新闻网站(制作精美)
  8. 银行笔试-暑期实习-邮储
  9. iOS后台任务的影响
  10. Ubuntu安装X11