天天看點

大資料學習筆記——Java篇之集合架構(ArrayList)

Java集合架構學習筆記

1. Java集合架構中各接口或子類的繼承以及實作關系圖:

大資料學習筆記——Java篇之集合架構(ArrayList)

2. 數組和集合類的差別整理:

數組:

1. 長度是固定的

2. 既可以存放基本資料類型又可以存放引用資料類型

3. 存放進數組的必須是相同類型的資料

VS

集合類:

1. 長度是可變的

2. 隻能存放對象的引用

3. 存放進集合的可以是不同的資料類型

3. 集合類常用API源碼分析

 在之後的大資料學習中,靈活運用各種各樣的資料結構可以說是一項基本技能了,是以,了解各種資料結構的底層源碼将有助于使用者更好地使用各種開源架構,以下将以ArrayList為例,詳細地解讀源碼,其他各種資料結構以後也會陸續更新:

 3.1 文檔解讀

那麼首先,我們先摘錄一段文檔,從整體上把控一下ArrayList類的概況:

* Resizable-array implementation of the <tt>List</tt> interface.  Implements
* all optional list operations, and permits all elements, including
* <tt>null</tt>.  In addition to implementing the <tt>List</tt> interface,
* this class provides methods to manipulate the size of the array that is
* used internally to store the list.  (This class is roughly equivalent to
* <tt>Vector</tt>, except that it is unsynchronized.)      

(1) 這段話首先點明了ArrayList類是實作自List接口的可調整大小的數組,說明它的底層仍然是使用數組實作的,它實作了一切可選的有關list的操作,并且允許任何類型的元素進入該集合,包括null

(2) 除了實作List接口外,此類還提供了方法能夠内部地操作數組的長度來存儲list

(3) 此類與Vector基本一緻,差別隻是Vector類是線程安全的,而ArrayList不是

* <p>The <tt>size</tt>, <tt>isEmpty</tt>, <tt>get</tt>, <tt>set</tt>,
* <tt>iterator</tt>, and <tt>listIterator</tt> operations run in constant
* time.  The <tt>add</tt> operation runs in <i>amortized constant time</i>,
* that is, adding n elements requires O(n) time.  All of the other operations
* run in linear time (roughly speaking).  The constant factor is low compared
* to that for the <tt>LinkedList</tt> implementation.      

(1) 這段話主要列舉了一些方法的時間複雜度,首先是size,isEmpty,get,set,iterator和ListIterator的方法是常數時間的複雜度O(1)

(2) add方法的複雜度是“amortized constant time”,分段式的常數時間,意思就是說add方法的複雜度是需要分類讨論的,如果是add一個元素,那麼時間複雜度是O(1),而如果是"adding n elements",時間複雜度就變成了O(n)

(3) 除上述兩種情形,其他所有的操作都是線性時間的複雜度,而常數因子對于LinkedList的實作來說要低一些

* <p>Each <tt>ArrayList</tt> instance has a <i>capacity</i>.  The capacity is
* the size of the array used to store the elements in the list.  It is always
* at least as large as the list size.  As elements are added to an ArrayList,
* its capacity grows automatically.  The details of the growth policy are not
* specified beyond the fact that adding an element has constant amortized
* time cost.      

(1) 每一個ArrayList類的執行個體對象都有一個“容量”,容量的意思是用來在list中存放元素的數組的長度,而這個長度至少和list的長度一樣大

(2) 當元素被添加到一個ArrayList的對象時,它的容量也會自動增長,然而,盡管之前提到增添元素的時間複雜度是分段式的常數時間,增長政策的細節是并不明确的

* <p>An application can increase the capacity of an <tt>ArrayList</tt> instance
* before adding a large number of elements using the <tt>ensureCapacity</tt>
* operation.  This may reduce the amount of incremental reallocation.      

(1) 這段話提到了一個API,ensureCapacity方法,在把大量元素添加到ArrayList中去之前,使用這個API可以提高執行個體對象的容量

