java類集框架



java類集框架collection講解(一)
一、綜述

      java集合框架定義了幾個接口,這些接口決定了collection類的基本特性。不同的是,具體類僅僅是提供了標準接口的不同實現,如圖,

colls-coreInterfacesjava集合框架接口圖

     從圖可知,java集合類的主要是由兩個接口派生而出——Collection和Map,Collection和Map是集合框架的根接口。其介紹如下:

  • Collection — 位於集合框架的頂層,一個Collection代表一組Object,即Collection的元素(Elements)。有的Collection(子類)允許有相同的元素出現而有的就不行,有的Collection(子類)支持排序而有的則不行。Java SDK不提供直接繼承自Collection的類,Java SDK提供的類都是繼承自Collection的“子接口”如List、Set和Queue。
  • Set — 擴展了Collection的集合,集合中的元素不可以重複,即任意的兩個元素e1和e2都有e1.equals(e2) == false。訪問集合中的元素只能根據元素本身來訪問。
  • List — 擴展了Collection的集合,集合中的元素可以重複,訪問集合中的元素可以根據元素的索引來訪問。
  • Queue — 提供了隊列的實現,除了基本的Collection操作外,隊列還提供其他的插入、提取和檢查操作。
  • Map — 以Key-value對形式保存元素,訪問時只能根據每項元素的key來訪問其value。key必須唯一,value的值可以重複。

尚未列出的兩個接口是Set和Map的排序版本:

  • SortedSet — 擴展Set,集合元素按升序排列
  • SortedMap — 擴展Map,以便關鍵字按升序排列

     另外,除了上面提到的接口,java集合使用Comparator、Iterator和ListIterator等接口,這些接口將會陸續講解,簡單來說,Comparator接口定義了兩個對象的比較方法,即Iterator和ListIterator接口集合裏的對象。同時,爲了提供集合最大的靈活性,每個集合接口裏的方法都是可修改的 —— 一個給定的implemention不一定支持所有的操作(方法)。當調用了不支持的方法時,將引發一個UnsupportedOperationException異常。

二、Collection接口

     Collection是集合框架的基礎,它聲明瞭所有集合都將擁有的核心方法。一個Collection代表一組Object,即Collection的元素(Elements)。所有實現Collection接口的類都必須提供兩個標準的構造函數:無參數的構造函數用於創建一個空的Collection,有一個 Collection參數的構造函數用於創建一個新的Collection,這個新的Collection與傳入的Collection有相同的元素。後 一個構造函數允許用戶複製一個Collection。如下是Collection接口的代碼實現:

public interface Collection<E> extends Iterable<E> {
    // 基本方法
    int size();
    boolean isEmpty();
    boolean contains(Object element);
    boolean add(E element);//可選
    boolean remove(Object element);//可選
    Iterator<E> iterator();

    // 批量操作
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c); //可選
    boolean removeAll(Collection<?> c);//可選
    boolean retainAll(Collection<?> c);//可選
    void clear();//可選

    // 數組操作
    Object[] toArray();
    <T> T[] toArray(T[] a);
}

附:“可選”代表了其子類(或子接口)可以有選擇的去實現,即可以不提供此操作(方法).

     Collection接口定義了操作一組objects的基本方法,如:集合裏有多少個objects(size,isEmpty),集合是否包含某個object(contain),從集合裏增加或刪除一個object(add,remove),返回集合的迭代(iterator)

遍歷Collection

     遍歷Collection,有兩種方法(foreach循環,Iterator接口)。

     如下演示了使用foreach循環來輸出集合裏的元素:

for (Object o : collection)
    System.out.println(o);

       Iterator接口如下:

public interface Iterator<E> {
    boolean hasNext();//如果仍有元素可以迭代,則返回 true。
    E next();      //返回迭代的下一個元素。
    void remove(); //可選操作,移除當前迭代的object
}

    在需要進行如下情況時,應使用Iterator接口迭代collection,而不選foreach方法:

    1.移除當前object。foreach方法隱藏了迭代器,故而不能使用remove()方法,同時也不能使用foreach過濾collection集合

    2.多個collection的迭代

     如下演示如何使用Iterator過濾任意collection—在遍歷集合時,移除指定object:

static void filter(Collection<?> c) {
    for (Iterator<?> it = c.iterator(); it.hasNext(); )
        if (!cond(it.next()))
            it.remove();
}

