提问



以下代码生成输出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规范允许这样做?

最佳参考


Unicode解码在任何其他词汇翻译之前进行。这样做的主要好处是可以在ASCII和任何其他编码之间来回切换。你甚至不需要弄清楚评论的开始和结束!


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



  [[...]] Java编程语言指定了一种将用Unicode编写的程序转换为ASCII的标准方法,该程序将程序转换为可由基于ASCII的工具处理的形式。 [[...]]



这为平台独立性(支持的字符集的独立性)提供了基本保证,这一直是Java平台的关键目标。


能够在文件中的任何位置编写任何Unicode字符是一个简洁的功能,在使用非拉丁语言记录代码时,在评论中尤为重要。它可以以这种微妙的方式干扰语义这一事实只是(不幸的)副作用。


关于这个主题有许多问题,Joshua Bloch和Neal Gafter的 Java Puzzlers 包括以下变体:[33]



  这是一个合法的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:在评论中执行代码?![34]

其它参考1


由于尚未解决,这里有一个解释,为什么Unicode转义的转换发生在任何其他源代码处理之前:


其背后的想法是它允许在不同的字符编码之间无损翻译Java源代码。今天,有广泛的Unicode支持,这看起来不是一个问题,但是当时西方国家的开发人员从包含亚洲字符的亚洲同事那里收到一些源代码并不容易做出一些改变(包括编译和测试它并将结果发回,所有这些都不会损坏。


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


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


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


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


其它参考2


我将完全无效地添加这一点,仅仅是因为我无法帮助自己而我还没有看到它,这个问题是无效的,因为它包含一个错误的隐藏前提,即代码在一条评论!


在Java源代码中,\\ u000d在各方面都与ASCII CR字符等效。无论它出现在哪里,它都是一个简单明了的行。问题中的格式是误导性的,字符序列实际上在语法上对应的​​是:


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


恕我直言,最正确的答案是:代码执行因为它不在评论中;它在下一行。 Java中不允许在注释中执行代码,就像您期望的那样。


大部分混淆源于语法高亮显示器和IDE不够复杂以考虑这种情况这一事实。他们要么根本不处理unicode转义,要么在解析代码之后而不是之前执行它,比如javac的确如此。

其它参考3


\u000d转义终止注释,因为\u转义在程序被标记化之前统一转换为相应的Unicode字符。您可以同样使用\u0057\u0057而不是//来开始评论。


这是IDE中的一个错误,它应该语法突出显示该行,以明确\u000d结束注释。


这也是语言中的设计错误。它现在无法纠正,因为这会破坏依赖它的程序。\u转义应该由编译器转换为相应的Unicode字符,仅在有意义的上下文中(字符串文字和标识符)或者可能在其他任何地方)或者他们应该被禁止在U + 0000-007F范围内生成字符,或者两者兼而有之。这些语义中的任何一个都会阻止评论被\u000d转义终止,而不会干扰对于\u转义是有用的情况 - 注意包含使用\u转义注释作为在非拉丁文脚本中编码注释的方法,因为文本编辑器可以更广泛地了解\u转义的重要性,而不是编译器。(我不知道任何编辑器或IDE将显示\u转义为 any <中的相应字符/em>上下文,但。)


在C系列中存在类似的设计错误, 1 ,其中在确定注释边界之前处理反斜杠换行符,例如,


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


我提出这个问题来说明这个特定的设计错误很容易发生,并且如果你习惯于考虑标记化和解析编译器的方式,那么直到它为时已晚才能解决这个问题并不会意外。程序员考虑标记化和解析。基本上,如果你已经定义了你的形式语法,然后有人提出了一个语法特殊情况— trigraphs,反斜杠换行,在源文件中编码任意Unicode字符,限制为ASCII,无论如何— that需要被嵌入,在标记器之前添加转换传递比重新定义标记生成器以注意使用该特殊情况有意义的地方更容易。


1 对于学生:我知道C的这个方面是100%故意的,理由—我不是这样做的 - mdash;它可以让你用任意长的线将代码机械地压入到打孔卡上。这仍然是一个不正确的设计决定。

其它参考4


这是一个有意的设计选择,一直追溯到Java的原始设计。


对于那些问谁想要在评论中逃脱Unicode?的人,我认为他们是那些母语使用拉丁字符集的人。换句话说,在Java的原始设计中,人们可以在Java程序中的任何合法地方使用任意Unicode字符,最常见的是在注释和字符串中。


可以说,用于查看源文本的程序(如IDE)的缺点是这些程序无法解释Unicode转义并显示相应的字形。

其它参考5


我同意@zwol这是一个设计错误;但我更加批评它。


\u转义在字符串和字符文字中很有用;并且它是唯一应该存在的地方。它应该像\n之类的其他转义一样处理;而"\u000A" 应该正好"\n"


在评论中\uxxxx绝对没有意义 - 没有人可以阅读。


同样地,在程序的其他部分中没有使用\uxxxx的唯一例外。唯一的例外是可能在强制包含一些非ascii字符的公共API中 - 我们最后一次看到的是什么那?


设计师在1995年有他们的理由,但20年后,这似乎是一个错误的选择。


(向读者提问 - 为什么这个问题不断获得新的投票?这个问题是否从流行的地方链接?)

其它参考6


唯一能够回答为什么Unicode转义被实现的人是编写规范的人。


一个似是而非的理由是,希望允许整个BMP成为Java源代码的可能字符。这提出了一个问题:



  • 您希望能够使用任何BMP角色。

  • 您希望能够相当容易地输入任何BMP字符。执行此操作的方法是使用Unicode转义。

  • 你想让词汇规范易于人类阅读和写作,并且相当容易实现。



当Unicode转义进入战斗时,这是非常困难的:它创建了一整套新的词法分析器规则。


简单的方法是分两步执行lexing:首先使用它所代表的字符搜索并替换所有Unicode转义符,然后解析生成的文档,就好像Unicode转义不存在一样。


这样做的好处在于它易于指定,因此它使规范更简单,并且易于实现。


不好的是,你的榜样。

其它参考7


编译器不仅会将Unicode转义转换为它们在将程序解析为令牌之前所代表的字符,但它会在丢弃注释和空格之前执行此操作。


该程序包含一个Unicode转义符(\\ u000d),位于其唯一注释中。正如评论告诉您的那样,此转义表示换行符,并且编译器在放弃注释之前正确地将其转换为。


这与平台有关。在某些平台上,例如UNIX,它可以工作;在其他方面,例如Windows,它不会。虽然肉眼可能看起来相同,但如果将其保存在文件中或通过管道传输到另一个程序进行后续处理,则很容易引起问题。