(2) 這種方法能夠降低在增添元素時重新配置設定空間所産生的開銷

* <p><strong>Note that this implementation is not synchronized.</strong>
* If multiple threads access an <tt>ArrayList</tt> instance concurrently,
* and at least one of the threads modifies the list structurally, it
* <i>must</i> be synchronized externally.  (A structural modification is
* any operation that adds or deletes one or more elements, or explicitly
* resizes the backing array; merely setting the value of an element is not
* a structural modification.)  This is typically accomplished by
* synchronizing on some object that naturally encapsulates the list.

      
* If no such object exists, the list should be "wrapped" using the
* {@link Collections#synchronizedList Collections.synchronizedList}
* method.  This is best done at creation time, to prevent accidental
* unsynchronized access to the list:<pre>
*   List list = Collections.synchronizedList(new ArrayList(...));</pre>      

(1) 必須要注意的是ArrayList這一實作子類并非是線程安全的(Vector類線程安全),如果有多個線程并發地進入到一個ArrayList執行個體對象中去并且至少有一個線程結構上修改了這一執行個體對象,那麼就必須在外部進行同步!!!

(2) 何為結構上改變了一個資料結構:僅僅是将這個集合中的某一個元素的值進行設定不能稱之為結構化地改變一個集合,必須要添加或删除一個或多個元素,換言之使得這個集合的長度發生了改變才能叫做結構化地改變一個集合

(3) 文檔中還推薦如果涉及到了多線程的場景,最好在建立對象的時候就使用同步的集合類,可以調用Collections工具類的靜态方法實作,給出的例子是:List list = Collections.synchronizedList(new ArrayList(...)) ;

* <p><a name="fail-fast">
* The iterators returned by this class\'s {@link #iterator() iterator} and
* {@link #listIterator(int) listIterator} methods are <em>fail-fast</em>:</a>
* if the list is structurally modified at any time after the iterator is
* created, in any way except through the iterator\'s own
* {@link ListIterator#remove() remove} or
* {@link ListIterator#add(Object) add} methods, the iterator will throw a
* {@link ConcurrentModificationException}.  Thus, in the face of
* concurrent modification, the iterator fails quickly and cleanly, rather
* than risking arbitrary, non-deterministic behavior at an undetermined
* time in the future.      

(1) 這段話提到了兩個疊代器,Iterator和ListIterator,這兩種疊代器都是“fail-fast”的

(2) 那麼何為"fail-fast"呢?文檔中又提到了一個叫做ConcurrentModificationException即“并發修改異常”的異常,當一個集合的疊代器對象被建立出來之後,當集合使用了它本身的方法進行了結構上的改變,比如,add,remove方法而沒有使用疊代器的方法時,就會抛出這個異常;而疊代器的這種行為是"fail-fast"的,因為一旦遇到并發修改,疊代器将不會采取任何武斷的,不明确的行為,而是“快速地”在采取下一步行動之前就抛出這個異常

3.2 API解讀

 首先我們看一下ArrayList類的成員變量以及之前提到過的那個ensureCapacity方法:

3.2.1 成員變量

  /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 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 = {};

    /**
     * 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預設的容量是10個元素,并且準備了兩個空的Object類型的數組,EMPTY_ELEMENTDATA以及DEFAULTCAPACITY_EMPTY_ELEMENTDATA,後者與前者的差別在于,後者可以知道ArrayList被添加了第一個元素之後,數組的長度應該要被被擴充到多長,這個長度是由DEFAULT_CAPACITY指定的,數值預設為10,elementData變量被transient所修飾,表明了它不能夠被序列化(可能是為了節省存儲空間),size變量指的是集合所存放的元素的個數

3.2.2 構造方法

  /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

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

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection\'s
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }      

 構造方法一共有三種:

(1) public ArrayList(int initialCapacity):第一種構造方法指定了一個初始容量,如果初始容量大于0,則建立一個該長度的Object類型數組,如果等于0,則傳回成員變量中的長度為0的數組變量,如果小于0,則抛異常

(2) public ArrayList():第二種構造方法是一個空參構造,使用這種方式,預設建立一個長度為10的數組

(3) public ArrayList():此外還提供了一個構造方法可以傳入一個集合對象c,該構造方法的執行流程是首先調用toArray方法轉換成數組對象賦給elementData,由于傳回值有可能不是Object類型的數組,是以又在if判斷中調用了Arrays工具類的copyOf方法将其轉化成數組

3.2.3 ensureCapacity方法

  /**
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list\'s current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     */
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

    /**
     * Increases the capacity of this <tt>ArrayList</tt> instance, if
     * necessary, to ensure that it can hold at least the number of elements
     * specified by the minimum capacity argument.
     *
     * @param   minCapacity   the desired minimum capacity
     */
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It\'s already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    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);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }      

 (1) 在講解ensureCapacity方法之前,我們先來看一個叫做trimToSize的方法,這個方法可以看成是一個優化手段,如果elementData對象的長度是大于size的,那麼就将它的長度調整至size大小,進而達到了節省空間的目的

(2) 在之後的API中,我們會反複看到modCount變量,檢視了一下本類,并沒有看到這個變量,說明我們應該去父類中找尋它,最終,在它的父類抽象類AbstractList中找到了它,根據文檔可知,它其實是一個修改計數器,也就是之前提到過的"Structual modification",隻有發生了結構化的改變才會觸發這個變量的增加,很顯然,上文的trimToSize引起了結構化的改變,是以導緻了這一變量的自增1

(3) 現在我們正式開始檢視ensureCapacity方法的代碼,當使用者傳入的參數minCapacity大于10的時候,就會調用另一個方法,ensureExplicitCapacity(minCapacity),這個方法中,我們看到了注釋,//overflow-conscious code,翻譯過來就是防止出現溢出現象,也就是說,隻有當你指定的最小容量是大于elementData.length的時候,才會觸發擴容操作!

(4) 成員變量MAX_ARRAY_SIZE解讀:由于虛拟機将某些"header words"轉化到數組中去,是以這個值并非是Integer.MAX_VALUE,而是整型的最大值減8,一旦想要配置設定的數組的長度大于這個值,則會觸發記憶體溢出錯誤,OutOfMemoryError

(5) 擴容操作的具體實作,grow(int minCapacity):首先oldCapacity變量記錄了elementData原本的長度,然後将oldCapacity + (oldCapacity >> 1)也就是oldCapacity的1.5倍指派給了變量newCapacity,如果擴了容後這個值都還比minCapacity小,那麼就把minCapacity賦給newCapacity,如果newCapacity大于MAX_ARRAY_SIZE,就調用hugeCapacity()方法,在這個方法中,有可能會抛出OOM錯誤,最後使用Arrays.copyOf(elementData,newCapacity)方法實作了數組擴容

3.2.4 常用的API

contains方法

  public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

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

contains方法中調用了indexOf方法,通過這個方法的傳回值是否大于等于0來判斷list是否包含某元素,而檢視indexOf方法可知,它是通過周遊這個elementData數組,如果equals方法傳回true,則傳回這個索引,如果找完了都沒找到,則傳回-1,由此可知,如果使用者自定義了一個類,就必須要重寫equals方法,那麼下面,我們就舉一個例子驗證一下這個問題!

首先定義一個學生類Student

public class Student {

    private String name;
    private int age;
    private int id;

    public Student(String name, int age, int id) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                id == student.id &&
                name.equals(student.name);
    }

}      

 然後寫一個測試類: 

import java.util.ArrayList;

/*
    測試ArrayList的contains方法
 */
public class StudentTest {
    public static void main(String[] args) {
        
        ArrayList<Student> students = new ArrayList<Student>();
        Student stu1 = new Student("tom", 10, 1);
        Student stu2 = new Student("alice", 20, 2);
        Student stu3 = new Student("peter", 25, 3);
        
        students.add(stu1);
        students.add(stu2);
        students.add(stu3);

        System.out.println(students.contains(new Student("tom",10,1)));

    }
}      

首先我們把equals方法注釋起來,最終控制台輸出的結果為false;然後将注釋放開,結果變為了true,由此得證。

add方法

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
}
private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
}      

add方法中是通過調用ensureCapacityInternal方法來實作數組的擴容的,而這個方法在之前講解ensureCapacity時并未提及,那麼,我們再回過頭來檢視這個方法的源碼,可知,當原數組是個空數組時,會直接把長度擴容到10,然後執行語句elementData[size++] = e,注意,++是寫在後面的,是以執行順序應該是先在size的索引位置處添加上新元素,然後size再自增1;add語句不斷執行,數組的長度不斷增長,當size + 1大于10的時候,ensureExplicitCapacity方法中的防溢出代碼就會觸發grow操作,将原數組的長度擴張到1.5倍,然後繼續執行相同流程

add方法的另一個重載

  /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
  /**
     * A version of rangeCheck used by add and addAll.
     */
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }      

