java for 迭代器_Java基础-迭代器Iterator与语法糖for-each
迭代器Iterator與語法糖for-each
一、為什么需要迭代器
設(shè)計(jì)模式迭代器
迭代器作用于集合,是用來遍歷集合元素的對象。迭代器不是Java獨(dú)有的,大部分高級語言都提供了迭代器來遍歷集合。實(shí)際上,迭代器是一種設(shè)計(jì)模式:迭代器模式提供一種方法順序訪問一個聚合對象中的各個元素,而又不暴露其內(nèi)部的表示。
迭代器封裝了對集合的遍歷,使得不用了解集合的內(nèi)部細(xì)節(jié),就可以使用同樣的方式遍歷不同的集合。
暴露細(xì)節(jié)的遍歷
要理解封裝遍歷的好處,必須理解暴露細(xì)節(jié)的遍歷帶來的壞處。
以下是兩個不同的集合接口,第一個是自定義集合,第二個是JDK的java.util.Listpublic?interface?IUserDefinedList?{????int?length();????E?getElement(int?index);
}public?interface?List?{????int?size();????E?get(int?index);
}
分別使用for循環(huán)對它們進(jìn)行遍歷://?自定義Listfor?(int?i?=?0,?len?=?ul.length();?i?
System.out.println(ul.getElement(i));
}//?java.util.Listfor?(int?i?=?0,?size?=?ll.size();?i?
System.out.println(ll.get(i));
}
遍歷集合的代碼與具體集合類型緊密耦合,不同類型的集合,必須寫出不同的遍歷代碼,不可重用。緊耦合在良好的代碼設(shè)計(jì)中是大忌,這時需要將遍歷邏輯抽離,封裝。這就是迭代器模式了。
二、封裝遍歷-迭代器面向接口編程
面向接口編程是基本的設(shè)計(jì)原則,迭代器模式將遍歷封裝到接口,然后各個集合類可以以實(shí)現(xiàn)接口,或者組合接口實(shí)現(xiàn)類的方式,將遍歷封裝。
迭代器接口如下:public?interface?Iterator?{????boolean?hasNext();????E?next();
}
然后就可以以統(tǒng)一的方式遍歷集合:Iterator?it?=?aIterator;while(it.hasNext)?{
it.next();
}
因?yàn)榧隙紝?shí)現(xiàn)了Iterator接口,所以以上的遍歷代碼是可以重用的,并且與具體集合類型松耦合。
Java迭代器
Java提供的Iterator原理大致相同:
java迭代器接口
忽略Java8提供的默認(rèn)方法forEachRemaining(),java迭代器多了可以移除上一個元素的remove()方法。
Java集合中有很多迭代器的具體實(shí)現(xiàn),以ArrayList為例:
ArrayList迭代器實(shí)現(xiàn)
ArrayList的迭代器是以內(nèi)部類的方式實(shí)現(xiàn)的,每次調(diào)用List的iterator()方法,都會得到一個基于當(dāng)前ArrayList對象狀態(tài)的迭代器:public?Iterator?iterator()?{????return?new?Itr();
}
next()方法:public?E?next()?{
checkForComodification();????int?i?=?cursor;????if?(i?>=?size)????????throw?new?NoSuchElementException();
Object[]?elementData?=?ArrayList.this.elementData;????if?(i?>=?elementData.length)????????throw?new?ConcurrentModificationException();
cursor?=?i?+?1;????return?(E)?elementData[lastRet?=?i];
}
每次調(diào)用next()方法,都會調(diào)用final?void?checkForComodification()?{???if?(modCount?!=?expectedModCount)????????throw?new?ConcurrentModificationException();
}
檢查所迭代的列表對象是否被修改過,modCount是外部類的一個字段,當(dāng)調(diào)用外部類的add, remove, ensureCapacity等方法,都會改變改字段的值,而expectedModCount是內(nèi)部類Itr初始化迭代器實(shí)例時,使用modeCount賦值的字段。這樣,在使用迭代器過程中,如果對正在迭代的對象調(diào)用了add, remove, ensureCapacity等方法,再去調(diào)用迭代器的next(),就會引發(fā)ConcurrentModificationException異常了。
一個誘發(fā)異常的例子:ArrayList?ls?=?new?ArrayList<>();
Iterator?it?=?ls.iterator();while?(it.hasNext())?{
System.out.println(it.next());
ls.ensureCapacity(10);?//?改變了集合對象modCount的值,下次調(diào)用next?拋出異常}
三、語法糖for-each
java中的for-each:List?ls?=?new?ArrayList<>();for?(String?s:?ls)?{
System.out.println(s);
}
for-each其實(shí)只是java提供的語法糖。語法糖是編程語言提供的一些便于程序員書寫代碼的語法,是編譯器提供給程序員的糖衣,編譯時會對這些語法特殊處理。語法糖雖然不會帶來實(shí)質(zhì)性的改進(jìn),但是在提高代碼可讀性,提高語法嚴(yán)謹(jǐn)性,減少編碼錯誤機(jī)會上確實(shí)做出了很大貢獻(xiàn)。
Java要求集合必須實(shí)現(xiàn)Iterable接口,才能使用for-each語法糖遍歷該集合的實(shí)例。
JDK對該接口的描述是:Implementing this interface allows an object to be the target of * the "for-each loop" statement.
接口如下:public?interface?Iterable?{
Iterator?iterator();
default?void?forEach(Consumer?super?T>?action)?{
Objects.requireNonNull(action);????????for?(T?t?:?this)?{
action.accept(t);
}
}
default?Spliterator?spliterator()?{????????return?Spliterators.spliteratorUnknownSize(iterator(),?0);
}
}
忽略默認(rèn)方法,該接口要求集合實(shí)現(xiàn)iterator()方法,并返回一個迭代器對象Iterator。這也是java中的集合通過實(shí)現(xiàn)Iterable接口,組合Iterator來提供迭代器,并不通過直接實(shí)現(xiàn)Iterator接口的方式來提供集合迭代器的原因了。所有java集合迭代器的直接用法都如下:List?ls?=?new?ArrayList<>();
Iterator?it?=?ls.iterator();while?(it.hasNext())?{
System.out.println(it.next());
}
for-each的編譯器實(shí)現(xiàn)
for-each遍歷集合,實(shí)際上被翻譯如下:for?(I?#i?=?Expression.iterator();?#i.hasNext();?)?{
{VariableModifier}?TargetType?Identifier?=
(TargetType)?#i.next();
Statement
}
當(dāng)然,除了集合,for-each還可以遍歷數(shù)組,翻譯如下:T[]?#a?=?Expression;
L1:?L2:?...?Lm:for?(int?#i?=?0;?#i?
{VariableModifier}?TargetType?Identifier?=?#a[#i];
Statement
}
另外,使用javap命令,反編譯字節(jié)碼,可以看到編譯器是怎樣處理for-each的。public?class?Test?{????public?static?void?main(String[]?args)?{
List?ls?=?new?ArrayList<>();????????for?(String?s:?ls)?{
}
}
}
以上源文件對應(yīng)class文件的反編譯匯編為:C:\OTHERS\Working\ideaWork\utils\Z_practice\src\main\java>javap?-c?pattern\adapter\java_example\Test.class
Compiled?from?"Test.java"public?class?pattern.adapter.java_example.Test?{??public?pattern.adapter.java_example.Test();
Code:???????0:?aload_0???????1:?invokespecial?#1??????????????????//?Method?java/lang/Object."":()V
4:?return
public?static?void?main(java.lang.String[]);
Code:???????0:?new???????????#2??????????????????//?class?java/util/ArrayList
3:?dup???????4:?invokespecial?#3??????????????????//?Method?java/util/ArrayList."":()V
7:?astore_1???????8:?aload_1???????9:?invokeinterface?#4,??1????????????//?InterfaceMethod?java/util/List.iterator:()Ljava/util/Iterator;
14:?astore_2??????15:?aload_2??????16:?invokeinterface?#5,??1????????????//?InterfaceMethod?java/util/Iterator.hasNext:()Z
21:?ifeq??????????37
24:?aload_2??????25:?invokeinterface?#6,??1????????????//?InterfaceMethod?java/util/Iterator.next:()Ljava/lang/Object;
30:?checkcast?????#7??????????????????//?class?java/lang/String
33:?astore_3??????34:?goto??????????15
37:?return}
不用關(guān)注全部匯編細(xì)節(jié),只需要看到InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;就知道實(shí)際上是使用了迭代器。
至于數(shù)組的for-each,也可以通過觀察class文件的匯編代碼理解。
四、最佳實(shí)踐for-each遍歷的集合對象不能為null
既然對集合的for-each遍歷實(shí)際上是使用迭代器,會調(diào)用集合對象的iterator()方法獲得迭代器,那么,對null集合的for-each遍歷,就會在null集合對象上調(diào)用方法,勢必會拋出空指針異常。
for-each遍歷時不能改變正在遍歷的集合
因?yàn)樵谑褂玫鞅闅v集合時,不能夠改變集合,所以for-each遍歷時改變集合,同樣會引發(fā)ConcurrentModificationException異常。
作者:理查德成
鏈接:https://www.jianshu.com/p/186bf11ffe51
總結(jié)
以上是生活随笔為你收集整理的java for 迭代器_Java基础-迭代器Iterator与语法糖for-each的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 王者荣耀回应无法登录:部分用户由于网络波
- 下一篇: java美元兑换,(Java实现) 美元