1. 程式人生 > 其它 >Iterator迭代器2

Iterator迭代器2

1.1    Iterator迭代器概述

java中提供了很多個集合,它們在儲存元素時,採用的儲存方式不同。我們要取出這些集合中的元素,可通過一種通用的獲取方式來完成。

Collection集合元素的通用獲取方式:在取元素之前先要判斷集合中有沒有元素,如果有,就把這個元素取出來,繼續在判斷,如果還有就再取出出來。一直把集合中的所有元素全部取出。這種取出方式專業術語稱為迭代。

集合中把這種取元素的方式描述在Iterator介面中。Iterator介面的常用方法如下:

hasNext()方法:用來判斷集合中是否有下一個元素可以迭代。如果返回true,說明可以迭代。

next()方法:用來返回迭代的下一個元素,並把指標向後移動一位。

迭代集合元素圖解:

1.2      Iterator迭代方式的程式碼體現

在Collection介面描述了一個抽象方法iterator方法,所有Collection子類都實現了這個方法,並且有自己的迭代形式。

進行程式碼演示:

//1,建立集合物件。

Collection<String> coll = new ArrayList<String>();

coll.add("abc1");

coll.add("abc2");

coll.add("abc3");

coll.add("abc4");

//2.獲取容器的迭代器物件。通過iterator方法。

Iterator it = coll.iterator();

//3,使用具體的迭代器物件獲取集合中的元素。參閱迭代器的方法

while(it.hasNext()){

    System.out.println(it.next());

}

/*

迭代器for迴圈的形式的使用

for (Iterator it = coll.iterator(); it.hasNext(); ) {

    System.out.println(it.next());

}

*/

注意:在進行集合元素取出時,如果集合中已經沒有元素了,還繼續使用迭代器的next方法,將會發生java.util.NoSuchElementException沒有集合元素的錯誤。

1.3     集合元素的向下轉型

學習到這裡,基本知道了Collection介面的簡單使用。可是集合中可以儲存任何物件,那麼存放進去的資料都是還是原來型別嗎?不是了,提升成了Object。

在使用集合時,我們需要注意以下幾點:

集合中儲存其實都是物件的地址。

集合中可以儲存基本數值嗎?jdk1.5版本以後可以儲存了。因為出現了基本型別包裝類,它提供了自動裝箱操作(基本型別物件),這樣,集合中的元素就是基本數值的包裝類物件。

儲存時提升了Object。取出時要使用元素的特有內容,必須向下轉型。

Collection coll = new ArrayList();

coll.add("abc");

coll.add("aabbcc");

coll.add(1);

Iterator it = coll.iterator();

while (it.hasNext()) {

    //由於元素被存放進集合後全部被提升為Object型別

//當需要使用子類物件特有方法時,需要向下轉型

    String str = (String) it.next();

    System.out.println(str.length());

}

注意:如果集合中存放的是多個物件,這時進行向下轉型會發生型別轉換異常。

提示:Iterator介面也可以使用<>來控制迭代元素的型別的。程式碼演示如下:

Collection<String> coll = new ArrayList<String>();

coll.add("abc");

coll.add("aabbcc");

coll.add("cat");

Iterator<String> it = coll.iterator();

while (it.hasNext()) {

    String str = it.next();

//當使用Iterator<String>控制元素型別後,就不需要強轉了。獲取到的元素直接就是String型別

    System.out.println(str.length());

}

第2章     增強for迴圈

增強for迴圈是JDK1.5以後出來的一個高階for迴圈,專門用來遍歷陣列和集合的。它的內部原理其實是個Iterator迭代器,所以在遍歷的過程中,不能對集合中的元素進行增刪操作。

格式:

for(元素的資料型別 變數 : Collection集合or陣列){

}

它用於遍歷Collection和陣列。通常只進行遍歷元素,不要在遍歷的過程中對集合元素進行增刪操作。

練習一:遍歷陣列int[] arr = new int[]{11,22,33};

for (int n : arr) {//變數n代表被遍歷到的陣列元素

    System.out.println(n);

}

練習二:遍歷集合

Collection<String> coll = new ArrayList<String>();

coll.add("a1");

coll.add("a2");