整個過程使用下圖即可解釋:

大資料學習筆記——Java篇之集合架構(ArrayList)

比如我要在第二個索引位置處加上元素7,實際過程就是如上圖所示,将3,4,5,6這四個元素往後移動一格,然後在空出來的那一位上填上7即可

addAll方法

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

addAll方法的實作原理是首先調用toArray方法将一個集合對象c轉換成Object數組,之後擷取到這個數組的長度,然後調用arraycopy方法将c中的每一個元素都加到ArrayList的實作子類對象中去,最後判斷加的集合是否為空,空的話就傳回false,非空表明添加成功,傳回true。注:addAll方法與add方法的最大差別是add方法會将一整個集合看作一個元素進行添加,而addAll則會把一個集合中的元素打散了一個一個地進行添加

remove方法

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

同樣畫圖示範:

大資料學習筆記——Java篇之集合架構(ArrayList)

根據源碼算法,如果需要移除的是索引為2的元素,首先計算出需要移動的元素個數為3,然後使用數組拷貝方法将index + 1之後的所有元素拷貝到index的位置,這樣再将最後一個索引置空交給java的垃圾回收機制處理即可,最後傳回需要移除的索引值對應的元素

注意:當在周遊集合的同時删除元素時,由于會發生整體移動,是以需要注意remove之後将索引減一!

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;
}
public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
}
public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);
}       

