foreach的一個“奇怪”現象——實現原理分析

先看一個長長的代碼,其實很簡單,就是使用不同的方法迭代Map,對值進行修改,只要遇到foreach就發現賦值看似成功,實則失敗。

就想搞清楚爲什麼,不想直接從搜索引擎搜來別人的總結好的背下來。

 

1、問題的引出

1.1、測試的源代碼

   1: public static void main(String[] args) {
   2:     Map<String, String[]> map = new HashMap<String, String[]>();
   3:     map.put("key1", new String[] { "值" });
   4:     System.out.println();
   5:     System.out.println("#######第一次#######");
   6:     System.out.println("#######第一種迭代#######");
   7:     String[] values = null;
   8:     for (String str : map.keySet()) {
   9:         values = map.get(str);
  10:         System.out.println(values.hashCode());
  11:         for (int i = 0; i < values.length; i++) {
  12:             System.out.println(values[i].hashCode());
  13:         }
  14:     }
  15:     System.out.println();
  16:     System.out.println("#######第二次#######");
  17:     System.out.println("#######第二種迭代#######");
  18:     for (Entry<String, String[]> entry : map.entrySet()) {
  19:         values = entry.getValue();
  20:         System.out.println(values.hashCode());
  21:         for (String string : values) {
  22:             System.out.println(string.hashCode());
  23:         }
  24:     }
  25:     System.out.println();
  26:     System.out.println("#######第三次#######");
  27:     System.out.println("#######第一種迭代#######");
  28:     values = null;
  29:     for (String str : map.keySet()) {
  30:         values = map.get(str);
  31:         System.out.println(values.hashCode());
  32:         for (int i = 0; i < values.length; i++) {
  33:             System.out.println("舊:" + values[i].hashCode());
  34:             values[i] = values[i] + "_1";
  35:             System.out.println("新:" + values[i].hashCode());
  36:         }
  37:     }
  38:     System.out.println("#######第二種迭代#######");
  39:     for (Entry<String, String[]> entry : map.entrySet()) {
  40:         values = entry.getValue();
  41:         System.out.println(values.hashCode());
  42:         for (String string : values) {
  43:             System.out.println(string.hashCode() + "-->" + string);
  44:         }
  45:     }
  46:     System.out.println();
  47:     System.out.println("#######第四次#######");
  48:     System.out.println("#######第二種迭代#######");
  49:     values = null;
  50:     for (Entry<String, String[]> entry : map.entrySet()) {
  51:         values = entry.getValue();
  52:         System.out.println(values.hashCode());
  53:         for (String string : values) {
  54:             System.out.println("舊:" + string.hashCode());
  55:             string = string + "_2";
  56:             System.out.println("新:" + string.hashCode());
  57:         }
  58:     }
  59:     System.out.println("#######第一種迭代#######");
  60:     for (String str : map.keySet()) {
  61:         values = map.get(str);
  62:         System.out.println(values.hashCode());
  63:         for (int i = 0; i < values.length; i++) {
  64:             System.out.println(values[i].hashCode() + "-->" + values[i]);
  65:         }
  66:     }
  67:     System.out.println("#######第二種迭代#######");
  68:     for (Entry<String, String[]> entry : map.entrySet()) {
  69:         values = entry.getValue();
  70:         System.out.println(values.hashCode());
  71:         for (String string : values) {
  72:             System.out.println(string.hashCode() + "-->" + string);
  73:         }
  74:     }
  75:     System.out.println("值沒有改變,還是指向原來的字符串位置");
  76:     System.out.println();
  77:     System.out.println("#######第五次#######");
  78:     System.out.println("#######第二種迭代#######");
  79:     values = null;
  80:     for (Entry<String, String[]> entry : map.entrySet()) {
  81:         values = entry.getValue();
  82:         System.out.println(values.hashCode());
  83:         for (int i = 0; i < values.length; i++) {
  84:             System.out.println("舊:" + values[i].hashCode());
  85:             values[i] = values[i] + "_3";
  86:             System.out.println("新:" + values[i].hashCode());
  87:         }
  88:     }
  89:     System.out.println("#######第一種迭代#######");
  90:     for (String str : map.keySet()) {
  91:         values = map.get(str);
  92:         System.out.println(values.hashCode());
  93:         for (int i = 0; i < values.length; i++) {
  94:             System.out.println(values[i].hashCode() + "-->" + values[i]);
  95:         }
  96:     }
  97:     System.out.println("#######第二種迭代#######");
  98:     for (Entry<String, String[]> entry : map.entrySet()) {
  99:         values = entry.getValue();
 100:         System.out.println(values.hashCode());
 101:         for (String string : values) {
 102:             System.out.println(string.hashCode() + "-->" + string);
 103:         }
 104:     }
 105:     System.out.println("實驗結果就是:使用簡單for遍歷來賦值成功,使用高級for循環取出賦值失敗");
 106: }
 

