引言
html
從JDK1.5起,增長了增強型的for循環語法,也被稱爲 「for-Each 循環」。增強型循環在操做數組與集合方面增長了很大的方便性。那麼,增強型for循環是怎麼解析的呢?同時,這是否是意味着基本for循環就會被取代呢?java
語法:程序員
for(var item:items){//var 表明各鍾類型
//相關操做
}
複製代碼
咱們先來看一下數組中的 for-Each 循環的使用;編程
String str[]= new String[]{"1","2","3"};
//普通for循環
for(int i=0;i<str.length;i++){
String item = str[i];
item += "str";
System.out.println(item);
}
//增強型for循環
for(String item:str){
item += "str";
System.out.println(item);
}
複製代碼
經過比較上面例子中的兩種類型的for循環,能夠看出,for-Each 循環編寫起來更加簡單,更加方便程序員。所以,在程序中,應該多使用增強型循環。數組
回答一下上面提出的兩個問題:bash
一、編譯器是怎麼處理數組中的for-Each循環的?微信
事實上,在數組中的 for-Each 最終會被編譯器處理成一個普通的for循環,也就是說 for-Each循環是徹底與普通for循環等價的,沒有任何特殊的命令。
能夠經過反編譯來驗證,很簡單,此處再也不多說。多線程
二、在數組中,for-Each 循環可否徹底替代普通for循環併發
答案是否認的。雖然for-Each 寫起來方便,但也有如下幾個侷限性:ide
只能對元素進行順序的訪問;
只能訪問數組或集合中的全部元素;
循環中沒有當前的索引,沒法對指定的元素操做。如更換當前索引位置的元素。
數組的增強型的for-Each循環很簡單,咱們再來看一下集合中的for-Each 循環又是怎麼樣的。咱們都知道集合中的遍歷都是經過迭代(iterator)完成的。也許有人說,也能夠按照下面的方式來遍歷集合,不必定非要使用迭代:
List<String> list = new LinkedList<String>();
list.add("a");
list.add("b");
list.add("c");
for(int i=0;i<list.size();i++){
String item = list.get(i);
System.out.println(item);
}
複製代碼
然而,這種方式對於基於鏈表實現的List來講,是比較耗性能的,由於get(int i)方法包含了一個循環,並且這個循環就是迭代遍歷一次List,直到遇到第i個元素,才中止循環,返回第i個元素。對於數量小,遍歷不頻繁的List來講,開銷能夠忽略。不然,開銷將不容忽視。
因此,正確集合遍歷是使用迭代器Iterator來遍歷的:
List<String> list = new LinkedList<String>();
list.add("a");
list.add("b");
list.add("c");
//獲取集合的迭代器
Iterator<String> itor = list.iterator();
//集合的普通for循環
for(;itor.hasNext();){//至關於 while(itor.hasNext())
String item = itor.next();
System.out.println(item);
}
複製代碼
再看看對應的for-Each循環的例子:
List<String> list = new LinkedList<String>();
list.add("a");
list.add("b");
list.add("c");
for(String item:list){//for-Each
System.out.println(item);
}
複製代碼
能夠看出,for-Each循環比普通for循環要簡潔不少。咱們依舊回答上面的兩個問題:
編譯器是如何處理 集合中的for-Each循環的?
public static void main(String args[])
{
List list = new LinkedList();
list.add("aa");
list.add("bb");
for(String item:list)
{
if("bb".equals(item))
list.add("cc");
}
}
複製代碼
咱們看一下上面例子的 反編譯代碼:
public static void main(String args[])
{
List list = new LinkedList();
list.add("aa");
list.add("bb");
for(Iterator iterator = list.iterator(); iterator.hasNext();)
{
String item = (String)iterator.next();
if("bb".equals(item))
list.add("cc");
}
}
複製代碼
與數組相似,編譯器最終也就是將集合中的for-Each循環處理成集合的普通for循環。 而集合的Collection
接口經過擴展Iterable
接口來提供iterator()
方。那麼咱們換一個角度,是否是隻要實現 Iterable
接口,提供iterator()
方法,也可使用 for-Each循環呢?來看個例子:
class MyList<T> implements Iterable<T>{
private ArrayList<T> list = new ArrayList<>();
public void addId(T id){
list.add(id);
}
public boolean removeId(T id){
return list.remove(id);
}
@Override
public Iterator<T> iterator() {//擴展自Iterable接口
//爲了簡單起見,就直接使用已有的迭代器
return list.iterator();
}
public static void main(String[] args) {
MyList<String> myList = new MyList<>();
myList.addId("666999");
myList.addId("973219");
//for-Each
for(String item:myList){
System.out.println(item);
}
}
}
複製代碼
上面的例子編譯經過,而且運行無誤。因此,只要實現了Iterable
接口的類,均可以使用for-Each循環來遍歷。
一、集合迭代的陷阱
集合循環遍歷時所使用的迭代器Iterator有一個要求:在迭代的過程當中,除了使用迭代器(如:Iterator.remove()
方法)對集合增刪元素外,是不容許直接對集合進行增刪操做。不然將會拋出 ConcurrentModificationException異常。因此,因爲集合的for-Each循環本質上使用的仍是Iterator來迭代,所以也要注意這個陷阱。for-Each循環很隱蔽地使用了Iterator,致使程序員很容易忽略掉這個細節,因此必定要注意。看下面的例子,for-Each循環中修改了集合。
public static void main(String[] args) {
List<String> list = new LinkedList<>();
list.add("aa");
list.add("bb");
for (String item : list) {//for-Each
if ("bb".equals(item)) {
list.add("cc"); //直接操做list
}
}
}
複製代碼
運行拋出異常:
上面僅僅是 單線程 下的狀況,若是你有併發編程的基礎的話,就會知道:在 多線程 的環境中,線程是交替運行的(時間片輪轉調度)。這就意味着,若是有兩個線程A、B,線程A對集合使用Iterator迭代遍歷,線程B則對集合進行增刪操做。線程A、B一旦交替運行,就會出如今迭代的同時對集合增刪的效果,也會拋出異常。解決辦法就是加鎖變成原子操做,多線程在這裏不是本文重點,很少說了。
一樣也是不能的。集合中的for-Each循環的侷限性與數組的for-Each循環是同樣的。集合的for-Each循環是不能對集合進行增刪操做、也不能獲取索引。而集合的普通for循環可使用的迭代器提供了對集合的增刪方法(如:Iterator.remove
,ListIterator.add()
),獲取索引的方法(如:ListIterator.nextIndex()
、ListIterator.previousIndex()
);
咱們來分析一下Iterator源碼,主要看看爲何在集合迭代時,修改集合可能會拋出ConcurrentModificationException
異常。以ArrayList中實現的Iterator爲例。
先來看一下ArrayList.iterator()
方法,以下:
public Iterator<E> iterator() {
return new Itr();
}
複製代碼
iterator()
方法直接建立了一個類Itr的對象。那就接着看 Itr類的定義吧!發現Itr
實際上是ArrayList
的內部類,實現了 Iterator
接口。
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // 當前的索引值,index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
//ArrayList的底層數組
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
//再次更新 expectedModCount
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
複製代碼
ArrayList.this.elementData
是ArrayList
的底層數組,上面這些方法都很簡單,都是對ArrayList.this.elementData
這個底層數組進行操做。
重點看一下checkForComodification()
方法,這個方法就是用來拋出 ConcurrentModificationException
異常,這個方法也很簡單,就是判斷modCount
與expectedModCount
是否相等。modCount
存儲的AarryList
中的元素個數。而expectedModCount
則是對象建立時將modCount
的值賦給它,也就是說expectedModCount
存儲的是迭代器建立時元素的個數。那麼checkForComodification()
方法其實在比較迭代期間,ArrayList
元素的個數 是否發生了改變,若是改變了,就拋出異常。注意一下,expectedModCount
除了在聲明時賦值外,也在remove()
方法中更新了一次。
不管是在數組中仍是在集合中,for-Each增強型for循環都是它們各自的普通for循環的一種「簡寫方式」,即二者意思上是等價的,但前者方便簡單,建議多使用。
for-Each循環不能徹底代替普通for循環,由於for-Each有必定的侷限性。
for-Each循環只能用於 數組、Iterable類型(包括集合)。
集合中的for-Each循環本質上使用了Ierator迭代器,因此要注意Itrator迭代陷阱(單線程和多線程都有問題)。
出處:http://www.cnblogs.com/jinggod/p/8424868.html
文章有不當之處,歡迎指正,你也能夠關注個人微信公衆號:`好好學java`,獲取優質學習資源。