coll.add("a3");

coll.add("a4");

for(String str : coll){//變數Str代表被遍歷到的集合元素

    System.out.println(str);

}

增強for迴圈和老式的for迴圈有什麼區別?

注意:新for迴圈必須有被遍歷的目標。目標只能是Collection或者是陣列。

建議:遍歷陣列時,如果僅為遍歷,可以使用增強for如果要對陣列的元素進行 操作,使用老式for迴圈可以通過角標操作。

第3章     泛型

3.1    泛型的引入

在前面學習集合時,我們都知道集合中是可以存放任意物件的,只要把物件儲存集合後,那麼這時他們都會被提升成Object型別。當我們在取出每一個物件,並且進行相應的操作,這時必須採用型別轉換。比如下面程式:

public class GenericDemo {

    public static void main(String[] args) {

        List list = new ArrayList();

        list.add("abc");

        list.add("oracle");

        list.add(5);//由於集合沒有做任何限定,任何型別都可以給其中存放

        Iterator it = list.iterator();

        while(it.hasNext()){

            //需要列印每個字串的長度,就要把迭代出來的物件轉成String型別

            String str = (String) it.next();

            System.out.println(str.length());

        }

    }

}

程式在執行時發生了問題java.lang.ClassCastException

為什麼會發生型別轉換異常呢?我們來分析下:

由於集合中什麼型別的元素都可以儲存。導致取出時,如果出現強轉就會引發執行時 ClassCastException。怎麼來解決這個問題呢?使用集合時,必須明確集合中元素的型別。這種方式稱為:泛型。

3.2     泛型的定義與使用

我們在集合中會大量使用到泛型,這裡來完整地學習泛型知識。

泛型,用來靈活地將資料型別應用到不同的類、方法、介面當中。將資料型別作為引數進行傳遞。

3.2.1     含有泛型的類

    定義格式:修飾符 class 類名<代表泛型的變數> { }

例如,API中的ArrayList集合:

class ArrayList<E>{

public boolean add(E e){ }

    public E get(int index){ }

}

使用格式:建立物件時,確定泛型的型別

例如,ArrayList<String> list = new ArrayList<String>();

此時,變數E的值就是String型別

class ArrayList<String>{

public boolean add(String e){ }

    public String get(int index){ }

}

例如,ArrayList<Integer> list = new ArrayList<Integer>();

此時,變數E的值就是Integer型別

class ArrayList<Integer>{

public boolean add(Integer e){ }

    public Integer get(int index){ }

}

3.2.2     含有泛型的介面

    定義格式:修飾符 interface介面名<代表泛型的變數> { }

例如,API中的Iterator迭代器介面

public interface Iterator<E> {

    public abstract E next();

}

    

使用格式:

1、定義類時確定泛型的型別

例如

public final class Scanner implements Iterator<String> {

public String next(){

 }

}

此時,變數E的值就是String型別。

2、始終不確定泛型的型別,直到建立物件時,確定泛型的型別

例如

ArrayList<String> list = new ArrayList<String>();

Iterator<String> it = list.iterator();

此時,變數E的值就是String型別。

public interface Iterator<String> {

    public abstract String next();

}

3.3     使用泛型的好處

將執行時期的ClassCastException,轉移到了編譯時期變成了編譯失敗。

避免了型別強轉的麻煩。

演示下列程式碼:

public class GenericDemo {

    public static void main(String[] args) {

        List<String> list = new ArrayList<String>();

        list.add("abc");

        list.add("oracle");

        //list.add(5);//當集合明確型別後,存放型別不一致就會編譯報錯

        //集合已經明確具體存放的元素型別,那麼在使用迭代器的時候,迭代器也同樣會知道具體遍歷元素型別

        Iterator<String> it = list.iterator();

        while(it.hasNext()){

    String str = it.next();

//當使用Iterator<String>控制元素型別後,就不需要強轉了。獲取到的元素直接就是String型別

            System.out.println(str.length());

        }

    }

}

3.4     泛型萬用字元

泛型是在限定資料型別,當在集合或者其他地方使用到泛型後,那麼這時一旦明確泛型的資料型別,那麼在使用的時候只能給其傳遞和資料型別匹配的型別,否則就會報錯。

