java集合框架>>>>ArrayList

目录

前言:

构造方法:

常用方法及简单源码分析:

1. public int size()

2. public boolean isEmpty()

 3. public boolean contains(Object o)

4. public int indexOf(Object o)

5. public int lastIndexOf(Object o)

6. public Object clone()

7.  public Object[] toArray()

8.  public  T[] toArray(T[] a)

9. public E get(int index)

10. public E set(int index, E element)

11. public boolean add(E e)

12. public void add(int index,E element)

13. public E remove(int index)

14. public boolean remove(Object o)

16. public boolean addAll(Collection collection)

17. public boolean addAll(int index,Collection collection)

18. protected void removeRange(int fromIndex,int toIndex)

 19. public boolean removeAll(Collection c)

20. public boolean retainAll(Collection c)

21. public ListIterator listIterator(int index)

22. public Iterator iterator()

23. public List subList(int fromIndex, int toIndex)

24. public void forEach(Consumer action)

25. public boolean removeIf(Predicate filter)

26. public void sort(Comparator c)

总结:


前言:

java中我们经常会用到List,Map,Set,Vector这些集合框架,却没有认真学习过,借这篇博客,认真复习一下

类的声明:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable

其实ArrayList就是一种数据结构,下面是ArrayList的简单结构

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
   /**
     * 实现序列化接口(Seriablizable)必须默认声明一个long类型serialVersionUID
     */
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     * 集合的数据元素
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     * 集合的长度
     * @serial
     */
    private int size;

    //下面是构造方法以及其他方法
}

也就是说所谓的ArrayList,实质上也就是两部分组成的,一个是存储集合元素的一个对象数组elementData[],另一个就是这个集合的长度,或者说大小,可以通过.size()方法获取集合的长度

除此之外,ArrayList还定了一个几个变量

//  默认的集合的容量为10
private static final int DEFAULT_CAPACITY = 10;

//  当创建一个初识容量的ArrayLIst时,当输入的值为0时,数据元素由这个集合赋值
private static final Object[] EMPTY_ELEMENTDATA = {};

//  采用默认的空的构造方法时,数据元素由这个集合赋值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

构造方法:

ArrayList共有三种构造方法

  • public ArrayList(int initialCapacity),                    构造具有指定初始容量的空列表。
  • public ArrayList(),                                               构造一个初始容量为十的空列表。
  • public ArrayList(Collection<? extends E> c),     构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。

创建一个长度为10的一个ArrayList

/**
  * Shared empty array instance used for default sized empty instances. We
  * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
  * first element is added.
  */
 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
  * Constructs an empty list with an initial capacity of ten.
  */
 public ArrayList() {
     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
 }

首先ArrayList定义了一个DEFAULTCAPACITY_EMPTY_ELEMENTDATA对象数组,且初始值为{},也就是空,所以创建的集合的长度为这个对象数组的长度,也就是0 

根据Collection创建一个ArrayList

Collection<String> collection = new LinkedList<String>();
collection.add("张三");
collection.add("李四");
ArrayList<String> arrayList = new ArrayList<String>(collection);

创建一个指定长度的可变数组

根据传入初识长度创建集合

如果initialCapacity大于0,直接创建一个指定容量的对象数组即可

如果initialCapacity等于0 ,将空的对象数组赋值给elementData

如果initialCapacity小于0,则抛出IllegalArgumentException异常

ArrayList<String> arrayList = new ArrayList<String>(5);

常用方法及简单源码分析:

1. public int size()

返回此列表中的元素数

实质上size方法就是ArrayList中size属性的get方法

    /**
     * Returns the number of elements in this list.
     *
     * @return the number of elements in this list
     */
    public int size() {
        return size;
    }

2. public boolean isEmpty()

如果此列表不包含元素,则返回 true

    /**
     * Returns <tt>true</tt> if this list contains no elements.
     *
     * @return <tt>true</tt> if this list contains no elements
     */
    public boolean isEmpty() {
        return size == 0;
    }

 3. public boolean contains(Object o)

    /**
     * Returns <tt>true</tt> if this list contains the specified element.
     * More formally, returns <tt>true</tt> if and only if this list contains
     * at least one element <tt>e</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
     *
     * @param o element whose presence in this list is to be tested
     * @return <tt>true</tt> if this list contains the specified element
     */
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