Collection的批量操作

    批量操作操縱的是整一個集合,同時批量操作的效率的較低。

  • containsAll — 如果此 collection 包含指定 collection 中的所有元素,則返回 true
  • addAll —  將指定collection中的所有元素添加到此collection
  • removeAll — 移除此 collection 中那些也包含在指定 collection 中的所有元素
  • retainAll — 僅保留此 collection 中那些也包含在指定 collection 的元素,換句話說,移除此 collection 中未包含在指定 collection 中的所有元素。
  • clear — 移除此 collection 中的所有元素

     以上批量操作的方法,如果成功修改此collection,都會返回true。如下:從集合c裏刪除所有指定的元素e(一個或多個),

  c.removeAll(Collections.singleton(e));
     更具體一點的就是,你可以刪除集合c裏所有的null元素
     c.removeAll(Collections.singleton(null));
     附:Collections.singleton是一個靜態工廠方法,返回一個只包含指定對象的不可變的set

Collection的數組操作

     Collection的toArray()方法會返回在一個數組,這個數組包含了collection集合裏的元素,數組長度取決於collection集合元素的個數。如下,c爲一個Collection,

     Object[] a = c.toArray();//返回的是一個object數組

    如果已知集合裏元素的類型,假設所有元素都爲String類型(Collection<String>),如下操作可以返回一個String數組:

    String[] a = c.toArray(new String[0]);

三、Set接口

     一個不包含重複元素的 collection。更確切地講,set 不包含滿足 e1.equals(e2) 的元素對 e1 和 e2,並且最多包含一個 null 元素。因此,Set構造函數裏的Collection參數不能包含相同的object。當兩個Set對象所包含的元素都相同,則認爲這兩個Set等同的。

     請注意:必須小心操作可變對象(Mutable Object)。如果一個Set中的可變元素改變了自身狀態導致Object.equals(Object)==true將導致Set的行爲不確定。

     某些 set 實現對其所包含的元素有所限制。例如,某些實現禁止包含 null 元素,而某些則對其元素的類型所有限制。試圖添加不合格的元素會拋出未經檢查的異常,通常是 NullPointerException 或 ClassCastException。試圖查詢不合格的元素是否存在可能會拋出異常,也可能簡單地返回 false;某些實現會採用前一種行爲,而某些則採用後者。概括地說,試圖對不合格元素執行操作時,如果完成該操作後不會導致在 set 中插入不合格的元素,則該操作可能拋出一個異常,也可能成功,這取決於實現的選擇。此接口的規範中將這樣的異常標記爲“可選”。

     Set接口的代碼如下:

public interface Set<E> extends Collection<E> {
    // 基本方法
    int size();
    boolean isEmpty();
    boolean contains(Object element);
    boolean add(E element); // 可選
    boolean remove(Object element);// 可選
    Iterator<E> iterator();

    // 批量操作
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);// 可選
    boolean removeAll(Collection<?> c);       // 可選
    boolean retainAll(Collection<?> c);// 可選
    void clear();// 可選

    // 數組操作
    Object[] toArray();
    <T> T[] toArray(T[] a);
}

     java提供了三種Set的常用實現:HashSet,TreeSet和LinkedHashSet,

  • HashSet — 以哈希表的形式存儲集合元素,它不保證 set 的迭代順序;特別是它不保證該順序恆久不變。可以包含 null 元素。
  • TreeSet — 以紅黑樹的形式存儲集合元素,使用元素的自然順序對元素進行排序。整體性能比HashSet低
  • LinkedHashSet — 具有可預知迭代順序的 Set 接口的哈希表和鏈接列表實現,按照元素的插入順序進行排序。可以包含null元素。

    在這裏,給出一個簡單而是用的Set用法:從一個可能含有重複元素裏的集合c,創建一個消除重複元素的集合noDups:(在下面的FindDups有具體的演示)

    Collection<Type> noDups = new HashSet<Type>(c);

    如果在刪除了重複元素的基礎上,還要保留原來元素的順序的,可以選擇使用LinkedHashSet,

    Collection<Type> noDups = new LinkedHashSet<Type>(c);
    這個LinkedHashSet的用法,也可以用如下的泛型方法來代替:
public static <E> Set<E> removeDups(Collection<E> c) {
    return new LinkedHashSet<E>(c);
}

