Troubles caused by Java ArrayList subList
Business scenarios:
Lists with n numbers need to sort the first n-1 numbers and then put the last number into it to output a new List.
The code is implemented as follows (non-original business code):
List<Integer> list = Lists.newArrayList();
//Initialize arrays
for (int i = 1; i < 11; i++) {
list.add(i);
}
for (int i = 0; i < 10; i++) {
List<Integer> listNew = new ArrayList<>();
listNew = list.subList(0, list.size() - 1);
listNew.sort(new Ordering<Integer>() {
@Override
public int compare(Integer integer, Integer t1) {
return integer - t1;
}
}
);
listNew.add(list.get(list.size() - 1));
System.out.println("after loop " + i + " listNew size : " + listNew.size());
System.out.println("after loop " + i + " handle list size : " + list.size());
}
Cause problems
Each iteration of sublist causes the size of the original array list to increase continuously after add ing, which changes the size of the original array, and has an unpredictable impact on the use of lists later.
Problem reason
By looking at the source code of ArrayList, it is found that the sublist function is not called by returning a new List and returning a SubList object with the following specific code:
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
SubList is an internal class of ArrayList. The source code of the analysis of Sublist is as follows:
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;
}
ยทยทยท
}
Through the construction method, we can see that subList has a reference to the parent ArrayList, that is, to the original ArrayList, and stores fromIndex and size relative to the original array. Next, we will focus on the add,set,remove methods related to sublist.
When we call the add method on a SubList object, we actually call the add method of AbstractList with the following source code:
public boolean add(E e) {
add(size(), e);
return true;
}
The SubList add method is then called with the following source code:
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
From the source code of sublist, you can see clearly that every time you add to a SubList, the bottom layer is to add an element to the ArrayList of the parent class and add its size+1. Note that the ArrayList for the parent class does not add this element at the end, but after the toIndex for the child class, to ensure continuity.
The source code of the SubList set method is as follows:
public E set(int index, E e) {
rangeCheck(index);
checkForComodification();
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue;
}
The source code for the SubList remote method is as follows
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
From the above source code, you can see that you need to be cautious about element manipulation of SubList, unless you can clearly understand the impact of the operation, it is not recommended to use the List generated by calling the subList method. The following methods can be used to avoid:
List<Integer> listNew = new ArrayList<>();
listNew.addAll(list.subList(0, list.size() - 1));