发表于: 2018-10-14 21:15:05
1 550
一、今天完成的事情
向ArrayList中添加一个集合对象
因为ArrayList的底层使用数组来实现的,所以要在ArrayList中添加一个集合对象要将其转成数组的形式再进行操作
然后判断是否扩容、通过arraycopy方法把要添加的集合对象的数组形式添加到elementData数组后面,最后更新的size的值,如果要添加的集合对象为空返回false。
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
toArray方法把集合对象转成数组的原理就是把集合对象底层的数组复制一份作为返回值
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
Arrays类中的copyOf方法,其可以传入三个参数,要复制的数组,新复本的长度,新复本的类型
判断要指定新复本的类型是否是Object[]类型,是的话直接new出一个指定长度的Object[],如果不是则利用反射创建一个指定类型的数组
然后调用底层的arraycopy将源数组复制一份给新创建的数组,长度取源数组的长度和指定的长度最小值。
它有一个重载的方法,当不指定新的类型时,将源数组的类型作为参数传入方法中。
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
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;
}
在指定位置添加一个集合对象
涉及到索引首先要进行越界检查,到检查是否越界都和重载方法一样,在指定位置添加一个几个对象和在指定位置添加一个元素类似,要把指定位置后面的元素都向后移动要添加集合的长度,空出插入集合的位置,然后在这个空位中插入集合。最后更新size的值,如果Collection中没有元素返回false。
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
ArrayList中向数组中添加元素的方法就是这些
删除指定位置的元素
检查是否越界,因为移除元素改变了结构,要改变modCount的值,调用elementData方法获取数组中该位置的元素,这里获取指定下标的元素单独调用一个方法并且强转类型返回而不是直接获取elementData指定下边的元素的原因是,ArrayList在实例化时传入一个集合或者添加元素是传入一个集合,这个集合的数组类型都是Object的,所以要把它强转返回。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
还可以删除指定元素第一次出现的位置的数据,如果有返回true,否则返回false
遍历比较数组元素之前有一步判断要删除的元素是否是null,遍历数组找到元素所在位置的下标,然后调用fastRemove方法删除指定位置的元素。
fastRemove方法和Remove的区别是方法中少了判断越界,查找元素的步骤,相对来说这样会节省很多时间,所以方法名叫快速删除。
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
批量删除与指定集合交集的元素
Objects工具类的方法判断一个对象是否是null,如果是null抛出空指针异常
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
当batchRemove方法传入false时,删除与指定集合中元素相同的元素,如果传入的是true,则留下这个元素。
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
遍历ArrayList中的数组,把不在指定集合中的元素重新排列在数组前面,注解的意思是contains方法可能会报错,导致遍历没有完成,把出现异常之后没有遍历完的元素全部移动到重新排列的元素后面。
这个contains方法会出现什么异常导致遍历终止,为什么异常之后的元素不再判断集合中是否有剩下的元素。
把重新排列的元素后面的位置清空,要进行清空的位置长度为size-w,所以修改次数应该增加size-w次。
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
修改,判断越界,修改指定位置的元素,返回原来在这个位置元素的值
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
查找,检查越界,调用获取元素的方法,并返回。
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
清空,增加修改次数,把数组全部元素清空
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
判空
public boolean isEmpty() {
return size == 0;
}
二、明天计划的事情
迭代器,比较一下几种遍历方法,LinkedList
三、遇到的问题
向ArrayList中添加一个数组
public void desTest() {
ArrayList<Integer> a = new ArrayList<>();
a.add(9);
Integer[] b = {5,5,5};
a.addAll(Arrays.asList(b));a.forEach(logger::info);
}
通过Array的工具类方法asList把指定数组数组变为ArrayList
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
这个方法是传入可变个形参,然后利用ArrayList的构造函数构造一个ArrayList
实际上可变个形参的形式是一个数组,但是ArrayList的构造函数没有传入数组的形式,不知道这里究竟是怎么实现的。
copyOf方法中比较传入类型和Object[]时要进行强转 (Object)newType == (Object)Object[].class
要使用==来比较两个类型是否相等,等号两边的类型必须是父子关系,否则编译器就会报错
Object是所有类的父类,但是直接比较还是会报错
但是和一个实例化的Object进行比较编译器不会报错
或者这种方式进行比较
不知道为什么,可能Object的继承关系在类中不是显示继承的原因???暂时没找到原因
四、收获
ArrayList
评论