1. 程式人生 > >Java語法糖(1):可變長度參數以及foreach循環原理

Java語法糖(1):可變長度參數以及foreach循環原理

i++ javap反編譯 信息 所有 子接口 inter new 編譯 意思

語法糖

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

可變長度參數

先講可變長度參數,看一段代碼:

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
我用數組遍歷的方式成功地將輸入的參數遍歷出來了,這說明兩個問題:

1、可以使用遍歷數組的方式去遍歷可變參數

2、可變參數是利用數組實現的

既然這樣,那我其實main函數也可以這麽寫,完全可以:

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:代碼MyEclipseTestArticleincom\xrq est21>javap -verbose TestMain.class
反編譯出來的內容很多,有類信息、符號引用、字節碼信息,截取一段信息:

public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: new #16 // class java/util/ArrayList
3: dup
4: invokespecial #18 // Method java/util/ArrayList."<in
it>":()V
7: astore_1
8: aload_1
9: ldc #19 // String 111
11: invokeinterface #21, 2 // InterfaceMethod java/util/List.
add:(Ljava/lang/Object;)Z
16: pop
17: aload_1
18: ldc #27 // String 222
20: invokeinterface #21, 2 // InterfaceMethod java/util/List.
add:(Ljava/lang/Object;)Z
25: pop
26: aload_1
27: invokeinterface #29, 1 // InterfaceMethod java/util/List.
iterator:()Ljava/util/Iterator;
看不懂沒關系,new、dup、invokespecial這些本來就是字節碼指令表內定義的指令,虛擬機會根據這些指令去執行指定的C++代碼,完成每個指令的功能。關鍵看到21、22這兩行就可以了,看到了一個iterator,所以得出結論:在編譯的時候編譯器會自動將對for這個關鍵字的使用轉化為對目標的叠代器的使用,這就是foreach循環的原理。進而,我們再得出兩個結論:

1、ArrayList之所以能使用foreach循環遍歷,是因為ArrayList所有的List都是Collection的子接口,而Collection是Iterable的子接口,ArrayList的父類AbstractList正確地實現了Iterable接口的iterator方法。之前我自己寫的ArrayList用foreach循環直接報空指針異常是因為我自己寫的ArrayList並沒有實現Iterable接口

2、任何一個集合,無論是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);
}
同樣反編譯一下,看一下關鍵的信息:

0: iconst_2
1: newarray int
3: dup
4: iconst_0
5: iconst_1
6: iastore
7: dup
8: iconst_1
9: iconst_2
10: iastore
11: astore_1
12: aload_1
13: dup
14: astore 5
16: arraylength
17: istore 4
19: iconst_0
20: istore_3
21: goto 39
24: aload 5
26: iload_3
27: iaload
28: istore_2
29: getstatic #16 // Field java/lang/System.out:Ljav
a/io/PrintStream;
32: iload_2
33: invokevirtual #22 // Method java/io/PrintStream.prin
tln:(I)V
36: iinc 3, 1
39: iload_3
40: iload 4
42: if_icmplt 24
45: return
這是完整的這段main函數對應的45個字節碼指令,因為這涉及一些壓棧、出棧、推送等一些計算機原理性的內容且對於這些字節碼指令的知識的理解需要一些C++的知識,所以就不解釋了。簡單對照字節碼指令表之後,我個人對於這45個字節碼的理解是Java將對於數組的foreach循環轉換為對於這個數組每一個的循環引用。

Java語法糖(1):可變長度參數以及foreach循環原理