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乱啊。

无意中看到了反编译的结果,哈哈,我要的东西在里面。

这正是我要的,证实了我的想法,这就是一个常引用啊,这里是常量赋值,就是只读的。

好,那再看看普通for循环

编译成字节码,体积较上一个代码还小了些。

再反编译

那么我们来看Java中的引用类型做了什么。

上源码

看反编译

果然是一个常引用。

四、总结

高级For在JDK 5.0开始引入,用其迭代代码简洁,但是要注意它取出的值是一个常变量,所以高级For循环可以用来遍历查询,不可修改当前取回的元素本身。

我在学习Java的时候,没有看到高级For的特点和缺点,方便的语法屏蔽了底层的实现,却不能让人了解幕后究竟是什么。

也许这个问题是很简单,是显而易见的,但是我就是不到黄河心不死,非要看个究竟。

时间: 2024-10-03 23:16:12

foreach的一个“奇怪”现象——实现原理分析的相关文章

一个日期算法的原理分析

1.问题描述 在 OSC 问答频道有一个问题:时间算法:帮忙解答下 简单的复述一遍就是能够通过如下式子来计算month月day日是一年的第几天. 闰年是 day_of_year=(275*month)/9 - (month+9)/12 + day - 30 非闰年比这个少1天.可以简单的验证,这个式子中每个部分计算后都取整,整个结果总是对的. 我们知道1.3.5.7.8.10.12都是31天,2月的天数有点诡异,其他都是30天,正常情况下我们写程序会写很多if来判断月份,进而计算累积的天数.但是

MySQL主从延迟现象及原理分析详解

一.现象 凌晨对线上一张表添加索引,表数据量太大(1亿+数据,数据量50G以上),造成主从延迟几个小时,各个依赖从库的系统无法查询数据,最终影响业务. 现在就梳理下主从延迟的原理. 二.原理 根据 MySQL 官方文档 MySQL Replication Implementation Details 中的描述,MySQL 主从复制依赖于三个线程:一个线程(),两个线程(和).主从复制流程如下图: master 服务器和 slave 服务器连接时,创建以发送数据: 一个对应一个 slave 服务器

手动实现一个单词统计MapReduce程序与过程原理分析

[toc] 手动实现一个单词统计MapReduce程序与过程原理分析 前言 我们知道,在搭建好hadoop环境后,可以运行wordcount程序来体验一下hadoop的功能,该程序在hadoop目录下的share/hadoop/mapreduce目录中,通过下面的命令: yarn jar $HADOOP_HOME/share/hadoop/mapreducehadoop-mapreduce-examples-2.6.4.jar wordcount inputPath outPath 即可对输入文

tcpkill工作原理分析

此文已由作者张耕源授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 日常工作生活中大家在维护自己的服务器.VPS有时会碰到这样的情况:服务器上突然出现了许多来自未知ip的网络连接与流量,我们需要第一时间切断这些可能有害的网络连接.除了iptables/ipset, blackhole routing这些常规手段,我们还可以借助一些更轻量级的小工具来临时处理这些情况,如tcpkill. tcpkill使用简介 tcpkill是一个网络分析工具集dsniff中的一个小工具.在

Adaboost算法原理分析和实例+代码(简明易懂)

Adaboost算法原理分析和实例+代码(简明易懂) [尊重原创,转载请注明出处] http://blog.csdn.net/guyuealian/article/details/70995333     本人最初了解AdaBoost算法着实是花了几天时间,才明白他的基本原理.也许是自己能力有限吧,很多资料也是看得懵懵懂懂.网上找了一下关于Adaboost算法原理分析,大都是你复制我,我摘抄你,反正我也搞不清谁是原创.有些资料给出的Adaboost实例,要么是没有代码,要么省略很多步骤,让初学者

Android 4.4 KitKat NotificationManagerService使用详解与原理分析(一)__使用详解

概况 Android在4.3的版本中(即API 18)加入了NotificationListenerService,根据SDK的描述(AndroidDeveloper)可以知道,当系统收到新的通知或者通知被删除时,会触发NotificationListenerService的回调方法.同时在Android 4.4 中新增了Notification.extras 字段,也就是说可以使用NotificationListenerService获取系统通知具体信息,这在以前是需要用反射来实现的. 转载请

黄金价格的变化及其经济学原理分析

目录 1.黄金价格的变化 1.1 黄金价格的概念 1.2国际市场金价的变化 1.3中国市场的黄金价格的变化 2. 黄金的历史演变和属性演变 2.1金本位的确立 2.2 布雷顿森林体系的建立与崩溃 3.黄金的属性 3.1财富属性 3.2 货币属性 3.3商品属性 3.4 投资品属性 4.黄金的定价机制及其对黄金价格的影响 4.1伦敦黄金市场的定价机制 4.2苏黎士黄金市场的定价机制 4.3纽约黄金市场的定价机制 4.4香港金银业贸易场的定价机制 4.5上海黄金交易所定价模式 5. 黄金价格及其影响

一个最不可思议的MySQL死锁分析

一个最不可思议的MySQL死锁分析 死锁问题背景 做MySQL代码的深入分析也有些年头了,再加上自己10年左右的数据库内核研发经验,自认为对于MySQL/InnoDB的加锁实现了如指掌,正因如此,前段时间,还专门写了一篇洋洋洒洒的文章,专门分析MySQL的加锁实现细节:<MySQL加锁处理分析>. 但是,昨天"润洁"同学在<MySQL加锁处理分析>这篇博文下咨询的一个MySQL的死锁场景,还是彻底把我给难住了.此死锁,完全违背了本人原有的锁知识体系,让我百思不得

linux下编译原理分析

linux下编译hello.c 程序,使用gcc hello.c,然后./a.out就可以运行:在这个简单的命令后面隐藏了许多复杂的过程,这个过程包括了下面的步骤: ====================================================================================== 预处理: 宏定义展开,所有的#define 在这个阶段都会被展开 预编译命令的处理,包括#if #ifdef 一类的命令 展开#include 的文件,像上面h