为什么变量初始化到赋值表达式[String x = (x = y)]会编译通过?

20

为什么这个代码能够通过编译?据我理解,编译器会检查变量的类型(在本例中是String),然后看右侧表达式的类型是否对应于变量的类型(或至少是其子类型,但我们来简单处理String类,因为它是final类)。

public class InitClass {
  public static void main(String[] args) {
    String str = (str = "hello");
    System.out.println(str);
  }
}

我的问题是str = "hello"是如何编译的?编译器是否已经意识到str应该是String类型?


(str =“hello”)返回字符串“hello”。Eclipse通过消息“变量str的赋值没有效果”来阻止它。 - LeTex
3
@LeTex 我通常会将 Eclipse 设置得更加严格,将这些东西报告为错误而不是警告。我认为没有任何理由让任何人使用这样的结构,即使它们被 JLS 允许。 - biziclop
2
这是一个有趣的问题,让你不禁思考“谁会写出这样的代码,为什么要这样写”。纯粹是学术研究吧? - TEK
2
@TEK 如果你想知道我是怎么得到这个问题的,那只是因为一个简单的复制粘贴问题 :-) - user2336315
3个回答

16

在评估 赋值表达式

首先,左操作数会被评估以生成一个变量。如果此评估突然完成,则赋值表达式由于相同原因而突然完成;不会评估右操作数,也不会进行赋值。

这将产生变量 str。然后

否则,将评估右操作数。如果此评估突然完成,则赋值表达式由于相同原因而突然完成,并且不会进行赋值。

在您的示例中,右侧操作数本身是另一个赋值表达式。所以, 赋值运算符的右操作数 str 再次被评估以生成变量 str。然后

否则,将把右操作数的值转换为左手变量的类型,被提交给适当的标准值集(不是扩展指数值集)进行值设置转换(§5.1.13), 并将转换结果存储到变量中。

因此,"hello" 存储在 str 中。 由于

在运行时,赋值表达式的结果是变量在赋值后的值。 赋值表达式的结果本身不是变量。

"hello" 赋给 str 的结果是值 "hello",该值再次存储在 str 中。


5
您的情况相当于
String str;
str = (str = "hello");

虽然这些任务看起来很有趣,但从概念上讲并没有什么问题。
然而,引用自身的变量初始化显然不是一个好主意。编译器会在可能是程序员错误的情况下尝试标记它;有时编译器无法标记它;有时编译器也会过度标记它。
局部变量有比字段变量更严格的要求——必须在使用其值之前先进行赋值。例如,以下代码将无法编译,因为局部变量在被分配之前就被读取了。
String str;  // local variable
str = str;   // error, try to read `str` before it's assigned

一个字段变量总是有一个默认的初始值;然而,编译器会检查明显的程序员错误。

int x = x+1;  // error. x is field variable.

但是如果这样的检查失败也不会造成灾难,因为在显式赋值之前x的值为0

int x;
{ x=x+1; } // ok. x==0, then x==1 after assignment

然而,如果 xfinal,上述代码会失败,因为编译器要求在读取 x 之前先确定赋值。对于局部变量也有相同的要求。但是这种检查可以被绕过,因为对于字段变量来说,完全分析和防止它是不可能的。
final int x = (this).x+1;  // compiles!

在某些情况下,编译器过于苛刻,阻止了涉及lambda的合理使用情况。
Runnable r1 = ()->System.out.println(r1);  // r1 is a field variable 

这个用例在概念上并没有问题;它可以通过(this).来规避。


1
你的回答最后一部分非常有趣,+1 - user2336315

0

首先发生的是编译器识别引用的类型,然后知道它是一个字符串,将“hello”分配给str是有效的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接