1. 程式人生 > >大資料學習筆記——Java篇之集合框架(ArrayList)

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

Java集合框架學習筆記

1. Java集合框架中各介面或子類的繼承以及實現關係圖:

 

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

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

 

比如我要在第二個索引位置處加上元素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;
}

 

同樣畫圖演示:

根據原始碼演算法,如果需要移除的是索引為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>());
    }
}