下面的代码产生输出“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规范允许这样做?


当前回答

我同意@zwol的观点,这是一个设计错误;但我对它更加挑剔。

\u转义在字符串和char字面值中很有用;这是它唯一应该存在的地方。它应该像其他转义一样处理,比如\n;“\u000A”的意思应该是“\n”。

在评论中有\uxxxx是绝对没有意义的——没有人能读懂。

类似地,在程序的其他部分使用\uxxxx也没有意义。唯一的例外可能是在强制包含一些非ascii字符的公共api中——我们上次看到这种情况是什么时候?

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

(问读者——为什么这个问题不断得到新的投票?这个问题是来自某个流行的地方吗?)

其他回答

\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%有意为之的,其基本原理(我不是瞎编的)是允许你机械地将任意长行代码强行装到穿孔卡片上。这仍然是一个不正确的设计决策。

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

它背后的想法是,它允许在不同字符编码之间无损地转换Java源代码。今天,Unicode得到了广泛的支持,这看起来不像是一个问题,但在当时,对于一个西方国家的开发人员来说,从他的亚洲同事那里收到一些包含亚洲字符的源代码,进行一些更改(包括编译和测试),并将结果发送回去,而不破坏任何东西是不容易的。

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

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

这就是为什么会出现另一个没有提到的奇怪特性:\uuuuuuxxxx语法:

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

我同意@zwol的观点,这是一个设计错误;但我对它更加挑剔。

\u转义在字符串和char字面值中很有用;这是它唯一应该存在的地方。它应该像其他转义一样处理,比如\n;“\u000A”的意思应该是“\n”。

在评论中有\uxxxx是绝对没有意义的——没有人能读懂。

类似地,在程序的其他部分使用\uxxxx也没有意义。唯一的例外可能是在强制包含一些非ascii字符的公共api中——我们上次看到这种情况是什么时候?

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

(问读者——为什么这个问题不断得到新的投票?这个问题是来自某个流行的地方吗?)

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

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

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

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

这样做的一个合理的原因是希望允许整个BMP作为Java源代码的可能字符。但这也带来了一个问题:

您希望能够使用任何BMP字符。 您希望能够相当容易地输入任何BMP字符。一种方法是使用Unicode转义。 您希望保持词汇规范易于人们阅读和编写,并且相当容易实现。

当Unicode escape进入争论时,这是非常困难的:它创建了一大堆新的词法分析器规则。

最简单的方法是分两步进行词法分析:首先搜索并将所有Unicode转义替换为它所代表的字符,然后解析结果文档,就好像Unicode转义不存在一样。

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

缺点是,你的例子。