为什么此后写的代码显示“ Hello World!”


261

这是我在Internet上找到的一些代码:

class M‮{public static void main(String[]a‭){System.out.print(new char[]
{'H','e','l','l','o',' ','W','o','r','l','d','!'});}}    

此代码会打印Hello World!到屏幕上;您可以看到它在这里运行。我可以清楚地看到public static void main书面内容,但这是倒退的。该代码如何工作?怎么编译?

编辑:我在IntellIJ中尝试过此代码,并且工作正常。但是,由于某些原因,它不能与cmd一起在notepad ++中工作。我仍然没有找到解决方案,因此,如果有人这样做,请在下面进行评论。


38
这很有趣...与RTL支持有关吗?
尤金(Eugene Sh)。

12
有Unicode字符#8237;就在之后M以及之后[]afileformat.info/info/unicode/char/202d/index.htm它被称为
从左到右覆盖

45
强制性xkcd:xkcd.com/1137
Pac0

4
通过使用鼠标在代码片段中进行选择,您可以非常容易地看到正在发生的事情。
Andreas Rejbrand '17

14
niam diov citats cilbup听起来像一个拉丁谚语..
米克助记符

Answers:


250

这里有一些不可见的字符,它们会改变代码的显示方式。在Intellij中,可以通过将代码复制粘贴到一个空字符串("")中来找到它们,该字符串将其替换为Unicode转义符,消除其影响并显示编译器看到的顺序。

这是该复制粘贴的输出:

"class M\u202E{public static void main(String[]a\u202D){System.out.print(new char[]\n"+
        "{'H','e','l','l','o',' ','W','o','r','l','d','!'});}}   "

源代码字符按此顺​​序存储,并且编译器将其视为按此顺序存储,但是它们的显示方式有所不同。

请注意,该\u202E字符是从右到左的替代,开始一个块,其中所有字符都被强制从右向左显示,而字符\u202D,这是一个从左到右的替代,开始一个嵌套块,其中所有字符被强制为从左到右的顺序,覆盖第一个替代。

Ergo在显示原始代码时class M会正常显示,但是将\u202E所有内容的显示顺序从那里\u202D颠倒到,从而再次颠倒所有内容。(通常,从\u202D到行终止符的所有内容都会反转两次,一次是由于的缘故,一次是由于\u202D其余文字的缘故\u202E是由于而反转的,这就是为什么此文本显示在行的中间而不是结尾的原因。)由于行终止符,下一行的方向性与第一行的方向无关,因此{'H','e','l','l','o',' ','W','o','r','l','d','!'});}}可以正常显示。

有关完整(极其复杂,长数十页)的Unicode双向算法,请参见Unicode标准附件#9


您无需解释编译器(与显示例程相对)如何处理这些Unicode字符本身。我可能会完全忽略它们(或将它们视为空白),或者可能会将它们解释为实际上对源代码有所贡献。我在这里不知道Java规则,但是将它们放在其他未使用的标识符的末尾这一事实向我表明,它可能是后者,而Unicode字符实际上是这些标识符名称的一部分。
Marc van Leeuwen

出于兴趣,这会在c#中以相同的方式工作吗?
IanF1

14
@ IanF1它可以在编译器/解释器将RTL和LTR字符视为空格的任何语言中工作。但是,如果您完全重视下一个要触摸您代码的人的理智,那就永远不要在生产代码中这样做,这很可能就是您。
wizzwizz4

2
或者换句话说:“总是进行编码,就像最终维护您的代码的人是知道您的住所的暴力心理变态者一样。” ,@ IanF1。或者也许是:“总是像最终维护您的代码的人那样编写代码,并且会把您当成Stack Overflow上的原始作者而羞辱您。”
科迪·格雷

43

由于Unicode双向算法,它看起来有所不同。Unicode双向算法使用RLO和LRO的两个不可见字符来更改嵌套在这两个元字符之间的字符的外观

结果是,它们在视觉上看起来是相反的顺序,但是内存中的实际字符不会被反转。您可以在此处分析结果。Java编译器将忽略RLO和LRO,并将它们视为空格,这就是代码进行编译的原因。

注1:文本编辑器和浏览器使用此算法在视觉上同时显示LTR字符(英语)和RTL字符(例如阿拉伯语,希伯来语)的字符-因此是“双向”的。您可以在Unicode 网站上阅读有关双向算法的更多信息。
注2:LRO和RLO的确切行为在算法的2.2节中定义。


这种能力的目的是什么?
尤金(Eugene Sh)。

6
有时需要这些字符才能视觉上正确呈现阿拉伯语和希伯来语。这些语言是从右到左(RTL)读写的,第一个被读写的字符出现在右侧。您可以在这里阅读更多内容。
詹姆斯·劳森

不过,阿拉伯和希伯来字符本质上是RTL-即使没有显式覆盖,它们也会显示RTL,并且它们甚至会自动反转附近其他某些字符的顺序,我认为主要是标点符号-因此,显式覆盖几乎没有必要。
user2357112支持Monica