如果此列表包含指定的元素,则返回true 。 更正式地说,返回true当且仅当此列表包含至少一个元素e这样(o==null ? e==null : o.equals(e))。

contains方法也就是间接的调用了indexOf方法,如果indexOf返回值>=0,也就是说当前元素存在,返回true,否则返回false

4. public int indexOf(Object o)

 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1

    /**
     * Returns the index of the first occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the lowest index <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     */
    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;
    }

因为ArrayList允许null值存在,因此在获得目标对象的索引值时需要考虑对象是否为空,如果为null,则简单循环遍历整个ArrayList,如果遇到null值,则返回当前索引,如果为查找到,则返回-1

如果目标对象不是null,则循环整个ArrayList,调用equals方法,判断两个对象是否相同,如果相同则返回当前索引,如果未查找到,则返回-1 

5. public int lastIndexOf(Object o)

返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1

这个和indexOf原理相同,只是循环从ArrayList的尾部开始,也就是从size-1开始循环

6. public Object clone()

返回此ArrayList实例的浅拷贝。 (元素本身不被复制。)

    public static void main(String[] args) {
        ArrayList<Friend> friends = new ArrayList<Friend>();
        friends.add(new Friend("张三",15));
        friends.add(new Friend("李四",25));
        ArrayList<Friend> copyFriends = (ArrayList<Friend>)friends.clone();
        Utils.showObjectList(friends);
        Utils.showObjectList(copyFriends);
    }

showObjectList()是写的一个工具方法,显示List集合数据

    public static void showObjectList(List<Friend> list){
        System.out.println("展示列表数据:");
        for(Friend obj:list){
            System.out.println(obj.toString());
        }
    }

 运行结果:

展示列表数据:
Friend{name='张三', age=15}
Friend{name='李四', age=25}
展示列表数据:
Friend{name='张三', age=15}
Friend{name='李四', age=25}

7.  public Object[] toArray()

以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。

返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的数组)。 因此,调用者可以自由地修改返回的数组。

这个方法实际中应用比较少,由于返回的是对象数组,想获得实际对象不能直接将整个数组转化,会抛出ClassCastException异常,必须遍历逐个强转,然后在访问或者修改

package test;

import java.util.ArrayList;
import bean.Friend;

public class ArrayListDemo implements Cloneable {

    public static void main(String[] args) {
        ArrayList<Friend> list = new ArrayList<Friend>();
        list.add(new Friend("张三", 15));
        list.add(new Friend("李四", 25));
        list.add(new Friend("王五", 35));
        
        System.out.println("第一种方式:");
        Object[] objects = list.toArray();
        for(Object object:objects){
            Friend friend = (Friend) object;
            System.out.println(friend);
        }
    }
}

运行结果:

第一种方式:
Friend{name='张三', age=15}
Friend{name='李四', age=25}
Friend{name='王五', age=35}

8.  public <T> T[] toArray(T[] a)

相比第一种,这一种方法应用更多

以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。 如果列表适合指定的数组,则返回其中。 否则,将为指定数组的运行时类型和此列表的大小分配一个新数组。

package test;

import java.util.ArrayList;
import bean.Friend;

public class ArrayListDemo implements Cloneable {

    public static void main(String[] args) {
        ArrayList<Friend> list = new ArrayList<Friend>();
        list.add(new Friend("张三", 15));
        list.add(new Friend("李四", 25));
        list.add(new Friend("王五", 35));
        System.out.println("第二种方式:");
        Friend[] friends = (Friend[]) list.toArray(new Friend[list.size()]);
        for (Friend friend : friends) {
            System.out.println(friend);
        }
    }
}

运行结果:

第二种方式:
Friend{name='张三', age=15}
Friend{name='李四', age=25}
Friend{name='王五', age=35}

9. public E get(int index)

返回此列表中指定位置的元素,返回类型由创建ArrayList所声明的泛型决定

抛出IndexOutOfBoundsException()【注意下标不要越界】

10. public E set(int index, E element)

用指定的元素替换此列表中指定位置的元素。

用法很简单

ArrayList<Friend> list = new ArrayList<Friend>();
list.set(1,new Friend("张三",99));