程式碼演示:

定義迭代集合元素的方法

public static void printCollection(Collection<Person> list) {

    Iterator<Person> it = list.iterator();

    while (it.hasNext()) {

        System.out.println(it.next());

    }

}

呼叫方法

Collection<Student> list = new ArrayList<Student>();

printCollection(list);

上面呼叫方法語句屬於語法錯誤,因為泛型限定不一致。方法要的是Collection<Person>型別,傳入的是Collection<Student>,二者型別不匹配。

上述定義的printCollection方法中,由於定義的是列印集合的功能,應該是可以列印任意集合中元素的。但定義方法時,根本無法確定具體集合中的元素型別是什麼。為了解決這個"無法確定具體集合中的元素型別"問題,java中,為我們提供了泛型的萬用字元<?>。

對上面的方法,進行修改後,實現了可迭代任意元素型別集合的方法

public static void printCollection(Collection<?> list) {

    Iterator<?> it = list.iterator();

    while (it.hasNext()) {

        System.out.println(it.next());

    }

}

總結一下:

當使用泛型類或者介面時,傳遞的資料中,泛型型別不確定,可以通過萬用字元<?>表示。但是一旦使用泛型的萬用字元後,只能使用Object類中的共性方法,集合中元素自身方法無法使用。

3.5     泛型限定

上述列印集合的功能,看似很強大,可以列印任意集合,可是問題也來了。如果想要對被列印的集合中的元素型別進行限制,只在指定的一些型別,進行列印。怎麼做呢?

要解決這個問題,我們就要學習泛型的限定。

限定泛型的上限:

格式:? extends E

? 代表接收E型別或者E的子型別的元素

例如,泛型限定為:? extends Person

則 ? 代表接收Person型別或者Person子型別的元素

限定泛型的下限:

格式:? super E

? 代表接收E型別或者E的父型別的元素

例如,泛型限定為:? super Student

則 ? 代表接收Student型別或者Student父型別的元素

練習:修改下面的方法,使該方法可以列印學生和工人的集合

class Student extends Person{ }

class Worker extends Person{ }

public static void printCollection(Collection<?> list) {

    Iterator<?> it = list.iterator();

    while (it.hasNext()) {

        System.out.println(it.next());

 }}

分析一下,我們可以找到學生和工人的共性型別Person。那麼,泛型的限定可以這樣書寫:

? extends Person : 接收Person型別或者Person的子型別。修改方法如下:

public static void printCollection(Collection<? extends Person> list) {

    Iterator<? extends Person> it = list.iterator();

    while (it.hasNext()) {

        System.out.println(it.next());

    }

}

第4章  List介面

我們掌握了Collection介面的使用後,再來看看Collection介面中的子類,他們都具備那些特性呢?

接下來,我們一起學習Collection中的常用幾個子類(List集合、Set集合)。

4.1  List介面介紹

查閱API,看List的介紹。有序的 collection(也稱為序列)。此介面的使用者可以對列表中每個元素的插入位置進行精確地控制。使用者可以根據元素的整數索引(在列表中的位置)訪問元素,並搜尋列表中的元素。與 set 不同,列表通常允許重複的元素。

看完API,我們總結一下:

List介面:

l  它是一個元素存取有序的集合。例如,存元素的順序是11、22、33。那麼集合中,元素的儲存就是按照11、22、33的順序完成的)。

l  它是一個帶有索引的集合,通過索引就可以精確的操作集合中的元素(與陣列的索引是一個道理)。

l  集合中可以有重複的元素,通過元素的equals方法,來比較是否為重複的元素。

 

List介面的常用子類有:

l  ArrayList集合

l  LinkedList集合

4.2  List介面中常用的方法

l  增加元素方 法

n  add(Object e):向集合末尾處,新增指定的元素

n  add(int index, Object e):向集合指定索引處,新增指定的元素,原有元素依次後移

l  刪除元素刪除

n  remove(Object e):將指定元素物件,從集合中刪除,返回值為被刪除的元素

n  remove(int index):將指定索引處的元素,從集合中刪除,返回值為被刪除的元素

l  替換元素方法

