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

Java集合框架學習筆記

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

 

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

數組:java

1. 長度是固定的算法

2. 既能夠存放基本數據類型又能夠存放引用數據類型數組

3. 存放進數組的必須是相同類型的數據安全

VS數據結構

集合類:多線程

1. 長度是可變的併發

2. 只能存放對象的引用app

3. 存放進集合的能夠是不一樣的數據類型框架

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

 在以後的大數據學習中,靈活運用各類各樣的數據結構能夠說是一項基本技能了,所以,瞭解各類數據結構的底層源碼將有助於用戶更好地使用各類開源框架,如下將以ArrayList爲例,詳細地解讀源碼,其餘各類數據結構之後也會陸續更新:ide

 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>()); } }
相關文章
相關標籤/搜索