for循環用了那么多次,但你真的了解它么?
標簽: Java基礎進階
一、基礎的for循環
0、使用while也是一種循環方式,此處探究for相關的循環,就不做拓展了。
1、遍歷數組的時候,初學時是使用的如下樣式的for循環:
for(int i=0;i<a.length;i ){
System.out.println(n);
}
2、而遍歷集合的時候使用的都是Iterator迭代器:
給定一組人名,兩兩組隊(此處允許自己和自己組隊),實現如下:
enum Option {Tom, Jerry, Jack, Mary}
想象中的寫法是:
Collection<Option> options = Arrays.asList(Option.values());
for(Iterator<Option> i = options.iterator(); i.hasNext();){
for (Iterator<Option> j = options.iterator(); j.hasNext();) {
System.out.println(i.next() " " j.next());
}
但是執行過后你會發現這段代碼是有瑕疵的,出現的結果只有四組:
那么剩下的組合去哪里了呢?
這里程序并不會拋出異常,只是單純的因為i.next()
每次都會取下一個值,所以就出現了上圖的情況。
但是,如果外部集合的元素大于內部元素:
例如下面這段代碼:
enum OptionFirst {Tom, Jerry, Jack, Mary}
enum OptionSecond {Tom, Jerry, Jack, Mary, Mali, Tomsun, Lijie, Oolyyou}
public static void main(String[] args) {
Collection<OptionFirst> optionFirstCollection = Arrays.asList(OptionFirst.values());
Collection<OptionSecond> optionSecondCollection = Arrays.asList(OptionSecond.values());
for (Iterator<OptionFirst> i = optionFirstCollection.iterator(); i.hasNext(); ) {
for (Iterator<OptionSecond> j = optionSecondCollection.iterator(); j.hasNext(); ) {
System.out.println(i.next() " " j.next());
}
}
}
運行后,就會拋出java.util.NoSuchElementException
異常,造成的原因就是因為外部循環調用了多次,而內不循環因為元素不足,導致循環拋出了這樣的異常。
要想解決這種困擾只需要在二次循環前添加一個變量來保存外部元素;即可實現想要達到的效果。
Collection<Option> options = Arrays.asList(Option.values());
for(Iterator<Option> i = options.iterator(); i.hasNext();){
Option option = i.next();
for (Iterator<Option> j = options.iterator(); j.hasNext();) {
System.out.println(option " " j.next());
}
}
二、for-each循環
這種循環方式不論是數組還是集合都實用,而且效率更高;表達形式更加簡潔明了。
for(Element e:elements){
System.out.println(e);
}
當再次遇到上面的兩兩組隊問題時,根本不需要考慮元素不足的問題,而且代碼也簡潔多了:
for (Option option : options) {
for (Option rank : options) {
System.out.println(option " " rank);
}
}
《Effective Java》中是這樣子寫for-each循環的:
三、for-each is not god
說了for-each那么多好處,但是它也不是神,并非萬能的,有那么三種情況是它需要注意的。
3.1、解構過濾的時候不能使用
如果需要遍歷集合,并刪除選定的元素,就需要使用顯式的迭代器,以便可以調用它的remove方法。不過在Java8中增加的Collection的removeIf方法常常可以避免顯式的遍歷。
例如下面這段代碼:
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
System.out.println("執行if語句成功");
}
}
直接運行這段代碼是沒什么問題的,數組list能成功刪除元素1,也能打印對應語句。
但是,我們進行如下任意一種操作:
- 若把list.remove(item)換成list.add(“3”);操作如何?
- 若在第6行添加list.add("3");那么代碼會出錯嗎?
- 若把if語句中的“1”換成“2”,結果你感到意外嗎?
如果都能正確執行當然就不需要問了,所以3個都會報ConcurrentModificationException的異常;
而出現這些情況的原因稍稍解釋下就是:
首先,這涉及多線程操作,Iterator是不支持多線程操作的,List類會在內部維護一個modCount的變量,用來記錄修改次數。
舉例:ArrayList源碼
protected transient int modCount = 0;
每生成一個Iterator,Iterator就會記錄該modCount,每次調用next()方法就會將該記錄與外部類List的modCount進行對比,發現不相等就會拋出多線程編輯異常。
為什么這么做呢?我的理解是你創建了一個迭代器,該迭代器和要遍歷的集合的內容是緊耦合的,意思就是這個迭代器對應的集合內容就是當前的內容,我肯定不會希望在我冒泡排序的時候,還有線程在向我的集合里插入數據對吧?所以Java用了這種簡單的處理機制來禁止遍歷時修改集合。
至于為什么刪除“1”就可以呢,原因在于foreach和迭代器的hasNext()方法,foreach這個語法,實際上就是
while(itr.hasNext()){
itr.next()
}
所以每次循環都會先執行hasNext(),那么看看ArrayList的hasNext()是怎么寫的:
public boolean hasNext() {
return cursor != size;
}
cursor是用于標記迭代器位置的變量,該變量由0開始,每次調用next執行 1操作,于是:
所以代碼在執行刪除“1”后,size=1,cursor=1,此時hasNext()返回false,結束循環,因此你的迭代器并沒有調用next查找第二個元素,也就無從檢測modCount了,因此也不會出現多線程修改異常;但當你刪除“2”時,迭代器調用了兩次next,此時size=1,cursor=2,hasNext()返回true,于是迭代器傻乎乎的就又去調用了一次next(),因此也引發了modCount不相等,拋出多線程修改的異常。
當你的集合有三個元素的時候,你就會神奇的發現,刪除“1”是會拋出異常的,但刪除“2”就沒有問題了,究其原因,和上面的程序執行順序是一致的。
因此,在《阿里巴巴Java開發手冊中有這樣一條規定》:
3.2、轉換
如果需要遍歷列表或數組,并取代它的部分或者全部元素值,就需要使用列表迭代器或者數組索引,以便替換元素的值。
因為for-each是一循到底的,中間不做停留和位置信息的顯示;所以要替換元素就不能使用它了。
3.3、平行迭代
如果需要并行的遍歷多個集合,就需要顯式的控制迭代器或者索引變量,以便所有迭代器或者索引變量都可以同步前進(就像上面講述Iterator迭代器的時候提到的組合減少的情況,只想出現下標一一對應的元素組合)。
四、總結
for-each循環不僅適用于遍歷集合和數組,而且能讓你遍歷任何實現Iterator接口的對象;最最關鍵的是它還沒有性能損失。而對數組或集合進行修改(添加刪除操作),就要用for循環。
所以循環遍歷所有數據的時候,能用它的時候還是選擇它吧,嘻嘻。
歡迎關注公眾號:Java學習之道
個人博客網站:www.mmzsblog.cn
智能推薦
你真的了解CSS包含塊么?
我所了解的CSS包含塊 包含塊有什么作用呢?下文接下來帶你知曉 ↓↓↓↓↓↓↓ 文章目錄 我所了解的CSS包含塊 指出錯誤觀念 什么是包含塊? 根元素包含塊 其他元素的包含塊 如何確定元素的包含塊? 元素包含塊的作用? 下面看些例子 示例一 示例二 示例三 示例四 示例五 后記 指出錯誤觀念 許多開發者認為一個元素的包含塊就是他...
Python趣味入門01:你真的了解Python么?
0、Why Python ? 什么入門用python,其實這和它的氣質有關,根據CHM(計算機歷史博物館)網站介紹[1],Van Rossum 曾經在1999年陳述過創造Python的動機,“她”應該是這樣: PS分享:很多人在學習Python的過程中,往往因為遇問題解決不了或者沒好的教程從而導致自己放棄,為此我整理啦從基礎的python腳本到web開發、爬蟲...
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
猜你喜歡
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...
統計學習方法 - 樸素貝葉斯
引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...
styled-components —— React 中的 CSS 最佳實踐
https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...