java 集合 深度复制多种实现方式和使用注意事项
今天遇到一个问题:对一个集合进行深度复制
最先想到的是利用集合工具类的copy()方法,但是它出现了一些问题
Collections.copy使用注意事项
示例代码:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class CollectionsTest {
public static void main(String[] args) {
// 创建一个集合list
String[] array = { "1", "2", "3", "4", "5" };
List<String> list = Arrays.asList(array);
// 创建一个新的集合dest
List<String> dest = new ArrayList<String>();
// 把list集合中的元素复制到dest集合中
Collections.copy(dest, list);
System.out.println(dest);
}
}
运行结果:
Exception in thread "main" java.lang.IndexOutOfBoundsException: Source does not fit in dest
at java.util.Collections.copy(Collections.java:556)
at com.CollectionsTest.main(CollectionsTest.java:17)
源码分析:
/**
* Copies all of the elements from one list into another. After the
* operation, the index of each copied element in the destination list
* will be identical to its index in the source list. The destination
* list must be at least as long as the source list. If it is longer, the
* remaining elements in the destination list are unaffected. <p>
*
* This method runs in linear time.
*
* @param <T> the class of the objects in the lists
* @param dest The destination list.
* @param src The source list.
* @throws IndexOutOfBoundsException if the destination list is too small
* to contain the entire source List.
* @throws UnsupportedOperationException if the destination list's
* list-iterator does not support the <tt>set</tt> operation.
*/
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
结论: dest.size()的长度不能小于srcSize();目标列表必须至少与源列表一样长。如果更长,则目标列表中的其余元素不受影响。
修改代码,对dest初始列表容量:
public class CollectionsTest {
public static void main(String[] args) {
// 创建一个集合list
String[] array = { "1", "2", "3", "4", "5" };
List<String> list = Arrays.asList(array);
// 创建一个新的集合dest
List<String> dest = new ArrayList<String>(5);
// 把list集合中的元素复制到dest集合中
Collections.copy(dest, list);
System.out.println(dest);
}
}
运行结果:
问题依旧出现,当打印dest的size,结果为 0
分析ArrayList源码:
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
结论:构造具有指定初始容量的空列表,实际上没有定义元素,元素数量为0
解决方案:
一个长度是原集合大小的数组,元素的个数是size,元素初始值为null。
public class CollectionsTest {
public static void main(String[] args) {
// 创建一个集合list
String[] array = { "1", "2", "3", "4", "5" };
List<String> list = Arrays.asList(array);
// 创建一个新的集合dest
List<String> dest = Arrays.asList(new String[list.size()]);
// 把list集合中的元素复制到dest集合中
Collections.copy(dest, list);
System.out.println(dest);
}
}
运行结果:
[1, 2, 3, 4, 5]
Arrays.asList()使用注意事项
Arrays.asList()
Arrays是java容器相关操作的工具类,asList()方法将数组转换为集合。
在使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,否则会抛出UnsupportOperationException异常。
示例代码:
public class CollectionsTest {
public static void main(String[] args) {
// 创建一个集合list
String[] array = { "1", "2", "3", "4", "5" };
List<String> list = Arrays.asList(array);
// 创建一个新的集合dest
List<String> dest = Arrays.asList(new String[list.size()]);
// 把list集合中的元素复制到dest集合中
Collections.copy(dest, list);
dest.remove("1");
System.out.println(dest);
}
}
运行结果:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.remove(AbstractList.java:161)
at java.util.AbstractList$Itr.remove(AbstractList.java:374)
at java.util.AbstractCollection.remove(AbstractCollection.java:293)
at com.CollectionsTest.main(CollectionsTest.java:19)
源码分析:
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);
}
@Override
public int size() {
return a.length;
}
@Override
public Object[] toArray() {
return a.clone();
}
@Override
@SuppressWarnings("unchecked")
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;
}
@Override
public E get(int index) {
return a[index];
}
@Override
public E set(int index, E element) {
E oldValue = a[index];
a[index] = element;
return oldValue;
}
@Override
public int indexOf(Object o) {
E[] a = this.a;
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;
}
@Override
public boolean contains(Object o) {
return indexOf(o) != -1;
}
@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(a, Spliterator.ORDERED);
}
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
for (E e : a) {
action.accept(e);
}
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
E[] a = this.a;
for (int i = 0; i < a.length; i++) {
a[i] = operator.apply(a[i]);
}
}
@Override
public void sort(Comparator<? super E> c) {
Arrays.sort(a, c);
}
}
结论:
由Arrays.asList() 返回的是Arrays的内部类ArrayList, 而不是java.util.ArrayList。
Arrays的内部类ArrayList和java.util.ArrayList都是继承AbstractList抽象类,都包含:remove、add等方法。
AbstractList中默认实现throw UnsupportedOperationException而且不作任何操作。
java.util.ArrayList重新实现了这些方法
Arrays的内部类ArrayList没有重新,所以会抛出异常。
解决方案:
参照下方使用构造方法复制
其他集合深度复制方法:
- 遍历循环复制
- 使用构造方法复制
List<String> dest = new ArrayList<String>(list);