为什么在注释中执行Java代码时允许使用某些Unicode字符?

java unicode comments


下面的代码产生的输出是 "Hello World!" (没有真的,试一试)。

public static void main(String... args) {

   // The comment below is not a typo.
   // \u000d System.out.println("Hello World!");
}

原因是Java编译器将Unicode字符 \u000d 解析为新行,并转换为:

public static void main(String... args) {

   // The comment below is not a typo.
   //
   System.out.println("Hello World!");
}

从而导致评论被 "执行"。

由于可以将其用于“隐藏”恶意代码或任何邪恶的程序员可以想象的内容,因此为什么允许在注释中使用它

为什么Java规范允许这样做?




Answer 1 aioobe


Unicode解码发生在任何其他词法翻译之前。这样做的主要好处是,它使得在ASCII和其他任何其他编码之间来回转换很简单。你甚至不需要弄清楚注释的开始和结束的位置。

JLS第3.3节所述,这允许任何基于ASCII的工具来处理源文件:

[...] The Java programming language specifies a standard way of transforming a program written in Unicode into ASCII that changes a program into a form that can be processed by ASCII-based tools. [...]

这就从根本上保证了平台的独立性(支持的字符集的独立性),这一直是Java平台的一个重要目标。

能够在文件中的任何地方写任何Unicode字符是一个很好的功能,尤其是在注释中,当记录非拉丁语语言的代码时,这个功能尤为重要。事实上,它可以以如此微妙的方式干扰语义,这只是一个(不幸的)副作用。

这个主题有很多陷阱,Joshua Bloch和Neal Gafter的Java Puzzlers包括以下变体:

这是一个合法的Java程序吗?如果是,它打印的是什么?

\u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0020\u0020\u0020
\u0063\u006c\u0061\u0073\u0073\u0020\u0055\u0067\u006c\u0079
\u007b\u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0020\u0020
\u0020\u0020\u0020\u0020\u0073\u0074\u0061\u0074\u0069\u0063
\u0076\u006f\u0069\u0064\u0020\u006d\u0061\u0069\u006e\u0028
\u0053\u0074\u0072\u0069\u006e\u0067\u005b\u005d\u0020\u0020
\u0020\u0020\u0020\u0020\u0061\u0072\u0067\u0073\u0029\u007b
\u0053\u0079\u0073\u0074\u0065\u006d\u002e\u006f\u0075\u0074
\u002e\u0070\u0072\u0069\u006e\u0074\u006c\u006e\u0028\u0020
\u0022\u0048\u0065\u006c\u006c\u006f\u0020\u0077\u0022\u002b
\u0022\u006f\u0072\u006c\u0064\u0022\u0029\u003b\u007d\u007d

(这个程序原来是一个普通的 "Hello World "程序)。

在解题思路中,他们指出了以下几点。

更严重的是,此难题有助于加强前三个方面的教训:当您需要在程序中插入无法以任何其他方式表示的字符时,Unicode转义必不可少。在所有其他情况下,请避免使用它们。


来源:Java:在注释中执行代码?




Answer 2 Holger


既然尚未解决,请在此处进行解释,解释为什么Unicode转义的转换发生在任何其他源代码处理之前:

其背后的想法是,它允许在不同字符编码之间无损翻译Java源代码。如今,已经有广泛的Unicode支持,这似乎不成问题,但是那时,来自西方国家的开发人员要从他的亚洲同事那里收到一些包含亚洲字符的源代码并不容易,需要进行一些更改( (包括对其进行编译和测试)并将结果发送回去,而不会损坏任何内容。

因此,Java源代码可以用任何编码编写,并允许在标识符,字符和 String 文字以及注释中使用各种字符。然后,为了无损地传输它,所有目标编码不支持的字符都将被其Unicode转义符代替。

这是一个可逆的过程,有趣的是,翻译可以通过不需要了解Java源代码语法的任何工具来完成,因为翻译规则不依赖于它。这是因为编译器内部的实际Unicode字符转换也独立于Java源代码语法进行。这意味着您可以在两个方向上执行任意数量的翻译步骤,而无需更改源代码的含义。

这是另一个甚至没有提到的奇怪功能的原因: \uuuuuuxxxx 语法:

当翻译工具转义字符并遇到已经是转义序列的序列时,它应在序列中插入一个附加 u ,将 \ucafe 转换为 \uucafe 。含义没有改变,但是当转换为另一个方向时,该工具应仅删除一个 u 并仅将包含单个 u 的序列替换为其Unicode字符。这样,来回转换时,即使Unicode转义也保留其原始形式。我猜,没有人使用过该功能...




Answer 3 Pepijn Schmitz


我想完全无效地补充一点,只是因为我没办法,而且还没看到我提出来,这个问题是无效的,因为它包含了一个隐藏的前提,这个前提是错误的,那就是代码是在注释中的,所以这个问题是无效的!我想说的是,这个问题是无效的。

在Java源代码中,u000d在各方面都相当于一个ASCII CR字符。它是一个行结尾,简单明了,无论它在哪里出现,都是一个行结尾。问题中的格式化是有误导性的,那一串字符实际上在语法上对应的是什么。

public static void main(String... args) {
   // The comment below is no typo. 
   // 
 System.out.println("Hello World!");
}

因此,IMHO最正确的答案是:代码执行,因为它不在注释中,而是在下一行。"在注释中执行代码 "在Java中是不允许的,就像你所期望的那样。

造成这种混乱的主要原因是语法突出显示和IDE不够复杂,无法考虑到这种情况。他们要么根本不处理Unicode转义,要么在解析代码之后而不是像 javac 那样在处理代码之前进行处理。




Answer 4 zwol


所述 \u000d 逃逸终止评论因为 \u 转义均匀地转化为相应的Unicode字符之前被标记化的程序。您也可以使用 \u0057\u0057 而不是 //开始注释。

这是您IDE中的一个错误,该错误应在语法上突出显示该行以使 \u000d 结束注释。

这也是该语言的设计错误。现在无法纠正,因为这会破坏依赖它的程序。 \u 转义符应该仅在“有意义”的上下文中(字符串文字和标识符,并且可能在其他任何地方)仅由编译器转换为相应的Unicode字符,或者应禁止它们在U + 0000中生成字符– 007F量程,或两者兼而有之。这两种语义中的任何一种都可以防止注释被 \u000d 转义符终止,而不会干扰 \u 转义符有用的情况-请注意,其中包括\u 在非拉丁脚本中对注释进行转义是一种对注释进行编码的方法,因为文本编辑器可以比编译器更广泛地了解 \u 转义的重要性。(不过,我不知道任何编辑器或IDE将在任何上下文中将 \u 转义符显示为相应的字符。)

有一个在C家族相似的设计错误,1其中反斜杠换行评论边界之前处理被确定,所以例如

// this is a comment \
   this is still in the comment!

我提出这一点是为了说明,如果您习惯于考虑标记化和解析编译器程序员的思维方式,那么容易犯此特定的设计错误,并且直到意识到为时已晚才意识到这是一个错误。关于令牌化和解析。基本上,如果您已经定义了正式的语法,然后有人提出了语法上的特殊情况-三字母组合,反斜杠换行符,在仅限于ASCII的源文件中编码任意Unicode字符(无论需要使用什么),则更容易在令牌生成器之前添加一个转换过程而不是重新定义令牌生成器,以注意使用该特殊情况的合理位置。

1对于学徒:我知道C的这一方面是100%有意的,其基本原理(我不是在编造这一原理)是,它允许您将带有任意长行的代码机械地强制拟合到打孔卡上。这仍然是一个错误的设计决定。




Answer 5 Jonathan Gibbons


这是一个有意为之的设计选择,可以追溯到Java的最初设计。

对于那些问 "谁想在注释中使用Unicode转义符?"的人,我想他们是指母语使用拉丁语字符集的人。换句话说,在Java的原始设计中,人们可以在Java程序中合法的地方使用任意的Unicode字符,最典型的是在注释和字符串中。

这可以说是用于查看源文本的程序(如IDE)的一个缺陷,即这些程序不能解释Unicode转义符并显示相应的字形。