大數據學習筆記——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>());
    }
}
posted @ 2020-04-07 20:41  終極菜雞  閱讀(...)  評論(...編輯  收藏
最新chease0ldman老人|无码亚洲人妻下载|大香蕉在线看好吊妞视频这里有精品www|亚洲色情综合网