Foreach





Foreach

面試中,初入行業的人往往最怕問到基礎的原理問題,爲什麼呢?因爲初入行業的人,對新技術、框架等比較好奇,研究的內容多而廣,卻遲遲停留於表面。如果你是這類人,那麼咱麼一起來研究熟悉而又陌生的內容,如果不是,那麼請多多指教。

介紹

起源

最早Foreach出現於Jdk1.5中,較爲舒適的寫法,引來了大量注目者,直至如今,已經廣泛的出現代碼中。此優秀的書寫規則一直被沿用下去,在Jdk1.8中,出現了Lamda表達式、Stream流等,皆是延用此寫法。

優點

  1. 書寫簡單,看起來清爽

缺點

  1. 遍歷過程中無法操作元素,比如賦值、刪除操作(此刪除操作有彩蛋,具體請看JavaList遍歷之刪除)。
  2. 同時只能遍歷一個Collection或數組,不能同時遍歷多餘一個Collection或數組。
  3. 只能正向遍歷,不能反向遍歷
  4. 只能Jdk1.5及之後的版本可以支持(相信這個對大夥,都是不存在的問題,是誰還用老版本呢)

原理

說的人多了,那便是真理?網上搜一搜Foreach原理,大致都這麼說:Foreach最終會變爲迭代器來完成遍歷的。

對此,半信半疑,是個程序員,有點自信的,一定要問,爲啥,啥時候把Foreach變爲迭代器遍歷了,在編譯前底層代碼處理,還是編譯後直接改寫爲迭代器遍歷?

好,我們不妨先提出了三個問題:

  1. List實現了迭代器,如果證明?
  2. 何時把Foreach執行,改成了和迭代器遍歷的一個效果?
  3. 數組也可以使用Foreach遍歷,但是好像沒有實現迭代器呀?

List與迭代器

博主不是一個大神,但是絕對是一個好奇的人,不管是爲了面試,還是爲了在小夥伴面前吹一波,博主和大家一樣,翻了常用的集合源碼,爲了驗證迭代器的實現也看到了以下的實現圖:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tWhrzhYI-1583936760604)(Foreach.assets/List.png)]

這裏很明顯的一點就是AbstractList實現了Iterable接口,這個Iterable大家也不會陌生,肯定和Iterator有關,不妨扒出來看看,下面這張圖可以看出,其中方法iterator()直接返回了一個迭代器。這個方法也是我們使用迭代器遍歷List,經常要用到的方法。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-mrsm2rqD-1583936760611)(Foreach.assets/image-20191210203244936.png)]

再看看我們我們平時使用迭代器遍歷的寫法,原來如此,但是各位要注意的是:每個集合的

        Iterator iterator = getList().iterator();
        while(iterator.hasNext()) {
            System.out.println(iterator.next());
        }

iterator()方法的實現並非都進行了重寫,以Jdk1.8爲例,ArrayList重寫了iterator()方法,實現了自己的Inner Class:Itr(Itr實現了Iterator接口),而LinkedList並沒有重寫iterator()方法,而是其父類AbstractSequentialList負責進行了重寫。

到此,我們已經驗證了List中的確實現了迭代器相關的內容。

Foreach如何變成了迭代器遍歷

在講着個問題之前,博主先說下,還在翻Jdk源碼、各種在源碼打斷點的童鞋,停下來,咱麼要麼百度下,要麼看下Jdk官方文檔,如果都不行,那你就聽我嘮嘮嗑。爲什麼這麼說呢,壓根在Jdk源碼中,就找不到Foreach被改寫或者被執行成迭代器的代碼。

那麼,答案是什麼呢?不賣關子了,講重點:

Jdk的編譯器在編譯的時候,會將Foreach語句改寫爲迭代器遍歷的寫法,這個改動從Jdk1.5開始。

What?編譯器搞得鬼?我有點不信,就說誰動了我代碼。不信的話,你聽我講一個驗證的方案。咱們把含有Foreach的代碼編譯一下,在把字節碼反編譯一下(能看得懂字節碼的同學請忽略反編譯過程),看看最終反編譯獲得的代碼是不是不一樣了,原本的Foreach被改寫成了迭代器遍歷。

反編譯工具:IDEA就行,通過直接打開Target下面的.class文件,即可得到反編譯之後的代碼。代碼如下:

    @Test
    public void testForeachSource() {
        List<Integer> list = Arrays.asList(1, 2, 3);
        for (Integer i : list) {
            System.out.println(i);
        }
    }

反編譯後:

    @Test
    public void testForeachSource() {
        List<Integer> list = Arrays.asList(1, 2, 3);
        Iterator var2 = list.iterator();

        while(var2.hasNext()) {
            Integer i = (Integer)var2.next();
            System.out.println(i);
        }
    }

看到了吧,被編譯器搞了一波吧,好叻,收拾收拾,各位準備找同事、朋友吹牛去了。

數組爲何可以用Foreach

估摸着大家看了上面的分析,都能猜測下面該怎麼驗證數組的實現了。

測試代碼如下:

    @Test
    public void testArrayForeachSource() {
        Integer[] array = new Integer[3];
        array[0] = 0;
        array[1] = 1;
        array[2] = 2;
        for (Integer i : array) {
            System.out.println(i);
        }
    }

反編譯:

    @Test
    public void testArrayForeachSource() {
        Integer[] array = new Integer[]{0, 1, 2};
        Integer[] var2 = array;
        int var3 = array.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            Integer i = var2[var4];
            System.out.println(i);
        }
    }

看了反編譯後面的代碼,無需多說,編譯器在對數組的Foreach處理時,會改寫成基本的for()循環來處理。

舉一反三

看一道題:

	@Test
	public void testForeach() {
		for (Integer i : getList()) {
			System.out.println(i);
		}
		System.out.println("&&&&&&&");
		for (Integer i : getArrays()) {
			System.out.println(i);
		}
		System.out.println("&&&&&&&");
		for (int i = 0; i < getArrays().length; i++) {
			System.out.println(i);
		}
	}

	private static List<Integer> getList() {
		System.out.println("------------");
		return Arrays.asList(1, 2, 3);
	}

	private static Integer[] getArrays() {
		System.out.println("------------");
		return (Integer[]) Arrays.asList(1, 2, 3).toArray();
	}

你們認爲,執行結果事什麼呢?猜錯了沒事,在看下下面的反編譯代碼,你就會明白爲什麼了。

以下爲執行結果:

------------
1
2
3
&&&&&&&
------------
1
2
3
&&&&&&&
------------
0
------------
1
------------
2
------------

反編譯:

    @Test
    public void testForeach() {
        Iterator var1 = getList().iterator();

        while(var1.hasNext()) {
            Integer i = (Integer)var1.next();
            System.out.println(i);
        }

        System.out.println("&&&&&&&");
        Integer[] var5 = getArrays();
        int var7 = var5.length;

        for(int var3 = 0; var3 < var7; ++var3) {
            Integer i = var5[var3];
            System.out.println(i);
        }

        System.out.println("&&&&&&&");

        for(int i = 0; i < getArrays().length; ++i) {
            System.out.println(i);
        }

    }

    private static List<Integer> getList() {
        System.out.println("------------");
        return Arrays.asList(1, 2, 3);
    }

    private static Integer[] getArrays() {
        System.out.println("------------");
        return (Integer[])((Integer[])Arrays.asList(1, 2, 3).toArray());
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章