n  set(int index, Object e):將指定索引處的元素,替換成指定的元素,返回值為替換前的元素

l  查詢元素方法

n  get(int index):獲取指定索引處的元素,並返回該元素

 

方法演示:

List<String> list = new ArrayList<String>();

//1,新增元素。

list.add("小紅");

list.add("小梅");

list.add("小強");

//2,插入元素。插入元素前的集合["小紅","小梅","小強"]

list.add(1, "老王"); //插入元素後的集合["小紅","老王","小梅","小強"]

//3,刪除元素。

list.remove(2);// 刪除元素後的集合["小紅","老王","小強"]

//4,修改元素。

list.set(1, "隔壁老王");// 修改元素後的集合["小紅","隔壁老王","小強"]

 

Iterator<String> it = list.iterator();

while (it.hasNext()) {

    String str = it.next();

    System.out.println(str);

}

由於List集合擁有索引,因此List集合迭代方式除了使用迭代器之外,還可以使用索引進行迭代。

for (int i = 0; i < list.size(); i++) {

    String str = list.get(i);

    System.out.println(str);           

}

4.2.1 Iterator的併發修改異常

在list集合迭代元素中,對元素進行判斷,一旦條件滿足就新增一個新元素。程式碼如下

public class IteratorDemo {

//在list集合迭代元素中,對元素進行判斷,一旦條件滿足就新增一個新元素

    public static void main(String[] args) {

         //建立List集合

         List<String> list = new ArrayList<String>();

         //給集合中新增元素

         list.add("abc1");

         list.add("abc2");

         list.add("abc3");

         list.add("abc4");

         //迭代集合,當有元素為"abc2"時,集合加入新元素"a"

         Iterator<String> it = list.iterator();

         while(it.hasNext()){

             String str = it.next();

             //判斷取出的元素是否是"abc2",是就新增一個新元素

             if("abc2".equals(str)){

                  list.add("a");// 該操作會導致程式出錯

             }

         }

         //列印容器中的元素

         System.out.println(list);

    }

}

執行上述程式碼發生了錯誤 java.util.ConcurrentModificationException[L1] 這是什麼原因呢?

在迭代過程中,使用了集合的方法對元素進行操作。導致迭代器並不知道集合中的變化,容易引發資料的不確定性。

併發修改異常解決辦法:在迭代時,不要使用集合的方法操作元素。

那麼想要在迭代時對元素操作咋辦?通過ListIterator迭代器操作元素是可以的,ListIterator的出現,解決了使用Iterator迭代過程中可能會發生的錯誤情況。

   

4.3  List集合儲存資料的結構

List介面下有很多個集合,它們儲存元素所採用的結構方式是不同的,這樣就導致了這些集合有它們各自的特點,供給我們在不同的環境下進行使用。資料儲存的常用結構有:堆疊、佇列、陣列、連結串列。我們分別來了解一下:

l  堆疊,採用該結構的集合,對元素的存取有如下的特點:

n  先進後出(即,存進去的元素,要在它後面的元素依次取出後,才能取出該元素)。例如,子彈壓進彈夾,先壓進去的子彈在下面,後壓進去的子彈在上面,當開槍時,先彈出上面的子彈,然後才能彈出下面的子彈。

n  棧的入口、出口的都是棧的頂端位置

n  壓棧:就是存元素。即,把元素儲存到棧的頂端位置,棧中已有元素依次向棧底方向移動一個位置。

n  彈棧:就是取元素。即,把棧的頂端位置元素取出,棧中已有元素依次向棧頂方向移動一個位置。

 

 

l  佇列,採用該結構的集合,對元素的存取有如下的特點:

n  先進先出(即,存進去的元素,要在後它前面的元素依次取出後,才能取出該元素)。例如,安檢。排成一列,每個人依次檢查,只有前面的人全部檢查完畢後,才能排到當前的人進行檢查。

n  佇列的入口、出口各佔一側。例如,下圖中的左側為入口,右側為出口。

 

 

l  陣列,採用該結構的集合,對元素的存取有如下的特點:

n  查詢元素快:通過索引,可以快速訪問指定位置的元素

n  增刪元素慢:

指定索引位置增加元素:需要建立一個新陣列,將指定新元素儲存在指定索引位置,再把原陣列元素根據索引,複製到新陣列對應索引的位置。如下圖

指定索引位置刪除元素:需要建立一個新陣列,把原陣列元素根據索引,複製到新陣列對應索引的位置,原陣列中指定索引位置元素不復制到新陣列中。如下圖

 

 

l  連結串列,採用該結構的集合,對元素的存取有如下的特點:

n  多個節點之間,通過地址進行連線。例如,多個人手拉手,每個人使用自己的右手拉住下個人的左手,依次類推,這樣多個人就連在一起了。

n  查詢元素慢:想查詢某個元素,需要通過連線的節點,依次向後查詢指定元素

n  增刪元素快:

u  增加元素:操作如左圖,只需要修改連線下個元素的地址即可。

u  刪除元素:操作如右圖,只需要修改連線下個元素的地址即可。

   

4.4  ArrayList集合

ArrayList集合資料儲存的結構是陣列結構。元素增刪慢,查詢快,由於日常開發中使用最多的功能為查詢資料、遍歷資料,所以ArrayList是最常用的集合。

許多程式設計師開發時非常隨意地使用ArrayList完成任何需求,並不嚴謹,這種用法是不提倡的。

4.5  LinkedList集合

LinkedList集合資料儲存的結構是連結串列結構。方便元素新增、刪除的集合。實際開發中對一個集合元素的新增與刪除經常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。如下圖

LinkedList是List的子類,List中的方法LinkedList都是可以使用,這裡就不做詳細介紹,我們只需要瞭解LinkedList的特有方法即可。在開發時,LinkedList集合也可以作為堆疊,佇列的結構使用。

方法演示:

         LinkedList<String> link = new LinkedList<String>();

         //新增元素

         link.addFirst("abc1");

         link.addFirst("abc2");

         link.addFirst("abc3");

         //獲取元素

         System.out.println(link.getFirst());

         System.out.println(link.getLast());

         //刪除元素

         System.out.println(link.removeFirst());

         System.out.println(link.removeLast());

        

         while(!link.isEmpty()){ //判斷集合是否為空

             System.out.println(link.pop()); //彈出集合中的棧頂元素

       }

4.6  Vector集合

Vector集合資料儲存的結構是陣列結構,為JDK中最早提供的集合。Vector中提供了一個獨特的取出方式,就是列舉Enumeration,它其實就是早期的迭代器。此介面Enumeration的功能與 Iterator 介面的功能是類似的。Vector集合已被ArrayList替代。列舉Enumeration已被迭代器Iterator替代。

l  Vector常見的方法:

l  Enumeration列舉常見的方法:

l  Vector集合對ArrayList集合使用的對比

第5章  Set介面

學習Collection介面時,記得Collection中可以存放重複元素,也可以不存放重複元素,那麼我們知道List中是可以存放重複元素的。那麼不重複元素給哪裡存放呢?那就是Set介面,它裡面的集合,所儲存的元素就是不重複的。

5.1  Set介面介紹

查閱Set集合的API介紹,通過元素的equals方法,來判斷是否為重複元素,

5.2  HashSet集合介紹

查閱HashSet集合的API介紹:此類實現Set介面,由雜湊表支援(實際上是一個 HashMap集合)。HashSet集合不能保證的迭代順序與元素儲存順序相同。

HashSet集合,採用雜湊表結構儲存資料,保證元素唯一性的方式依賴於:hashCode()與equals()方法。

5.3  HashSet集合儲存資料的結構(雜湊表)

什麼是雜湊表呢?

雜湊表底層,使用的也是陣列機制陣列中也存放物件,而這些物件往陣列中存放時的位置比較特殊,當需要把這些物件給陣列中存放時,那麼會根據這些物件的特有資料結合相應的演算法,計算出這個物件在陣列中的位置,然後把這個物件存放在陣列中。而這樣的陣列就稱為雜湊陣列,即就是雜湊表。