1.2、運行結果


#######第一次#######
#######第一種迭代#######
1951510703
20540
 
#######第二次#######
#######第二種迭代#######
1951510703
20540
 
#######第三次#######
#######第一種迭代#######
1951510703
舊:20540
新:19741934
#######第二種迭代#######
1951510703
19741934-->值_1
 
#######第四次#######
#######第二種迭代#######
1951510703
舊:19741934
新:1792132385
#######第一種迭代#######
1951510703
19741934-->值_1
#######第二種迭代#######
1951510703
19741934-->值_1
值沒有改變,還是指向原來的字符串位置
 
#######第五次#######
#######第二種迭代#######
1951510703
舊:19741934
新:1792132386
#######第一種迭代#######
1951510703
1792132386-->值_1_3
#######第二種迭代#######
1951510703
1792132386-->值_1_3
實驗結果就是:使用簡單for遍歷來賦值成功,使用高級for循環取出賦值失敗


實驗結果就是:

使用簡單for遍歷來賦值成功,使用高級for循環取出賦值失敗

 

二、問題

爲什麼它是隻讀的呢,編譯器做了什麼?高級for做了什麼?

沒有找到合適的理由,先認爲高級for它是隻讀的吧。

 

三、問題的解決

熬了半宿,只是有一個猜測,那麼怎麼證實呢?

寫一個最最簡單的程序,使用foreach,先看字節碼。

public class TestFor {
    public static void main(String[] args) {
        int a[] = new int[] { 1, 2, 3 };
        for (int i : a) {
            System.out.println(i);
        }
    }
}
程序簡單的,都不用加註釋了。

看字節碼?javap看了半天沒有很亂的,怎麼辦?

換工具,都是32位的,64位的牆外邊不好拿到,怎麼辦?

安裝虛擬機,裝32位系統,非要看看裏面是什麼。

用工具打開一看那是一個亂啊,引用常量表,那個goto亂啊。

無意中看到了反編譯的結果,哈哈,我要的東西在裏面。

1

這正是我要的,證實了我的想法,這就是一個常引用啊,這裏是常量賦值,就是隻讀的。

好,那再看看普通for循環

2

編譯成字節碼,體積較上一個代碼還小了些。

再反編譯

3

 

 

那麼我們來看Java中的引用類型做了什麼。

上源碼

4

看反編譯

5

果然是一個常引用。

 

四、總結

高級For在JDK 5.0開始引入,用其迭代代碼簡潔,但是要注意它取出的值是一個常變量,所以高級For循環可以用來遍歷查詢,不可修改當前取回的元素本身。

 

我在學習Java的時候,沒有看到高級For的特點和缺點,方便的語法屏蔽了底層的實現,卻不能讓人瞭解幕後究竟是什麼。

也許這個問題是很簡單,是顯而易見的,但是我就是不到黃河心不死,非要看個究竟。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章