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

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