當向雜湊表中存放元素時,需要根據元素的特有資料結合相應的演算法,這個演算法其實就是Object類中的hashCode方法。由於任何物件都是Object類的子類,所以任何物件有擁有這個方法。即就是在給雜湊表中存放物件時,會呼叫物件的hashCode方法,算出物件在表中的存放位置,這裡需要注意,如果兩個物件hashCode方法算出結果一樣,這樣現象稱為雜湊衝突,這時會呼叫物件的equals方法,比較這兩個物件是不是同一個物件,如果equals方法返回的是true,那麼就不會把第二個物件存放在雜湊表中,如果返回的是false,就會把這個值存放在雜湊表中。

總結:保證HashSet集合元素的唯一,其實就是根據物件的hashCode和equals方法來決定的。如果我們往集合中存放自定義的物件,那麼保證其唯一,就必須複寫hashCode和equals方法建立屬於當前物件的比較方式。

5.4   HashSet儲存JavaAPI中的型別元素

給HashSet中儲存JavaAPI中提供的型別元素時,不需要重寫元素的hashCode和equals方法,因為這兩個方法,在JavaAPI的每個類中已經重寫完畢,如String類、Integer類等。

l  建立HashSet集合,儲存String物件。

public class HashSetDemo {

    public static void main(String[] args) {

         //建立HashSet物件

         HashSet<String> hs = new HashSet<String>();

         //給集合中新增自定義物件

         hs.add("zhangsan");

         hs.add("lisi");

         hs.add("wangwu");

         hs.add("zhangsan");

         //取出集合中的每個元素

         Iterator<String> it = hs.iterator();

         while(it.hasNext()){

             String s = it.next();

             System.out.println(s);

         }

    }

}

輸出結果如下,說明集合中不能儲存重複元素:

wangwu

lisi

zhangsan

5.5  HashSet儲存自定義型別元素

給HashSet中存放自定義型別元素時,需要重寫物件中的hashCode和equals方法,建立自己的比較方式,才能保證HashSet集合中的物件唯一

l  建立自定義物件Student

public class Student {

    private String name;

    private int age;

    public Student(String name, int age) {

         super();

         this.name = name;

         this.age = age;

    }

    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;

    }

    @Override

    public String toString() {

         return "Student [name=" + name + ", age=" + age + "]";

    }

    @Override

    public int hashCode() {

         final int prime = 31;

         int result = 1;

         result = prime * result + age;

         result = prime * result + ((name == null) ? 0 : name.hashCode());

         return result;

    }

    @Override

    public boolean equals(Object obj) {

         if (this == obj)

             return true;

         if(!(obj instanceof Student)){

             System.out.println("型別錯誤");

             return false;

         }

         Student other = (Student) obj;

         return this.age ==  other.age && this.name.equals(other.name);

    }

}

 

l  建立HashSet集合,儲存Student物件。

public class HashSetDemo {

    public static void main(String[] args) {

         //建立HashSet物件

         HashSet hs = new HashSet();

         //給集合中新增自定義物件

         hs.add(new Student("zhangsan",21));

         hs.add(new Student("lisi",22));

         hs.add(new Student("wangwu",23));

         hs.add(new Student("zhangsan",21));

         //取出集合中的每個元素

         Iterator it = hs.iterator();

         while(it.hasNext()){

             Student s = (Student)it.next();

             System.out.println(s);

         }

    }

}

輸出結果如下,說明集合中不能儲存重複元素:

Student [name=lisi, age=22]

Student [name=zhangsan, age=21]

Student [name=wangwu, age=23]

5.6  LinkedHashSet介紹

我們知道HashSet保證元素唯一,可是元素存放進去是沒有順序的,那麼我們要保證有序,怎麼辦呢?

在HashSet下面有一個子類LinkedHashSet,它是連結串列和雜湊表組合的一個數據儲存結構。

    演示程式碼如下:

public class LinkedHashSetDemo {

    public static void main(String[] args) {

         Set<String> set = new LinkedHashSet<String>();

         set.add("bbb");

         set.add("aaa");

         set.add("abc");

         set.add("bbc");

Iterator it = set.iterator();

         while (it.hasNext()) {

             System.out.println(it.next());

         }

    }

}

輸出結果如下,LinkedHashSet集合保證元素的存入和取出的順序:

bbb


併發修改異常