抛出IndexOutOfBoundsException()【注意下标不要越界】

11. public boolean add(E e)

当创建了一个空的ArrayList时,在向这个集合中添加元素是,函数调用关系如下

add() ---> ensureCapacityInternal() --> ensureExplicitCapacity( calculateCapacity() ) --> grow()

紫色部分是calculateCapacity()方法的返回值作为参数,大家根据这个函数调用关系看一下源码,我弄在一块,方便大家看

直接在备注中针对源码讲解吧

ArrayList<String> list = new ArrayList<String>();
list.add("Something");

/*
 * ArrayList的add方法,在这个方法中首先调用ensureCapacityInternal()方法,将集合当前大小加一作为参数,也就是0+1=1
 * 【先看完底下代码分析,在看这里】
 * 当ensureCapacityInternal方法执行结束后,也就是扩充完ArrayList的容量之后,进行赋值操作
 * elementData[size++],size此时为0,也就是elementData[0] =e;同时将size++,变为1
 * 完成add操作,返回true
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

/*
 * 此时参数minCapacity的值为size+1,也就是传递过来的1,,然后调用ensureExplicitCapacity
 * 首先调用calculateCapacity,然后将返回值作为参数
 * 因此先分析一下calculateCapacity这个方法
 */
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

/*
 * 将数据元素elementData和minCapacity作为参数,elementData为空对象数组,minCapacity是1
 * 第一层判断,由于ArrayList是采用的默认的空的构造方法
 * 因此此时elementData就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * 不太清楚的可以看一下上面构造方法中默认的空的构造方法的分析
 * 因此返回的是Math.max()这个方法,也就是返回的DEFAULT_CAPACITY,和minCapacity的最大值
 * 再来看一下这个DEFAULT_CAPACITY,发现默认创建的数组的长度为10,因此返回的值为10
 * 【之前看API还以为写错了,怎么创建的空的长度为10,看过源码才清楚】
 */
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

/*
 * 在ensureCapacityInternal方法中调用过这个方法,此时minCapacity应该是calculateCapacity返回的10
 * modCount类似于下标,初识为0,此时变为1
 * protected transient int modCount = 0;这是源码中声明的,作为依据
 * minCapacity为10,elementData还是空的对象数组,因此长度为0,满足if条件
 * minCapacity - elementData.length=10>0因此调用grow方法,参数是10
 */
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

/*
 * minCapacity为10
 * 定义两个参数,oldCapacity初识值为集合的长度也就是0
 *                      newCapacity初始值为0+(0>>1),>>表示右移,也就是除以二,因此值也是0
 * newCapacity - minCapacity = -10,满足第一个if条件,因此newCapacity此时为10
 * newCapacity - MAX_ARRAY_SIZE不满足,因此MAX_ARRAY_SIZE是最大整数-8,总之是一个很大的数
 * 调用Arrays类的copyOf方法给elementData赋值, 但elementData此时为空数组
 * 默认值为null,也就是说此时elementData是一个容量为10,元素均为null的一个对象数组
 */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

12. public void add(int index,E element)

ArrayList的索引从0开始,当前add方法,是将元素插入到index索引处,同时将原来元素全部向后偏移一个单位,举个例子

import java.util.ArrayList;
import java.util.List;

public class ArrayListDemo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("第一个");
		list.add("第二个");
		list.add("第三个");
		System.out.println("增加前:");
		show(list);
		list.add(1,"新节点");
		System.out.println("增加后:");
		show(list);
	}
	/**
	 * 定义一个遍历显示的方法
	 */
	public static void show(List<String> list) {
		for(int i=0; i<list.size(); i++) {
			System.out.println("        "+list.get(i));
		}
	}
}

首先创建一个List集合,然后通过普通add方法,插入三个节点,(先备注那一句),然后测试效果,定义了一个静态方法,遍历显示,结果如下

增加前:
        第一个
        第二个
        第三个
增加后:
        第一个
        新节点
        第二个
        第三个

List集合从0开始,索引为1的正好为"第二个",因此"新节点",会插入到"第一个"和"第二个"之间

将指定的元素插入此列表中的指定位置。向右移动当前位于该位置的元素(如果有)以及所有后续元素(将其索引加 1)