此处的页面介绍了何时需要覆盖。@ user2357112是正确的,几乎不需要它们。确实,当您使用标点符号,引号和数字时-这些特殊字符被视为“中性”。对于无法读取单词并理解上下文的计算机,尚不清楚将其视为LTR还是RTL,但是bidi算法必须选择一些顺序。有时它“弄错了”,您需要使用这些替代字符来“更正”。
詹姆斯·劳森

3
同样,U + 202E和U + 202D不被视为空白。Java仅将ASCII空间,水平制表符,换页和CR / LF / CRLF视为空白。实际上,它们实际上是标识符M\u202E和的组成部分a\u202D,但这些标识符似乎被视为与M和等效a。(JLS并不能很好地解释这一点。)
user2357112支持Monica

28

字符U+202E从右到左镜像代码,但是它非常聪明。隐藏在M开头

"class M\u202E{..."

我是如何找到这背后的魔力的

好吧,一开始,当我看到一个棘手的问题时,“这是一个玩笑,要浪费别人的时间”,但是随后,我打开了我的IDE(“ IntelliJ”),创建了一个类,然后跳过了代码... 它编译!因此,我做了一个更好的观察,发现“公共静态空缺”是向后的,所以我带着光标去了那里,并删除了一些字符 ……然后会发生什么?字符开始向后擦除,因此,我认为mmm ....难得...我必须执行它...所以我继续执行程序,但是首先我需要保存它 ...那时是我找到了!。我无法保存文件,因为我的IDE表示某些字符的编码不同,并指出了它在哪里,于是我在Google中研究了可以完成这项工作的特殊字符,就是这样:)

关于一点

Unicode双向算法及其U+202E涉及的内容简要说明

Unicode标准规定了称为逻辑顺序的内存表示顺序。当文本以水平线显示时,大多数脚本从左到右显示字符。但是,有几种脚本(例如阿拉伯语或希伯来语)显示的水平文本的自然顺序是从右到左。如果所有文本的水平方向一致,则显示文本的顺序是明确的。

但是,由于这些从右到左的脚本使用的数字是从左到右书写的,因此文本实际上是双向的:从右到左和从左到右的文本的混合。除数字外,英语和其他脚本中的嵌入词也从左到右书写,也产生双向文本。如果没有明确的说明,当文本的水平方向不一致时,在确定显示字符的顺序时可能会产生歧义。

本附件描述了用于确定双向Unicode文本方向性的算法。该算法扩展了许多现有实现当前采用的隐式模型,并为特殊情况添加了显式格式字符。在大多数情况下,无需在文本中包含其他信息即可获得正确的显示顺序。

但是,在双向文本的情况下,在某些情况下,隐式双向排序不足以生成可理解的文本。为了应对这些情况,定义了最少的一组定向格式字符,以控制呈现时的字符顺序。这样可以精确控制显示顺序以实现清晰的互换,并确保用于简单项目(如文件名或标签)的纯文本始终可以正确排序以进行显示。

为什么要建立一些算法,像这样

bidi算法可以从右到左一个接一个地渲染阿拉伯或希伯来字符序列。


4

语言规范的第3章通过详细描述Java程序的词法转换方式来提供解释。最重要的问题是:

程序以Unicode(第3.1节)编写,但提供了词法翻译(第3.2节),因此Unicode转义符(第3.3节)可用于仅使用ASCII字符包括任何Unicode字符。

因此,程序是用Unicode字符编写的,\uxxxx如果文件编码不支持Unicode字符,则作者可以使用它们进行转义,在这种情况下,它会转换为适当的字符。在这种情况下,存在的Unicode字符之一是\u202E。片段中未直观显示该字符,但是如果您尝试切换浏览器的编码,则可能会出现隐藏的字符。

因此,词汇翻译会导致类声明:

class M\u202E{

这表示类别识别码为M\u202E。该规范认为这是有效的IDENTIFER:

Identifier:
    IdentifierChars but not a Keyword or BooleanLiteral or NullLiteral
IdentifierChars:
    JavaLetter {JavaLetterOrDigit}

“ Java字母或数字”是方法Character.isJavaIdentifierPart(int)返回true 的字符。


对不起,但这是向后的(双关语是故意的)。源代码中没有转义符。您正在描述它可能是如何编写的。并且,它将编译为名为“ M”的类(仅一个字符)。
汤姆·布洛杰特

@TomBlodget确实,但要点(实际上我在规范引用中强调了这一点)是,编译器还可以处理原始Unicode字符。这确实是整个解释。转义翻译只是附加信息,与本例没有直接关系。至于编译的类,我认为这是因为RTL开关字符以某种方式被编译器丢弃。我将尝试看看是否可以预期,但是我认为会在词汇翻译阶段之后发生。
M阿努蒂
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.