(1) 檢視removeAll以及retainAll方法可知,它們兩個都是通過調用batchRemove方法來實作的,差別隻是removeAll方法中complement參數是false,而retainAll方法是true,于是我們轉向研究batchRemove方法,首先可以看到定義了兩個局部變量r和w,可以了解為read 和 write,r負責周遊ArrayList中的元素,w負責将符合條件的元素進行寫出,看到這裡,我們就恍然大悟,原來complement參數指的是你要保留還是移除,如果指定的是true,即隻有當集合中的元素和ArrayList中的相等時才寫出,那麼就等同于retainAll方法,而反之亦然

(2) 了解了上述這一點我們就能了解elementData[w++] = elementData[r]這句代碼了,我們發現這個方法是套在try-finally架構中的,這就意味着,無論try裡面的語句有沒有發生異常,finally語句塊中的語句是一定會被執行到的,那麼我們轉而去看一下finally中到底做了些什麼吧!首先看第一個if塊中的代碼,if(r != size),我相信,大多數人在看到這裡時都是懵逼的,try中的是一個循環語句,當r等于size的時候就會跳出循環,是以最終r應該是等于size的才對,那麼這句語句為什麼會被執行到呢?我們先跳過這個問題不談,先看一下r = size的時候會發生什麼?很明顯,r = size的時候代碼會運作到第二個if判斷,即if(w != size),這段代碼就相對好了解一些了,由于在之前的try語句塊中我們已經找到了符合要求的元素并進行寫出了,是以在第二個if語句塊中,直接把w之後的元素直接置空,最後将size的值調整到w的值即可,而modified變量這時也變成了true,因為确确實實進行過了修改!