Set的基本操作

     size()返回集合元素的個數,isEmpty()判斷集合是否爲空;add()方法往集合添加一個object,如果Set集合裏尚未包含這個元素,則可順利添加,否則返回false;類似的,remove方法可以刪除Set集合裏存在的一個元素,如果不存在則返回false。iterator()返回集合的迭代。

     Demo:FindDups利用Set的基本方法,輸出String數組裏重複的元素,

import java.util.HashSet;
import java.util.Set;

public class FindDups {
        public static void main(String[] args) {
                String[] str = {"d","b","c","a","d"};
                Set<String> s = new HashSet<String>();
                for (String a : str)
                        if (!s.add(a))
                                System.out.println("重複的元素: " + a);

                System.out.println(s.size() + " 個不同的元素: " + s);
        }
}


其輸出如下:image    

       在FindDups裏使用HashSet— 一個沒有排序功能的Set,如果要實現元素排序的輸出,可以選擇使用TreeSet作爲Set的實現。

       Set<String> s = new TreeSet<String>();

       其輸出如下:image

Set批量操作

       collection裏的批量操作很是適用於Set,具體批量操作可參考collection的批量操作。在這裏演示一下如何求兩個Set集合的並集、交集和差集:

                              // 並集
                Set<Type> union = new HashSet<Type>(s1);
                union.addAll(s2);
                // 交集
                Set<Type> intersection = new HashSet<Type>(s1);
                intersection.retainAll(s2);
                // 差集
                Set<Type> difference = new HashSet<Type>(s1);
                difference.removeAll(s2);

     在此,可以回顧一下FindDups,假設你需要找出collection裏有哪些元素只出現了一次,哪些元素出現了多次,應該如何實現呢???可以設計兩個Set,一個包含了只出現一次的元素,另外一個包含了哪些重複出現的元素,具體的實現如下:
public class FindDups2 {

        public static void main(String[] args) {
                String[] str = {"d","b","c","a","d"};
                Set<String> uniques = new HashSet<String>();
                Set<String> dups = new HashSet<String>();

                for (String a : str){
                        if (!uniques.add(a))
                                dups.add(a);
                }
                // 移除所有的重複出現的元素
                uniques.removeAll(dups);

                System.out.println("只出現一次的元素:    " + uniques);
                System.out.println("重複出現的元素: " + dups);
        }
}


其輸出如下:image

Set的數組操作

    Set的數組操作和collection的數組一致,並沒有特別的用法,詳情可看Collection的數組操作。

四、List接口

    List是一個有序的collection(有時也叫做“序列”),可以包含重複的元素。除了從Collection接口繼承過來的方法,List也提供瞭如下操作(方法)。

  • 索引訪問 — 根據元素的索引來操縱元素
  • 搜索— 搜索指定元素並返回其索引
  • Iteration — 繼承了Iterator ,充分利用了List的索引優勢
  • 截取 — 截取List任意範圍的元素

List接口代碼如下:

public interface List<E> extends Collection<E> {
    // 索引訪問
    E get(int index);
    E set(int index, E element);//可選
    boolean add(E element); //可選
    void add(int index, E element);//可選
    E remove(int index);//可選
    boolean addAll(int index, Collection<? extends E> c);//可選

    // 搜索
    int indexOf(Object o);
    int lastIndexOf(Object o);

    // 迭代器
    ListIterator<E> listIterator();
    ListIterator<E> listIterator(int index);

    // 截取List
    List<E> subList(int from, int to);
}

    javat提供了兩個List的常用實現:ArrayList和LinkedList。在搜索頻繁的情況下,選擇使用ArrayList;插入刪除頻繁的情況下,選擇使用ListArray。值得提一下的的是,從java2開始,Vector向量改進爲可以實現List。

Collection操作

    List從Collection那繼承過來的方法,它的使用方式和Collection是一致的,如果不太熟悉Collection操作,可以看一下Collection接口。remove()方法會刪除List集合裏的第一個元素,add()和addAll()方法從List的尾部依次添加元素,所以下面的代碼會連接兩個List:

    list1.addAll(list2);

    如果需要新建一個List3來連接List1和List2,可以這樣做:

    List<Type> list3 = new ArrayList<Type>(list1);
    list3.addAll(list2);

    和Set接口一樣,如果兩個List所包含的元素一致,則認爲這兩個List是等同的。

List的索引訪問與搜索

     List的索引訪問與搜索和數組的操作基本相似,如下演示了交換List裏的兩個元素的位置