13. public E remove(int index)

删除该列表中指定位置的元素。 将任何后续元素移动到左侧(从其索引中减去一个元素)。

    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;
    }

首先通过rangeCheck方法检验index是否越界,将index位置出的元素存到oldValue中,elementData方法其实就是get(index)方法

然后numMoved是移动的数组的大小,比如说此时集合长度为5,想要删除索引为1的元素,因此需要将索引为2,3,4的元素索引分别减小一,因此需要移动的元素个数为size-index-1=3,索引的减小是通过System.arraycopy方法实现的

最后将最后一个元素置为null,因为修改index后面所有元素的索引是通过arraycopy实现的,也就是说把index后面的元素整体向前赋值覆盖之前的值,但是最后一个位置还是原来的值,因此需要单独考虑,将其设置为null

最后返回oldValue

14. public boolean remove(Object o)

从列表中删除指定元素的第一个出现(如果存在)。 如果列表不包含该元素,则它不会更改。 更正式地,删除具有最低索引i的元素,使得(o==null ? get(i)==null : o.equals(get(i))) (如果存在这样的元素)。

复习一下三目运算符

如a > b ? a : b这种形式,如果a=2,b=1,则这个表达式结果为a,也就是2,反之则为b,也就是1

在如,o==null ? get(i)==null : o.equals(get(i))这种形式

首先判断o是不是null,如果是null,则返回为get(i)==null,如果不是null,则返回o.equals(get(i))

也就是说如果你移除null元素,就会去遍历所有元素,如果匹配到null元素,则移除这个元素,并且返回true

如果你移除的节点不是null,则会遍历所有元素,待删除节点调用equal(Object类的方法)判断是否与集合中元素相同,如果相同则移除节点,同时返回true

API给出的解释基本就是源码具体实现思路

    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;
    }

首先判断元素是否是null,如果是null则遍历集合,找到null值后根据匹配的索引,调用fastRemove方法进行删除

如果元素不是null,也是通过遍历找到匹配索引,在调用fastRemove方法进行删除

实质fastRemove就是remove(index),也就是说直接删除对象,就是通过index方法获取对象索引,然后在调用remove(index)方法进行删除

例子:

package test;

import java.util.ArrayList;
import bean.Friend;
import utils.Utils;

public class ArrayListDemo implements Cloneable {

    public static void main(String[] args) {
        ArrayList<Friend> list = new ArrayList<Friend>();
        list.add(new Friend("张三", 15));
        list.add(new Friend("李四", 25));
        list.add(new Friend("王五", 35));
        Friend remove = new Friend("张三",15);
        list.remove(remove);
        Utils.showObjectList(list);
    }
}

运行结果:

 展示列表数据:
Friend{name='张三', age=15}
Friend{name='李四', age=25}

15. public void clear()

从列表中删除所有元素。 此呼叫返回后,列表将为空。

    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

考虑一个问题,为什么不直接将elementData赋值为,EMPTY_ELEMENTDATA,在将size置为0呢??

欢迎知道的大佬指点!!!

16. public boolean addAll(Collection<? extends E> collection)

按照指定 collection 的迭代器所返回的元素顺序,将该 collection 中的所有元素添加到此列表的尾部。如果正在进行此操作时修改指定的 collection ,那么此操作的行为是不确定的。(这意味着如果指定的 collection 是此列表且此列表是非空的,那么此调用的行为是不确定的).这是API中进行的解释

通俗点来说就是,将一个Collection(Collection的子类,如ArrayList或者其他实现类的集合)追加到ArrayList集合的后面,并且在这个过程中不可修改将作为参数传递过来的Collection,

import java.util.ArrayList;
import java.util.List;

public class ArrayListDemo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("第一个");
		list.add("第二个");
		list.add("第三个");
		System.out.println("添加前:");
		show(list);
		List<String> temp = new ArrayList<String>();
		temp.add("添加项1");
		temp.add("添加项2");
		System.out.println("操作的结果"+list.addAll(temp));
		System.out.println("添加后:");
		show(list);
	}
	/**
	 * 定义一个遍历显示的方法
	 */
	public static void show(List<String> list) {
		for(int i=0; i<list.size(); i++) {
			System.out.println("        "+list.get(i));
		}
	}
}

