以下代码生成输出“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!");
}
从而导致评论被“执行” .
既然这可以用于"hide"恶意代码或任何邪恶的程序员可以设想的, why is it allowed in comments ?
为什么Java规范允许这样做?
8 回答
Unicode解码在任何其他词汇翻译之前进行 . 这样做的主要好处是可以在ASCII和任何其他编码之间来回切换 . 你甚至不需要弄清楚评论的开始和结束位置!
如JLS Section 3.3中所述,这允许任何基于ASCII的工具处理源文件:
这为平台独立性(支持的字符集的独立性)提供了基本保证,这一直是Java平台的关键目标 .
能够在文件中的任何位置编写任何Unicode字符是一个简洁的功能,在使用非拉丁语言记录代码时,在评论中尤其重要 . 它可以以这种微妙的方式干扰语义这一事实只是(不幸的)副作用 .
关于这个主题有许多问题,Joshua Bloch和Neal Gafter的Java Puzzlers包括以下变体:
(这个程序原来是一个简单的“Hello World”程序 . )
在解决益智游戏的过程中,他们指出了以下内容:
资料来源:Java: Executing code in comments?!
这是一个有意的设计选择,一直回到Java的原始设计 .
对于那些问“谁希望Unicode在评论中逃脱?”的人,我认为他们是那些母语使用拉丁字符集的人 . 换句话说,Java的原始设计中固有的,人们可以在Java程序中的任何合法地方使用任意Unicode字符,最常见的是在注释和字符串中 .
可以说,用于查看源文本的程序(如IDE)的缺点是这些程序无法解释Unicode转义并显示相应的字形 .
我将完全无效地添加这一点,仅仅是因为我无法帮助自己,我还没有看到它,但问题是无效的,因为它包含一个错误的隐藏前提,即代码在一条评论!
在Java源代码中,\ u000d在各方面都与ASCII CR字符等效 . 无论它出现在哪里,它都是一个简单明了的行 . 问题中的格式是误导性的,字符序列实际上在语法上对应的是:
恕我直言,最正确的答案是:代码执行,因为它不在评论中;它在下一行 . Java中不允许“在注释中执行代码”,就像您期望的那样 .
大部分混淆源于这样的事实:语法高亮显示器和IDE根本不处理unicode转义,或者它们在解析代码之后而不是之前执行它,就像
javac
那样 .由于尚未解决,这里有一个解释,为什么Unicode转义的转换发生在任何其他源代码处理之前:
其背后的想法是它允许在不同的字符编码之间无损翻译Java源代码 . 今天,有广泛的Unicode支持,这看起来不是一个问题,但是当时西方国家的开发人员从包含亚洲字符的亚洲同事那里收到一些源代码并不容易做出一些改变(包括编译和测试它并将结果发回,所有这些都不会损坏 .
因此,Java源代码可以用任何编码编写,并允许广泛的字符在标识符,字符和
String
文字和注释中 . 然后,为了无损地传输它,目标编码不支持的所有字符都被它们的Unicode转义替换 .这是一个可逆的过程,有趣的是,转换可以通过一个工具完成,该工具不需要了解Java源代码语法的任何信息,因为转换规则不依赖于它 . 这适用于编译器内部实际Unicode字符的转换也独立于Java源代码语法 . 这意味着您可以在两个方向上执行任意数量的转换步骤,而无需更改源代码的含义 .
这就是另一个奇怪的功能,甚至没有提到的原因:
\uuuuuuxxxx
语法:当翻译工具转义字符并遇到已经是转义序列的序列时,它应该在序列中插入一个额外的
u
,将\ucafe
转换为\uucafe
. 意思不会改变,但是当转换到另一个方向时,该工具应该只删除一个u
并仅用它们的Unicode字符替换包含单个u
的序列 . 这样,即使Unicode转义在来回转换时也会以原始形式保留 . 我猜,没有人使用过这个功能......\u000d
转义终止注释,因为在程序被标记化之前,\u
转义被统一转换为相应的Unicode字符 . 您可以同样使用\u0057\u0057
而不是//
来开始评论 .这是IDE中的一个错误,它应该语法突出显示该行,以明确
\u000d
结束注释 .这也是语言中的设计错误 . 它现在无法纠正,因为这会破坏依赖它的程序 . 只有在"makes sense"(字符串文字和标识符,可能没有其他地方)或者它们应该被禁止生成U 0000-007F范围内的字符的上下文中,编译器才能将
\u
转义转换为相应的Unicode字符,或者两者都是 . 这些语义中的任何一个都会阻止注释被\u000d
转义终止,而不会干扰\u
转义有用的情况 - 请注意,这包括在注释中使用\u
转义作为在非拉丁文脚本中编码注释的方法,因为文本编辑器可以更广泛地了解\u
转义的重要性,而不是编译器 . (我不知道任何编辑器或IDE会在任何上下文中显示\u
转义为相应的字符 . )C系列中存在类似的设计错误,1在确定注释边界之前处理反斜杠换行符,例如,
我提出这个来说明这个特定的设计错误很容易发生,并且没有意识到它在标记器之前添加转换传递比重新定义标记器以注意它有意义的地方更容易 . 使用那个特例 .
1对于学龄儿童:我知道C的这个方面是100%有意的,理由是 - 我不是这样做的 - 它可以让你用任意长线机械强制编码代码到打孔卡上 . 这仍然是一个不正确的设计决定 .
我同意@zwol这是一个设计错误;但我更加批评它 .
\u
escape在字符串和字符文字中很有用;这是它应该存在的唯一地方 . 它应该像其他转义一样处理,如\n
;和"\u000A"
应该完全是"\n"
.在评论中完全没有意义
\uxxxx
- 没有人可以阅读 .同样,在程序的其他部分使用
\uxxxx
也没有意义 . 唯一的例外可能是公共API被强制包含一些非ascii字符 - _112242已经看到了什么?设计师在1995年有他们的理由,但20年后,这似乎是一个错误的选择 .
(向读者提问 - 为什么这个问题不断获得新的选票?这个问题是否从流行的地方联系起来?)
唯一能够回答为什么Unicode转义被实现的人是编写规范的人 .
一个似是而非的理由是,希望允许整个BMP成为Java源代码的可能字符 . 这提出了一个问题:
您希望能够使用任何BMP字符 .
您希望能够相当容易地输入任何BMP字符 . 一种方法是使用Unicode转义 .
您希望保持词汇规范易于人类阅读和编写,并且相当容易实现 .
当Unicode转义进入战斗时,这是非常困难的:它创建了一整套新的词法分析器规则 .
最简单的方法是分两步执行lexing:首先搜索和用它表示的字符替换所有Unicode转义符,然后解析生成的文档,就好像Unicode转义不存在一样 .
这样做的好处在于它易于指定,因此它使规范更简单,并且易于实现 .
不好的是,你的榜样 .
编译器不仅会将Unicode转义转换为它们在将程序解析为令牌之前所代表的字符,但它会在丢弃注释和空格之前执行此操作 .
该程序包含一个Unicode转义符(\ u000d),位于其唯一注释中 . 正如评论告诉你的那样,这个转义表示换行字符,编译器在丢弃注释之前适当地转换它 .
这与平台有关 . 在某些平台上,例如UNIX,它可以工作;在其他方面,例如Windows,它不会 . 虽然肉眼可能看起来相同,但如果将其保存在文件中或通过管道传输到另一个程序进行后续处理,则很容易引起问题 .