可變長度參數以及foreach循環原理

語法糖 html

接下來幾篇文章要開啓一個Java語法糖系列,因此首先講講什麼是語法糖。語法糖是一種幾乎每種語言或多或少都提供過的一些方便程序員開發代碼的語法,它只是編譯器實現的一些小把戲罷了,編譯期間以特定的字節碼或者特定的方式對這些語法作一些處理,開發者就能夠直接方便地使用了。這些語法糖雖然不會提供實質性的功能改進,可是它們或能提升性能、或能提高語法的嚴謹性、或能減小編碼出錯的機會。Java提供給了用戶大量的語法糖,好比泛型、自動裝箱、自動拆箱、foreach循環、變長參數、內部類、枚舉類、斷言(assert)等。java

 

可變長度參數程序員

先講可變長度參數,看一段代碼:設計模式

複製代碼
public static void main(String[] args)
{
    print("000", "111", "222", "333");
}
    
public static void print(String... strs)
{
    for (int i = 0; i < strs.length; i++)
    {
        System.out.println(strs[i]);
    }
}
複製代碼

print方法的參數的意思是表示傳入的String個數是不定的,看一下代碼的運行結果:數組

000
111
222
333

我用數組遍歷的方式成功地將輸入的參數遍歷出來了,這說明兩個問題:函數

一、可使用遍歷數組的方式去遍歷可變參數性能

二、可變參數是利用數組實現的編碼

既然這樣,那我其實main函數也能夠這麼寫,徹底能夠:spa

String[] strs = {"000", "111", "222", "333"};
print(strs);

那直接傳入一個數組不就行了?問題是,數組是要指定長度的,萬一此次我想傳2個String,下次我想傳3個String怎麼辦呢?設計

最後,注意一點,可變長度參數必須做爲方法參數列表中的的最後一個參數且方法參數列表中只能有一個可變長度參數

 

foreach循環原理

之前對foreach循環就是這麼用着,觸動我去研究foreach循環的原理的緣由是大概兩個月前,本身寫了一個ArrayList,想用foreach循環遍歷一下看一下寫的效果,結果報了空指針異常。本文就寫寫foreach循環的原理,先看一下這麼一段代碼:

複製代碼
public static void main(String[] args)
{
    List<String> list = new ArrayList<String>();
    list.add("111");
    list.add("222");
    
    for (String str : list)
    {
        System.out.println(str);
    }
}
複製代碼

用foreach循環去遍歷這個list,結果就不說了,都知道。看一下Java是如何處理這個foreach循環的,javap反編譯一下:

F:\代碼\MyEclipse\TestArticle\bin\com\xrq\test21>javap -verbose TestMain.class

反編譯出來的內容不少,有類信息、符號引用、字節碼信息,截取一段信息:

複製代碼
 1   public static void main(java.lang.String[]);
 2     flags: ACC_PUBLIC, ACC_STATIC
 3     Code:
 4       stack=2, locals=4, args_size=1
 5          0: new           #16                 // class java/util/ArrayList
 6          3: dup
 7          4: invokespecial #18                 // Method java/util/ArrayList."<in
 8 it>":()V
 9          7: astore_1
10          8: aload_1
11          9: ldc           #19                 // String 111
12         11: invokeinterface #21,  2           // InterfaceMethod java/util/List.
13 add:(Ljava/lang/Object;)Z
14         16: pop
15         17: aload_1
16         18: ldc           #27                 // String 222
17         20: invokeinterface #21,  2           // InterfaceMethod java/util/List.
18 add:(Ljava/lang/Object;)Z
19         25: pop
20         26: aload_1
21         27: invokeinterface #29,  1           // InterfaceMethod java/util/List.
22 iterator:()Ljava/util/Iterator;
複製代碼

看不懂不要緊,new、dup、invokespecial這些原本就是字節碼指令表內定義的指令,虛擬機會根據這些指令去執行指定的C++代碼,完成每一個指令的功能。關鍵看到2一、22這兩行就能夠了,看到了一個iterator,因此得出結論:在編譯的時候編譯器會自動將對for這個關鍵字的使用轉化爲對目標的迭代器的使用,這就是foreach循環的原理。進而,咱們再得出兩個結論:

一、ArrayList之因此能使用foreach循環遍歷,是由於ArrayList全部的List都是Collection的子接口,而Collection是Iterable的子接口,ArrayList的父類AbstractList正確地實現了Iterable接口的iterator方法。以前我本身寫的ArrayList用foreach循環直接報空指針異常是由於我本身寫的ArrayList並無實現Iterable接口

二、任何一個集合,不管是JDK提供的仍是本身寫的,只要想使用foreach循環遍歷,就必須正確地實現Iterable接口

實際上,這種作法就是23中設計模式中的迭代器模式

 

數組呢?

上面的講完了,好理解,可是不知道你們有沒有疑問,至少我是有一個疑問的:數組並無實現Iterable接口啊,爲何數組也能夠用foreach循環遍歷呢?先給一段代碼,再反編譯:

複製代碼
public static void main(String[] args)
{
    int[] ints = {1,2,3,4,5};
        
    for (int i : ints)
        System.out.println(i);
}
複製代碼

一樣反編譯一下,看一下關鍵的信息:

複製代碼
 1          0: iconst_2
 2          1: newarray       int
 3          3: dup
 4          4: iconst_0
 5          5: iconst_1
 6          6: iastore
 7          7: dup
 8          8: iconst_1
 9          9: iconst_2
10         10: iastore
11         11: astore_1
12         12: aload_1
13         13: dup
14         14: astore        5
15         16: arraylength
16         17: istore        4
17         19: iconst_0
18         20: istore_3
19         21: goto          39
20         24: aload         5
21         26: iload_3
22         27: iaload
23         28: istore_2
24         29: getstatic     #16                 // Field java/lang/System.out:Ljav
25 a/io/PrintStream;
26         32: iload_2
27         33: invokevirtual #22                 // Method java/io/PrintStream.prin
28 tln:(I)V
29         36: iinc          3, 1
30         39: iload_3
31         40: iload         4
32         42: if_icmplt     24
33         45: return
複製代碼

這是完整的這段main函數對應的45個字節碼指令,由於這涉及一些壓棧、出棧、推送等一些計算機原理性的內容且對於這些字節碼指令的知識的理解須要一些C++的知識,因此就不解釋了。簡單對照字節碼指令表以後,我我的對於這45個字節碼的理解是Java將對於數組的foreach循環轉換爲對於這個數組每個的循環引用。其實就是將foreache拆成for循環去執行,循環每個數組內的內容。

 

轉載:http://www.cnblogs.com/xrq730/p/4868465.html

相關文章
相關標籤/搜索