我們能自己寫一個容器類,然後使用 for-each 迴圈碼?
阿新 • • 發佈:2019-01-25
今天繼續分享一道Java經典面試題:
直接上題:
我們能自己寫一個容器類,然後使用 for-each 迴圈嗎?
可以,你可以寫一個自己的容器類。如果你想使用 Java 中增強的迴圈來遍歷,你只需要實現 Iterable 介面。如果你實現 Collection 介面,預設就 具有該屬性。
對於for-each的理解答案過於淺 所以上網查詢了相關資料進行了拓展,現在分享給大家:
for-each迴圈是jdk1.5引入的新的語法功能。並不是所有東西都可以使用這個迴圈的。可以看下Iterable介面的註釋,它說明了除了陣列外,其他類想要使用for-each迴圈必須實現這個介面。這一點表明除了陣列外的for-each可能底層是由迭代器實現的。
Iterable介面在1.8之前只有一個方法,Iterator<T> iterator(),此方法返回一個迭代器。由於更早出現的Collection介面中早就有了這個同樣的方法,所以只需要讓Collection介面繼承Iterable介面,基於Collection的集合類就可以不做任何更改就使用for-each迴圈。
對於陣列,因為陣列不實現Iterable介面,它的for-each實現原理應該和Collection不一樣。 下面就通過分析下不同方式編譯後的位元組碼,簡單研究下for-each的的底層原理。 一、陣列的for-each 下面是的兩個很簡單的類,可以看出它們的功能是一樣的。Java環境使用的是jdk1.8_111。- package iter;
- public class TestArray {
- public static void main(String[] args) {
- //String[] a = {"a", "b", "c"};
- long[] a = {2L, 3L, 5L};
- for (long i : a) {
- System.err.println(i);
- }
- }
- }
- package iter;
- public class TestArrayFor {
- public
- //String[] a = {"a", "b", "c"};
- long[] a = {2L, 3L, 5L};
- for (int i = 0, len = a.length; i < len; i++) {
- System.err.println(a[i]);
- }
- }
- }
- Compiled from "TestArray.java"
- public class iter.TestArray {
- public iter.TestArray();
- Code:
- 0: aload_0
- 1: invokespecial #8 // Method java/lang/Object."<init>":()V
- 4: return
- public static void main(java.lang.String[]);
- Code:
- 0: iconst_3
- 1: newarray long
- 3: dup
- 4: iconst_0
- 5: ldc2_w #16 // long 2l
- 8: lastore
- 9: dup
- 10: iconst_1
- 11: ldc2_w #18 // long 3l
- 14: lastore
- 15: dup
- 16: iconst_2
- 17: ldc2_w #20 // long 5l
- 20: lastore
- 21: astore_1 /* 0-21行,建立long陣列a,並儲存線上程的當前棧幀的區域性變量表的第1格*/
- 22: aload_1 /* 讀取儲存線上程的當前棧幀的區域性變量表的第1格的物件的引用,就是讀取陣列a */
- 23: dup /* 把a的引用複製一遍並且再放到棧頂 */
- 24: astore 6 /* 把棧頂的數存線上程的當前棧幀的區域性變量表的第6格,就是生成a的一個值複製品b並存儲起來,暫時不知道為什麼這裡要複製一次,後面的陣列都還是用a表示 */
- 26: arraylength /* 獲取陣列長度a.length */
- 27: istore 5 /* 把陣列長度儲存線上程的當前棧幀的區域性變量表的第5格,22-27隱式執行了int len = a.length */
- 29: iconst_0 /* 讀取數字0(這個就是普通的0)到棧中 */
- 30: istore 4 /* 把數字0放線上程的當前棧幀的區域性變量表的第4格,29-30隱式執行了int i = 0 */
- 32: goto 51 /* 無條件跳轉到51那個地方,開始迴圈的程式碼 */
- 35: aload 6 /* 讀取陣列a */
- 37: iload 4 /* 讀取i */
- 39: laload /* 讀取a[i] */
- 40: lstore_2 /* 把a[i]存線上程的當前棧幀的區域性變量表的第2格 */
- 41: getstatic #22 // Field java/lang/System.err:Ljava/io/PrintStream; /* 獲取類的static屬性,就是System.err */
- 44: lload_2 /* 讀取存線上程的當前棧幀的區域性變量表的第2格的資料,就是讀取a[i] */
- 45: invokevirtual #28 // Method java/io/PrintStream.println:(J)V /* 執行虛擬機器方法,30-36就是執行System.err.println(a[i]) */
- 48: iinc 4, 1 /* 將第4格的數字加1,就是執行i++ */
- 51: iload 4 /* 讀取i */
- 53: iload 5 /* 讀取a.length */
- 55: if_icmplt 35 /* 如果i < len,跳到標記35的那個地方,不滿足就往下 */
- 58: return
- }
- Compiled from "TestArrayFor.java"
- public class iter.TestArrayFor {
- public iter.TestArrayFor();
- Code:
- 0: aload_0
- 1: invokespecial #8 // Method java/lang/Object."<init>":()V
- 4: return
- public static void main(java.lang.String[]);
- Code:
- 0: iconst_3
- 1: newarray long
- 3: dup
- 4: iconst_0
- 5: ldc2_w #16 // long 2l
- 8: lastore
- 9: dup
- 10: iconst_1
- 11: ldc2_w #18 // long 3l
- 14: lastore
- 15: dup
- 16: iconst_2
- 17: ldc2_w #20 // long 5l
- 20: lastore
- 21: astore_1 /* 0-21行,建立long陣列a,並儲存線上程的當前棧幀的區域性變量表的第1格*/
- 22: iconst_0 /* 讀取數字0(這個就是普通的0)到棧中 */
- 23: istore_2 /* 將棧頂的數字0儲存在第二個,22-23就是執行int i = 0; */
- 24: aload_1 /* 讀取儲存線上程的當前棧幀的區域性變量表的第1格的物件的引用,就是讀取陣列a */
- 25: arraylength /* 獲取陣列長度a.length */
- 26: istore_3 /* 把陣列長度儲存線上程的當前棧幀的區域性變量表的第3格,24-26就是執行int len = a.length */
- 27: goto 42 /* 無條件跳到標記42的那個地方,開始迴圈的程式碼 */
- 30: getstatic #22 // Field java/lang/System.err:Ljava/io/PrintStream; /* 獲取類的static屬性,就是System.err */
- 33: aload_1 /* 讀取陣列a */
- 34: iload_2 /* 讀取i */
- 35: laload /* 讀取a[i] */
- 36: invokevirtual #28 // Method java/io/PrintStream.println:(J)V /* 執行虛擬機器方法,30-36就是執行System.err.println(a[i]) */
- 39: iinc 2, 1 /* 將第2格的數字加1,就是執行i++ */
- 42: iload_2 /* 讀取i */
- 43: iload_3 /* 讀取len */
- 44: if_icmplt 30 /* 如果i < len,跳到標記30的那個地方,不滿足就往下 */
- 47: return
- }
二、Collection的for-each 還是先貼兩段簡單的對比的程式碼,程式碼邏輯一樣。Java環境使用的是jdk1.8_111。
- package iter;
- import java.util.ArrayList;
- import java.util.List;
- public class TestFor {
- public static void main(String[] args) {
- List<String> listA = new ArrayList<String>();
- for(String str : listA) {
- System.err.println(str);
- }
- }
- }
- package iter;
- import java.util.ArrayList;
- import java.util.Iterator;
- import java.util.List;
- public class TestIter {
- public static void main(String[] args) {
- List<String> listA = new ArrayList<String>();
- for (Iterator<String> iter = listA.iterator(); iter.hasNext();) {
- String s = iter.next();
- System.err.println(s);
- }
- }
- }
- Compiled from "TestFor.java"
- public class iter.TestFor {
- public iter.TestFor();
- Code:
- 0: aload_0
- 1: invokespecial #8 // Method java/lang/Object."<init>":()V
- 4: return
- public static void main(java.lang.String[]);
- Code:
- 0: new #16 // class java/util/ArrayList
- 3: dup
- 4: invokespecial #18 // Method java/util/ArrayList."<init>":()V
- 7: astore_1
- 8: aload_1
- 9: invokeinterface #19, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
- 14: astore_3
- 15: goto 35
- 18: aload_3
- 19: invokeinterface #25, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
- 24: checkcast #31 // class java/lang/String
- 27: astore_2
- 28: getstatic #33 // Field java/lang/System.err:Ljava/io/PrintStream;
- 31: aload_2
- 32: invokevirtual #39 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 35: aload_3
- 36: invokeinterface #45, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
- 41: ifne 18
- 44: return
- }
- Compiled from "TestIter.java"
- public class iter.TestIter {
- public iter.TestIter();
- Code:
- 0: aload_0
- 1: invokespecial #8 // Method java/lang/Object."<init>":()V
- 4: return
- public static void main(java.lang.String[]);
- Code:
- 0: new #16 // class java/util/ArrayList
- 3: dup
- 4: invokespecial #18 // Method java/util/ArrayList."<init>":()V
- 7: astore_1
- 8: aload_1
- 9: invokeinterface #19, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
- 14: astore_2
- 15: goto 35
- 18: aload_2
- 19: invokeinterface #25, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
- 24: checkcast #31 // class java/lang/String
- 27: astore_3
- 28: getstatic #33 // Field java/lang/System.err:Ljava/io/PrintStream;
- 31: aload_3
- 32: invokevirtual #39 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 35: aload_2
- 36: invokeinterface #45, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
- 41: ifne 18
- 44: return
- }
這兩段位元組碼中自帶的註釋很多,基本上看得懂,就不添加註釋了。 兩段位元組碼除了幾個變數儲存線上程的當前棧幀的區域性變量表的索引(astore_n,這個n就是索引)不一樣外,其餘的都是一模一樣的。不排除某次編譯後連那幾個索引值也一樣,那就真一模一樣了。位元組碼自帶的註釋都說了,Collection的for-each底層也是使用迭代器來實現的,兩種方式可以說是完全等價的。 對於實現了RandomAccess介面的實現類,因為它們的隨機訪問操作的時間複雜度為O(1),大多數情況使用傳統for迴圈會比用迭代器迴圈(這裡的迭代器也可以用for-each替換,上面說了它們底層整體是一樣的)要快。至於這一點是為什麼,可以看下ArrayList的原始碼。它的迭代器雖然也是通過下標直接訪問elementData陣列,但是迭代器多了很多方法呼叫以及其他的額外操作,現在很多編譯器cpu也都會對傳統for迴圈進行特別的優化,在這個層面十幾個指令的差別就很大了,這些因素加在一起導致RandomAccess的迭代器比傳統for迴圈要慢一些。對於ArrayList這種,在cpu密集型的應用中應該只使用傳統for迴圈,在迴圈體執行時間比較長的應用中,傳統for迴圈和迭代器迴圈的差別就很小了,這時候使用迭代器(for-each迴圈)也不會明顯降低執行效率。