【问题标题】:Assigning a variable, what actually happens, Java分配一个变量,实际发生了什么,Java
【发布时间】:2013-02-24 13:11:33
【问题描述】:

在下面的例子中实际发生了什么?

int a = 1;
a += (a = 2);

输出是 3,但我想知道幕后实际发生了什么。 例如,我知道括号对+ 具有更高的优先级,因此首先发生(a = 2)表达式应该变为a = 2 + 2。 在运行时,首先应该执行括号内的表达式,然后 a 变为 2。似乎 + 左侧的第一个 a(a = 2) 之前被“加载”,最后一个表达式似乎没有覆盖之前的加载。 换句话说,我对幕后究竟发生了什么感到很困惑。

如果有人知道,提前非常感谢。

【问题讨论】:

  • int a = 1; int tmpvar = (a = 2); a += tmpvar;
  • 翻译成a = a + (a = 2);,操作数从左到右求值。
  • 顺便说一句,在这样的语句中间实际使用赋值是一种资本编程犯罪。
  • 不执行括号。或评估。或者任何东西。它们只是消除了关联的歧义。

标签: java runtime variable-assignment


【解决方案1】:

我们看一下下面程序的字节码:

package A;

public class Test
{
    public static void main(String[] args)
    {
        int a = 1;
        a += (a = 2);
    }
}

我们只需要运行这个命令:

javap -c Test.class

获取以下字节码:

public class A.Test {
  public A.Test();
    Code:
       0: aload_0
       1: invokespecial #1           // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iload_1
       3: iconst_2
       4: dup
       5: istore_1
       6: iadd
       7: istore_1
       8: return
}

说明:

我们将只关注 main 方法中的两行:

int a = 1;
a += (a = 2);

[int a = 1; 从这里开始]

0: iconst_1
  • 将 int 1 推入堆栈。
-------------
|           |
-------------
|           |
-------------
|     1     |
-------------
    STACK

1: istore_1
  • 将int值从堆栈中弹出到variable 1variable 1代表a
-------------
|           |             variable 1
-------------           --------------
|           |           |     1      |
-------------           --------------
|           |
-------------
    STACK

[int a = 1; 在这里结束]


[a += (a = 2); 从这里开始]

2: iload_1
  • 从本地 variable 1 加载一个 int 值并将其压入堆栈。
-------------
|           |             variable 1
-------------           --------------
|           |           |            |
-------------           --------------
|     1     |
-------------
    STACK

3: iconst_2
  • 将 int 2 推入堆栈。
-------------
|           |             variable 1
-------------           --------------
|     2     |           |            |
-------------           --------------
|     1     |
-------------
    STACK

4: dup
  • 复制堆栈顶部的值。
-------------
|     2     |             variable 1
-------------           --------------
|     2     |           |            |
-------------           --------------
|     1     |
-------------
    STACK

5: istore_1
  • 将 int 值从堆栈中弹出到 variable 1
-------------
|           |             variable 1
-------------           --------------
|     2     |           |      2     |
-------------           --------------
|     1     |
-------------
    STACK

6: iadd
  • 将前两个值相加。
-------------
|           |             variable 1
-------------           --------------
|           |           |      2     |
-------------           --------------
|     3     |
-------------
    STACK

7: istore_1
  • 将 int 值从堆栈中弹出到 variable 1
-------------
|           |             variable 1
-------------           --------------
|           |           |      3     |
-------------           --------------
|           |
-------------
    STACK

[a += (a = 2); 在这里结束]


8: return
  • main 方法返回。

结论:

a = a + (a = 2) 是通过几个操作完成的。 2: iload_1 作为a += (a = 2); 的第一个命令执行,它读取方程a = a + (a = 2) 的第一个操作数并压入堆栈。

接下来,3: iconst_24: dup 被执行,它们基本上将 int 2 两次压入堆栈;一个用于将其加载到a,另一个作为第二个操作数。之后,5: istore_1 被执行,将2 加载到a (a = 2)。

最后,6: iadd7: istore_1 被执行,其中6: iadd 将第一个操作数和第二个操作数相加并将结果压入堆栈,7: istore_1 弹出结果并将其加载到a


为简单起见,让我们快速看一下这段代码:

int a = 1;
int b = 3;
a += b;

这是它的字节码:

public class A.Test {
  public A.Test();
    Code:
       0: aload_0
       1: invokespecial #1            // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_3
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: istore_1
       8: return
}

如您所见,它只是执行以下操作:

  • 将 int 1 加载到 a
  • 将 int 3 加载到 b
  • a 然后b 推入堆栈。
  • 对它们执行加法并将结果压入堆栈。
  • 从堆栈中弹出结果并将其存储到a

【讨论】:

    【解决方案2】:

    参见http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.7.1 中引用的示例15.7.1-2,它与您提供的示例几乎相同。特别是:

    如果运算符是复合赋值运算符(第 15.26.2 节),则 左手操作数的评估包括记住 左侧操作数表示的变量以及获取和保存 该变量的值用于隐含的二元运算。

    由于这种优先级,首先计算 += 的左手。

    您可能会因为括号而感到困惑,但请注意括号评估部分:http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.7.3,尤其是:

    Java 编程语言尊重求值顺序 用括号显式表示,由运算符隐式表示 优先级。

    在这种情况下,+= 运算符设置的隐式优先级表示将根据规范记住左侧操作数。虽然赋值运算符(包括“+=”)的优先级最低,但 += 的规范表明左侧操作数将在 15.26.2 中被记住。

    【讨论】:

    • 其实+=的优先级最低,应该最后评估
    • 请阅读示例文本:在下面的程序中,两个赋值语句都获取并记住左侧操作数的值,即 9,在右侧操作数之前计算加法运算符,此时变量设置为 3。
    • 这个例子和你的一样,只是编号不同。
    【解决方案3】:

    来自 JLS section §15.26.2 Compound Assignment Operators

    E1 op= E2 形式的复合赋值表达式是等价的 到 E1 = (T)((E1) op (E2)),其中 T 是 E1 的类型,除了 E1 只评估一次。

    因此,对于您的示例,我们有:

    a = (a) + (a = 2)
    

    表达式从左到右求值。因此输出 3

    【讨论】:

    • 您引用的声明没有说明评估顺序。
    • @rich.okelly 好的,所以基本上左边的(a)被隐式地放在括号中?
    • @nhahtdh 是推断出来的 - 优先顺序由带括号消除歧义的语句中的顺序保证。
    • @Rollerball 可以,但不影响语句的结果。那里的括号是多余的。 = 运算符的右侧从左到右计算
    • @rich.okelly:按照你的推理,最好说顺序是由所有有效的op从左到右评估这一事实来保证的。
    猜你喜欢
    • 2011-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-02
    • 1970-01-01
    • 2014-09-28
    相关资源
    最近更新 更多