在所有关于Java的书中,我都读到编译器以相同的方式处理所有空白,只是忽略额外的空白,因此最好大量使用它们来提高代码的可读性。我在我写的每一句话中都找到了证据:有没有空格,有多少空格都无关紧要(或者我只是没有注意)。
最近,我决定对运算符优先级和关联性进行一些实验,以测试优先级表的实际操作,并尝试编译
int a = 2;
int b = 3;
int c = a+++b;
int d = a+++++b;
虽然前者的语句编译得很完美,但后者产生了一个异常:
线程"main"java.lang.RuntimeException中的异常:不可执行源代码-意外类型。必需:变量。找到:值。
但是,当我添加空格int d = a++ + ++b
时,它进行了编译。为什么会出现这种情况?据说Java无论如何都会忽略多余的空白。(如果这很重要的话,我有Java 8和Netbeans IDE 8.2。)
我想这可能与表达式的解析方式有关,但我不确定。我试着在SO和谷歌上查找了几个关于解析、空格和运算符的问题,但找不到确切的答案。
UPD。为了解决这样的评论,即重要的是"额外",而不是所有的空白:由于int c = a++ + b;
和int c=a+++b;
都是编译的,可以类比地说,在int d = a ++ + ++b;
中,空白也是"额外"的。
Java语言规范第3.2节,"词汇翻译",says(emphasis mine):
使用以下三个词汇翻译步骤,将原始Unicode字符流翻译成标记序列,依次应用:
Unicode转义的翻译[…]
将[…]翻译成输入字符流和行终止符[…]。
将第2步产生的输入字符流和行终止符转换为输入元素序列(§3.5),该序列在空白(§3.6)和注释(§3.7)被丢弃后,包括标记,即句法语法的终止符号(§2.3)
在每一步都使用尽可能长的翻译,即使结果最终没有形成正确的程序,而另一个词汇翻译会。
因此,在确定"输入元素序列"之后,将丢弃空白字符。第3.5节"输入元素和令牌"说:
空白(§3.6)和注释(§3.7)可以用来分隔标记,如果这些标记相邻,则可能以另一种方式标记化例如,只有在没有空格或注释的情况下,输入中的ASCII字符-和=才能形成运算符标记-=(§3.12)
语法分析器需要了解您在写什么。
从语法分析器的角度来看,如果不在加号和加号之间放置分隔符,就无法理解加号的序列。
在最小值上添加更多的空格不会改变结果。
所以两条线都有相同的结果:
int d = a++ + ++b;
int d = a++ + ++b;
考虑以下代码:
int d = a +++ b;
你的意图是什么?
int d = a + ++b;
或
int d = a++ + b;
此外,从人类的角度来看,如果没有额外的空白,就不可能理解。
此外,如果这段代码适用于编译器,从人类的角度来看是不可理解的。
从编译器的角度来看,没有空格的序列a++++++b
是不可理解的,因为他试图读取尽可能多的字符以确定导致序列a ++ ++ + b
不是有效的令牌序列的令牌。
无论如何,我的建议是从人类的角度尽可能保持代码的干净,这样它将更容易维护、阅读和增强。因此,在需要时使用空格,不要滥用空格,但如果生成的代码可读性较差,则不要删除空格。
这里应该标记为正确的答案是严格按照JLS关于您所给出的示例所说的答案。这个答案有点推测性,但它首先要观察int c
:发生了什么
int c = a+++b;
这被评估为:
int c = (a++) + b; // c == 6
因此,编译器似乎正在分配后缀运算符,该运算符具有非常高的优先级。现在:
int d = a+++++b;
如果我们也试图形成后缀运算符,我们会遇到问题:
int d = (a++)++ + b;
上面的内容不会编译,因为我们不能将++
应用于除基元之外的任何内容。因此,这在编译时失败。
我可以从@Daniel那里看到答案,他引用JLS的话说,每一步都使用尽可能长的翻译,这意味着在添加之前会尝试后缀,因为后者更长。这与d
表达式似乎正在发生的情况一致。
解析器将使用尽可能多的字符:
a+++b
被解析为a, ++, +, b
a+++++b
解析为a, ++, ++, +, b
前者是有效语法,但后者不是。