Fellow Travellers

Java 8 之collection细节

万世威
字数统计: 1.2k阅读时长: 6 min
2018/10/22 Share

Collection细节

一、指定初始容量

二、asList的缺陷

2.1 避免使用基本数据类型数组转换为列表

1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
int[] ints = {1,2,3,4,5};
List list = Arrays.asList(ints);
System.out.println("list'size:" + list.size());
}
}
outPut: list'size:1

因为该实例是将int 类型的数组当做其参数,而在Java中数组是一个对象,它是可以泛型化的。所以该例子是不会产生错误的。既然例子是将整个int 类型的数组当做泛型参数,那么经过asList转换就只有一个int 的列表了

1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
Integer[] ints = {1,2,3,4,5};
List list = Arrays.asList(ints);
System.out.println("list'size:" + list.size());
}
}
outPut: list'size:5

2.2 asList产生的列表不可操作

1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
Integer[] ints = {1,2,3,4,5};
List list = Arrays.asList(ints);
list.add(6);//exception
}
}
//Exception in thread "main" java.lang.UnsupportedOperationException

因为 此 List 是 Arrays的一个内部类,

1
2
3
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
java.util.Arrays$ArrayList
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);
}
}

里面并没有 add 方法,所以会报错。

三、subList的缺陷

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<Integer>();
list1.add(1);
list1.add(2);
//通过构造函数新建一个包含list1的列表 list2
List<Integer> list2 = new ArrayList<Integer>(list1);
//通过subList生成一个与list1一样的列表 list3
List<Integer> list3 = list1.subList(0, list1.size());
//修改list3
list3.add(3);
System.out.println("list1 == list2:" + list1.equals(list2));
System.out.println("list1 == list3:" + list1.equals(list3));
}
}
//list1 == list2:false
//list1 == list3:true

3.1 subList返回仅仅只是一个视图

由subList 源码,可以发现问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
//截取部分
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;

SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
}
//this.parent = parent;而parent就是在前面传递过来的list,也就是说this.parent就是原始list的引用
//再看 add 方法
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);//引用原始list
this.modCount = parent.modCount;
this.size++;
}
//同理还有 remove 等,操作的都是原始list里面的数据
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}

so,subList返回的只是原列表的一个视图,它所有的操作最终都会作用在原列表上

3.2 subList生成子列表后,不要试图去操作原列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test {
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<Integer>();
list1.add(1);
list1.add(2);
//通过subList生成一个与list1一样的列表 list3
List<Integer> list3 = list1.subList(0, list1.size());
//修改list3
list1.add(3);
System.out.println("list1'size: " + list1.size());
System.out.println("list3'size:" + list3.size());
}
}
//Exception in thread "main" java.util.ConcurrentModificationException
//fail-fast 异常机制
private void checkForComodification() {
//修改原始list,会导致两个list的修改次数不一致
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}

3.3 推荐使用subList处理局部列表

如 获取一堆数据后,需要删除某段数据。例如,有一个列表存在1000条记录,我们需要删除100-200位置处的数据

1
2
3
4
5
6
7
public void clear() {
modCount++;
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
list.subList(100, 200).clear();
CATALOG
  1. 1. Collection细节
    1. 1.1. 一、指定初始容量
    2. 1.2. 二、asList的缺陷
      1. 1.2.1. 2.1 避免使用基本数据类型数组转换为列表
      2. 1.2.2. 2.2 asList产生的列表不可操作
    3. 1.3. 三、subList的缺陷
      1. 1.3.1. 3.1 subList返回仅仅只是一个视图
      2. 1.3.2. 3.2 subList生成子列表后,不要试图去操作原列表
      3. 1.3.3. 3.3 推荐使用subList处理局部列表