运行结果

添加前:
        第一个
        第二个
        第三个
操作的结果true
添加后:
        第一个
        第二个
        第三个
        添加项1
        添加项2

17. public boolean addAll(int index,Collection<? extends E> collection)

从指定的位置开始,将指定 collection 中的所有元素插入到此列表中。向右移动当前位于该位置的元素(如果有)以及所有后续元素(增加其索引)。新元素将按照指定 collection 的迭代器所返回的元素顺序出现在列表中

其中index是插入集合中首个元素的索引,collection是插入的集合

System.out.println("操作的结果"+list.addAll(1,temp));

这个和上一个addAll差不多,只需要将参数修改一下,因此不给出全部代码了

以下是运行结果:

添加前:
        第一个
        第二个
        第三个
操作的结果true
添加后:
        第一个
        添加项1
        添加项2
        第二个
        第三个

18. protected void removeRange(int fromIndex,int toIndex)

看到这些protected有一点懵,学了好久的面向对象,最基本的修饰符还有点不明确,接机复习一下

  • default (即缺省,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。

  • private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

  • public : 对所有类可见。使用对象:类、接口、变量、方法

  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之间的所有元素。向左移动所有后续元素(减小其索引)。此调用将列表缩短了 (toIndex - fromIndex) 个元素。(如果 toIndex==fromIndex,则此操作无效。)

和remove方法实现原理相似,先拷贝元素,然后将尾部元素置为null

为啥只有其子类才能访问,日后补充,同时欢迎大佬指点!!!

 19. public boolean removeAll(Collection<?> c)

从此列表中删除指定集合中包含的所有元素。

想来用法也比较简单,看下面例子

package test;

import java.util.ArrayList;
import bean.Friend;
import utils.Utils;

public class ArrayListDemo implements Cloneable {

    public static void main(String[] args) {
        ArrayList<Friend> list = new ArrayList<Friend>();
        list.add(new Friend("张三", 15));
        list.add(new Friend("李四", 25));
        list.add(new Friend("王五", 35));
        ArrayList<Friend> removeList = new ArrayList<Friend>();
        removeList.add(new Friend("张三", 15));
        removeList.add(new Friend("李四", 25));
        list.removeAll(removeList);
        Utils.showObjectList(list);
    }
}

运行结果:

展示列表数据:
Friend{name='张三', age=15}
Friend{name='李四', age=25}
Friend{name='王五', age=35}

嗯???怎么结果出乎意料,按理说应该只剩下第三条记录,某人曾说过,代码不会欺骗你,于是我们分析源码,看看问题出在哪

在main方法的removeAll处设置断点进入

首先我们要明确源码中的思路:

我看过csdn中有人发的removeAll的讲解,附带的源码应该是1.7或者1.6版本,是通过判断,并循环remove掉不用的元素

而在jdk1.8版本中中的removeAll实现元素移除不是remove,而是将老数组进行筛选,将有用的元素放到新的数组中,新的数组就是这个集合最终的元素

具体思路就是:

遍历这个数组,判断c中的元素是否在elementData中,

如果存在,则证明需要去掉这个元素,因此不加入到新数组中

如果不存在,则证明不需要去除,则将这个元素放到新数组elementData中,同时w++

//    removeAll方法
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }

首先调用Object类的requireNonNull方法,判断List中是否有null值存在

//    Object类中requireNonNull方法
    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }

然后在调用batchRemove方法,,此时参数Collection为{name='张三', age=15},{name='李四', age=25},complete为false

//    batchRemove方法
    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 {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            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;
    }

 r,w初始值为0,声明一个对象数组,将ArrayList的elementData赋值给它,此时的值为

{name='张三', age=15},{name='李四', age=25},{name='王五', age=35},首先判断第一个元素,张三存在,因此c.contains(elementData[r])返回true,因此不满足if条件,进入下一轮循环,李四同样存在,第三轮循环,王五不存在,因此执行elementData[i] = elementData[2],同时w++,此时r++,变为3,不满足循环条件退出循环,此时elementData长度为3,{name='王五', age=35},{name='李四', age=25},{name='王五', age=35},第一个元素被替换,r=3,w=1