public static <E> void swap(List<E> a, int i, int j) {
    E temp = a.get(i);
    a.set(i, a.get(j));
    a.set(j, temp);
}

    利用這個元素位置交換的性質,我們可以模擬撲克牌的洗牌操作,如下

public static void shuffle(List<?> list, Random rnd) {
    for (int i = list.size(); i > 1; i--)
        swap(list, i - 1, rnd.nextInt(i));
}

    利用隨機數的性質,使洗牌這一操作對每一位玩家來說都是公平的。而這個洗牌shuffle的方法,其實包含在Collections類裏,如下代碼演示了隨機輸出集合裏各個元素(發牌):

public class Shuffle {
        public static void main(String[] args) {
                String[] str = {"a","b","c","d","e","f"};//可以選擇湊夠52張牌
                List<String> list = new ArrayList<String>();
                for (String a : str){
                        list.add(a);
                }
                Collections.shuffle(list, new Random());
                System.out.println(list);
        }

}

其輸出如下:image(因爲是隨機輸出的,所以每一次運行的結果都會不一樣。)

    附:事實上,還可以採取一種更爲快捷簡單的方法,使用Arrays提供的靜態工廠方法asList:

public class Shuffle2 {

        public static void main(String[] args) {
        String[] str = {"a","b","c","d","e","f"};//可以選擇湊夠52張牌
        List<String> list = Arrays.asList(str);
        Collections.shuffle(list);
        System.out.println(list);
    }
}

迭代器Iterators

    List提供了一個功能更爲豐富的迭代器ListIterator,ListIterator,允許程序員按任一方向遍歷列表、迭代期間修改列表,並獲得迭代器在列表中的當前位置。ListIterator 沒有當前元素;它的光標位置 始終位於調用 previous() 所返回的元素和調用 next() 所返回的元素之間。長度爲 n 的列表的迭代器有 n+1 個可能的指針位置(如圖,長度爲4的列表的迭代器有5個可能的指針位置)。

colls-fivePossibleCursor

ListIterator接口的代碼如下:

public interface ListIterator<E> extends Iterator<E> {
    boolean hasNext();
    E next();
    boolean hasPrevious();
    E previous();
    int nextIndex();//返回對 next 的下一個元素的索引。
    int previousIndex();//返回對 previous 的上一個元素的索引
    void remove(); //可選
    void set(E e); //可選
    void add(E e); //可選

}

    如下代碼演示瞭如何反向迭代列表元素:

for (ListIterator<Type> it = list.listIterator(list.size()); it.hasPrevious(); ) {
    Type t = it.previous();
    ...
}

     hasNext, next, 和remove方法的用法和原來的Iterator的用法一致,nextIndex和previousIndex通常用於記錄某個搜索到的元素的位置或者用於創建一個元素位置一致的新ListIterator。如果指針位置在第一個元素之前調用previousIndex()方法,會返回-1;類似的,如果指針位置在最後一個元素之後調用nextIndex()方法,會返回List.size。更爲詳細的講解, 可以參考一下List.indexOf方法的實現:

public int indexOf(E e) {
    for (ListIterator<E> it = listIterator(); it.hasNext(); )
        if (e == null ? it.next() == null : e.equals(it.next()))
            return it.previousIndex();
    // 元素不存在
    return -1;
}

  ListIterator接口提供了兩種更新列表的方法—set和add。set(E e)用指定元素替換 nextprevious 返回的最後一個元素。如下代碼演示瞭如何用另一個值替換列表裏得指定值:

public static <E> void replace(List<E> list, E val, E newVal) {
    for (ListIterator<E> it = list.listIterator(); it.hasNext(); )
        if (val == null ? it.next() == null : val.equals(it.next()))
            it.set(newVal);
}

    這個Demo裏只需要比較val和it.next是否相同就行了,同時需要判斷null,防止出現NullPointerException。add(E e)會在指針位置的前面插入一個元素。如下Demo演示了用一組數據替換List裏的指定的值:

public static <E> void replace(List<E> list, E val, List<? extends E> newVals) {
                for (ListIterator<E> it = list.listIterator(); it.hasNext();) {
                        if (val == null ? it.next() == null : val.equals(it.next())) {
                                it.remove();
                                for (E e : newVals)
                                        it.add(e);
                        }
                }
        }