(3) 那麼回到第二點中的遺留問題,什麼時候才會出現try語句塊中循環條件沒有執行完的情況呢?不着急,先看一下finally語句塊一上來的那兩句注釋,// Preserve behavioral compatibility with AbstractCollection,// even if c.contains() throws.

翻譯過來的意思是,它要和AbstractCollection類保持相容性,contains方法是有可能抛出異常的,這樣一來循環條件執行不完這種情況就是有可能會發生的了,是以在finally語句塊中第一個if判斷就有可能被觸發!我們再回過頭來看這個if判斷,可以發現實際上它就是把沒有周遊到的那些元素(即size - r個元素)又拷貝到了w索引的後面,然後執行完w += size - r之後再判斷w是否和size相等

3.2.5 疊代器

并發修改異常舉例:

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

/*
    示範并發修改異常
 */
public class ConcurrentModificationExceptionDemo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        ListIterator<Integer> it = list.listIterator();
        while(it.hasNext()){
            if(it.next().equals(1)){
                list.add(5);
            }
        }
    }
}
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at com.lf.ConcurrentModificationExceptionDemo.main(ConcurrentModificationExceptionDemo.java:18)      

在此例中,使用ListIterator進行集合的周遊,然而卻調用了list自身的add方法進行元素的添加,結果抛出了"ConcurrentModificationException"的并發修改異常

三種集合疊代方法:注意,foreach的本質其實還是疊代器!!!

import java.util.ArrayList;
import java.util.Iterator;

/*
    示範三種疊代集合的方法
 */
public class IterateDemo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        list.add("tom");
        list.add("alice");
        list.add("peter");
        list.add("mary");
        //使用Iterator
        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }
        System.out.println("=======================");
        //使用索引法
        for(int i = 0; i < list.size(); i++){
            System.out.println(list.get(i));
        }
        System.out.println("=======================");
        //增強for循環
        for (String name : list) {
            System.out.println(name);
        }
    }
}       

3.2.6 泛型以及泛型方法

泛型的好處:

1. 對進入集合的元素進行了類型檢查,将運作時期的異常提前到了編譯時期

2. 避免了類型轉換異常,ClassCastException

import java.util.Date;

/*
    示範泛型方法,定義一個泛型列印方法,可以列印任何資料類型
 */
public class GenericMethodDemo {
    public static void main(String[] args) {
        //列印字元串
        genericPrint("tom");
        //列印整數
        genericPrint(3);
        //列印目前日期
        genericPrint(new Date());
    }
    public static <T> void genericPrint(T t){
        System.out.println(t);
    }
}       

泛型方法的定義方式和泛型類不同,需要把泛型寫在傳回值的前面,程式會根據使用者傳入的參數自定義地判斷它是屬于什麼資料類型的

3.2.7 泛型通配符

/*
    示範泛型通配符
 */

import java.util.ArrayList;
import java.util.Collection;

class A{

}

class B extends A{

}

class Generic{
    public void test0(Collection<?> c){

    }
    public void test(Collection<? extends A> c){

    }

    public void test2(Collection<? super A> c){

    }
}
public class GenericDemo {
    public static void main(String[] args) {
        Generic gen = new Generic();
        //任何類型都可以傳入
        gen.test0(new ArrayList<String>());
        //A以及A的子類都可以傳入泛型中去
        gen.test(new ArrayList<B>());
        //A以及A的父類都可以傳入泛型中去
        gen.test2(new ArrayList<Object>());
    }
}