然后向下执行,满足条件,w != size,执行if中语句,循环将新的elementData数组w索引后面的值置为null,同时设置size为w,也就是1,此时elementData只有一个元素就是{name='王五', age=35},修改完成

有一点疑惑的地方:

ArrayList的ElementData设置为transient,也就是不需要被序列化,难道在这里声明final Object[] elementData就是为了覆盖原来的?

可能你还沉浸在我理想的debug状态中,忘记了我们的问题,remove失败!!!回到正题,单步的结果应该是这样的

和上面流程一样

r,w初始值为0,声明一个对象数组,将ArrayList的elementData赋值给它,此时的值为

{name='张三', age=15},{name='李四', age=25},{name='王五', age=35},首先判断第一个元素,张三存在,于是理应不满足条件,执行下一次循环啊,结果,它偏偏就满足了???嗯???这是为啥??于是退出重新单步,到这一步,step into,进入方法contains方法,然后调用indexOf方法,然后循环调用Object的equals方法,问题出现了!!!

让我们看一下equals方法

 public boolean equals(Object obj) {
    return (this == obj);
 }

问题就在这,equals方法比较的直接是两个对象的引用,当时创建的是两个不同的ArrayList,引用肯定不同,这样肯定contains方法返回false

发现问题,解决方法也就很简单了,在Friend类中重写equals方法,同时还应该重写hashCode这个方法,但是还没怎么用过hashCode,待日后补充吧,在这里附一个equal的模板,大部分工具都可以直接生成这两个方法

    @Override
    public boolean equals(Object o) {
//        引用相同,两个对象一定相同
        if (this == o) return true;
//        如果o不是是Friend的一个实例,则返回false
        if (!(o instanceof Friend)) return false;
//        将参数强转为Friend对象,并判断参数是否相同
        Friend friend = (Friend) o;
        return getAge() == friend.getAge() &&
                getName().equals(friend.getName());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getName(), getAge());
    }

然后重新运行,问题解决,运行结果如下

 展示列表数据:
Friend{name='王五', age=35}

20. public boolean retainAll(Collection<?> c)

仅保留此列表中包含在指定集合中的元素。 换句话说,从此列表中删除其中不包含在指定集合中的所有元素

这个方法和removeAll都是调用同一个方法,只不过传递参数complete,一个是true,一个是false,传递false一个是排除,传递true是保留

大家可以设置断点,看看具体流程

例子大家也可以看看上面那个,只是把removeAll改成retainAll

发一下运行结果吧

展示列表数据:
Friend{name='张三', age=15}
Friend{name='李四', age=25}

21. public ListIterator<E> listIterator(int index)

从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。 指定的索引表示初始调用将返回的第一个元素为next。 初始调用previous将返回指定索引减1的元素。

    public ListIterator<E> listIterator() {
        return new ListItr(0);
    }

真的是学到啥不会啥,Iterator这个模糊知道是个迭代器,但是用的比较少,ListIterator是Iterator针对List进行的强化版,借机学习一下吧

ListIterator学习笔记

22. public Iterator<E> iterator()

以正确的顺序返回该列表中的元素的迭代器。

    public Iterator<E> iterator() {
        return new Itr();
    }

返回一个局限性更大的一个迭代器(功能不及ListIterator)

23. public List<E> subList(int fromIndex, int toIndex)

 返回指定的fromIndex (包含)和toIndex(不包含)之间的列表部分的视图

实质就是获取当前ArrayList集合指定索引范围的子集

这个方法不知道具体应用场景,单纯做个了解吧,返回的List实质上就是ArrayList内部类SubList,而SubList的元素集合来自其外部类也就是ArrayList

    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;
        }
        public List<E> subList(int fromIndex, int toIndex) {
            subListRangeCheck(fromIndex, toIndex, size);
            return new SubList(this, offset, fromIndex, toIndex);
        }
    }

看看源码就清楚了,在subList这个方法中,将this传入,也就是直接将外部类作为参数传入,而在内部类的构造方法中

this.parent = parent,是直接将对象的引用赋值给内部类,因此在操作SubList也就是在操作这个ArrayList,举个例子

package test;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import bean.Friend;
import utils.Utils;

public class ArrayListDemo implements Cloneable {