List的截取操作

      List的截取操作,subList(int fromIndex, int toIndex),截取原List第fromIndex位到第toIndex位的元素(新List包含第fromIndex位,不包含toIndex位,用集合表示爲[fromIndex,toIndex]。注意的是,返回的新List還是原List的一部風,在新List所做的改變,同時也會改變原List。

    任和需要List對象的操作都可以看成是對其子List的一系列操作。舉個例子,如下代碼可刪除List指定範圍裏的元素:

    list.subList(fromIndex, toIndex).clear();

    類似地,可以這樣搜索List表裏的指定元素:

    int i = list.subList(fromIndex, toIndex).indexOf(object);
    int j = list.subList(fromIndex, toIndex).lastIndexOf(object);
    注:如上搜索指定元素的方法,返回的字表的索引值,而不是原List的索引值。

任意一個操作List的多肽算法,如replace和shuffle方法,都是與字表打交道的。這裏給出一個使用subList的多肽方法,使用這個方法來處理經典發撲克牌的問題。首先,先假設只給一個人發牌(hand),撲克牌堆用deck表示,那麼發n張牌就可以看做從deck表的尾部截取n個元素的子List。代碼如下:

public static <E> List<E> dealHand(List<E> deck, int n) {
                int deckSize = deck.size();//撲克牌的(剩餘)張數
                List<E> handView = deck.subList(deckSize - n, deckSize);
                List<E> hand = new ArrayList<E>(handView);//發給這個人n張牌
                handView.clear();//已發的牌,需要從deck裏刪除
                return hand;
        }

    注意到了,我們是從deck(List)的尾端截取的。對多數List的實現來說,如ArrayList,從尾端移除元素比從頭部移除元素的效率好。接着,我們來看一下如何給n個人發牌(這裏假設有52張牌)。
public class Deal {

        public static void main(String[] args) {
                // 52張撲克牌
                String[] suit = new String[] { "黑桃", "紅心", "梅花", "方塊" };
                String[] rank = new String[] { "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K" };
                // 初始化deck列表
                List<String> deck = new ArrayList<String>();
                for (int i = 0; i < suit.length; i++) {
                        for (int j = 0; j < rank.length; j++) {
                                deck.add(suit[i] + rank[j]);
                        }
                }

                dealing(deck, 4, 13); // 4個玩家,每個玩家13張牌
        }
        //發牌,共有numHands玩家,每個玩家有cardsPerPerson張牌
        public static <E> void dealing(List<E> deck, int numHands, int cardsPerPerson) {
                // 洗牌
                Collections.shuffle(deck);

                if (numHands * cardsPerPerson > deck.size()) {
                        System.out.println("玩家太多,撲克牌不足");
                        return;
                }

                for (int i = 0; i < numHands; i++) {
                        System.out.println(dealHand(deck, cardsPerPerson));
                }
        }

        public static <E> List<E> dealHand(List<E> deck, int n) {
                int deckSize = deck.size();
                List<E> handView = deck.subList(deckSize - n, deckSize);
                List<E> hand = new ArrayList<E>(handView);
                handView.clear();
                return hand;
        }
}

其輸出如下:image(因爲是隨機發牌的,所以每次運行的結果都會不一樣)

List 算法

     類Collections提供的多數多肽方法都適用於List,在這裏給出這些算法概覽(以後會詳談):

  • sort — 使用合併排序算法排序List,默認爲升序排列。
  • shuffle — 使用隨機源對指定列表進行置換。(洗牌).
  • reverse — 反轉指定列表中元素的順序。
  • rotate — 根據指定的距離輪換指定列表中的元素。
  • swap — 交換指定位置上的元素
  • replaceAll — 使用一個值替換列表中出現的所有某一指定值。
  • fill — 使用指定元素替換列表中的所有元素。
  • copy — 將所有元素從一個列表複製到另一個列表。
  • binarySearch — 使用二分搜索法搜索指定列表,以獲得指定對象。
  • indexOfSubList — 返回指定列表中第一次出現指定目標列表的起始位置;如果沒有出現這樣的目標,則返回 -1。
  • lastIndexOfSubList — 返回指定源列表中最後一次出現指定目標列表的起始位置;如果沒有出現這樣的目標,則返回 -1。

    最後附上兩張經典的collection圖,希望可以給你帶來一定的 幫助,感謝你的瀏覽。

201203102245165206

201203102245182566



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