    public static void main(String[] args) {
        ArrayList<Friend> list = new ArrayList<Friend>();
        list.add(new Friend("张三", 15));
        list.add(new Friend("李四", 25));
        list.add(new Friend("王五", 35));
        list.add(new Friend("赵六", 45));
        List<Friend> subList = list.subList(1,3);
        subList.remove(1);
        Utils.showObjectList(list);
        Utils.showObjectList(subList);
    }
}

运行结果:

展示列表数据:
Friend{name='张三', age=15}
Friend{name='李四', age=25}
Friend{name='赵六', age=45}
展示列表数据:
Friend{name='李四', age=25}

移除subList的第二个节点, ArrayList的对应节点也被移除,因此二者的elementData指向同一个内存地址

这个SubList和ArrayList一样操作,只是所有的操作都要考虑offset,也就是偏移量,举个最简单的例子

这个是SubList内部类的get(int index)方法

    public E get(int index) {
        rangeCheck(index);
        checkForComodification();
        return ArrayList.this.elementData(offset + index);
    }

和ArrayList思路一样,只是参数需要传递SubList的offset+index,offset在构造方法中定义的是offset+fromIndex,offset初始为0,因此offset的值就是fromIndex这个参数,也就是截取集合的起始索引

24. public void forEach(Consumer<? super E> action)

 看这个没太看懂,因此引用一下其他博客的内容,先做个了解,后面慢慢了解java8新特性,函数式编程

尝试了一下,原来java8中已经实现了lambda表达式。以循环打印Arraylist中的值为例,在java8之前的写法是

for(Integer i : list) {
    System.out.println(i);
}

现在非常简单

list.forEach(x -> System.out.print(x));

25. public boolean removeIf(Predicate<? super E> filter)

 删除满足给定谓词的此集合的所有元素

也是java8新特性,等有时间学一下,这里给一个简单例子

package test;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.function.Predicate;

import bean.Friend;
import utils.Utils;

public class ArrayListDemo implements Cloneable {

    public static void main(String[] args) {
        ArrayList<Friend> list = new ArrayList<Friend>();
        list.add(new Friend("张三", 15));
        list.add(new Friend("李四", 25));
        list.add(new Friend("王五", 35));
        list.add(new Friend("赵六", 45));
        Predicate<Friend> predicate = (friend) -> friend.getName().equals("张三");
        list.removeIf(predicate);
        Utils.showObjectList(list);
    }
}

运行结果:

展示列表数据:
Friend{name='李四', age=25}
Friend{name='王五', age=35}
Friend{name='赵六', age=45}

26. public void sort(Comparator<? super E> c)

使用提供的 Comparator对此列表进行排序,以比较元素。

例子:

package test;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.function.Predicate;

import bean.Friend;
import utils.Utils;

public class ArrayListDemo implements Cloneable {

    public static void main(String[] args) {
        ArrayList<Friend> list = new ArrayList<Friend>();
        list.add(new Friend("李四", 25));
        list.add(new Friend("赵六", 45));
        list.add(new Friend("张三", 15));
        list.add(new Friend("王五", 35));

        list.sort(new Comparator<Friend>() {
            @Override
            public int compare(Friend o1, Friend o2) {
                if (o1.getAge() <= 0 || o2.getAge() <= 0) {
                    return 0;
                }
                //注意compareTo方法是Integer类提供的,因此getAge方法,必须返回Integer
                return o1.getAge().compareTo(o2.getAge());
            }
        });
        Utils.showObjectList(list);
    }
}

 运行结果:

Friend{name='张三', age=15}
Friend{name='李四', age=25}
Friend{name='王五', age=35}
Friend{name='赵六', age=45}

总结:

由JDK1.2开始引入使用

1. 实现原理:采用动态数组方式进行实现

2. ArrayList不适合进行删除或者插入操作,如果添加元素时容量不够,会自动扩容,扩容过程及其繁琐,影响性能

扩容过程:将容量变为原来的1.5倍,并调用Arrays.Copy方法进行元素的复制

因此:为了防止ArrayList在创建时应该给定初始容量initCapacity,通过构造方法

3. ArrayList不是线程安全的,只适合在单线程访问时使用,如果是多线程,应该采用Vector

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章