以下是我在互联网上找到的一些代码:
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!
打印到屏幕上;你可以看到它运行here。我可以清楚地看到public static void main
写的,但它是倒退的。这段代码是如何工作的?这甚至如何编译?
编辑:我在IntellIJ中尝试了这个代码,它运行正常。但是,由于某种原因,它在记事本++和cmd中都不起作用。我仍然没有找到解决方案,所以如果有人这样做,请在下面评论。
答案 0 :(得分:244)
这里有隐形字符可以改变代码的显示方式。在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
,这是一个从左到右覆盖,启动一个嵌套块,其中所有字符都被强制为从左到右的顺序,覆盖第一个覆盖。
因为,当它显示原始代码时,class M
会正常显示,但\u202E
会反转从那里到\u202D
的所有内容的显示顺序,这会再次反转所有内容。 (正式地,从\u202D
到行终止符的所有内容都会被反转两次,一次是由\u202D
引起的,一次是由于\u202E
而文本的其余部分被反转,这就是为什么文本显示在行的中间而不是结尾。)由于行终止符,下一行的方向性独立于第一行处理,因此{'H','e','l','l','o',' ','W','o','r','l','d','!'});}}
正常显示。
对于完整(极其复杂,数十页长)的Unicode双向算法,请参阅Unicode Standard Annex #9。
答案 1 :(得分:43)
由于Unicode Bidirectional Algorithm,它看起来有所不同。 Unicode双向算法使用两个不可见的RLO和LRO字符来更改嵌套在这两个元字符之间的字符的视觉外观。
结果是在视觉上它们以相反的顺序查看,但内存中的实际字符不会反转。您可以分析结果here。 Java编译器将忽略RLO和LRO,并将它们视为空格,这就是代码编译的原因。
注1:文本编辑器和浏览器使用该算法可视地显示LTR字符(英语)和RTL字符(例如,
阿拉伯语,希伯来语)同时在一起 - 因此“双向” - 双向。您可以阅读有关双向算法的更多信息
在Unicode的website。
注2:LRO和RLO的确切行为在Section 2.2中定义
算法。
答案 2 :(得分:27)
角色U+202E
从右到左镜像代码,但它非常聪明。从M开始隐藏,
"class M\u202E{..."
我是如何在此背后找到 magic 的?
嗯,刚开始的时候,我看到了一个很难的问题,"这是一种玩笑,失去了别人的时间"但是,然后,我打开了我的IDE(" IntelliJ& #34;),创建一个类,并通过代码... 并编译 !!! 所以,我看了一眼,看到了" public static void"是向后的,所以我带着光标去了那里,并擦掉几个字符 ...... 会发生什么? 字符开始向后删除,所以,我认为嗯......很少......我必须执行它...所以我继续执行程序,但首先我需要来保存它 ......当我找到它时!。我无法保存文件,因为我的IDE说某些字符有不同的编码,并指出我在哪里,所以我开始在谷歌研究可以做的特殊字符这份工作,就是这样:)
一点关于
Unicode双向算法,U+202E
涉及,简要explain:
Unicode标准规定了称为逻辑顺序的内存表示顺序。当文本以水平线显示时,大多数脚本从左到右显示字符。但是,有几个脚本(如阿拉伯语或希伯来语),其中显示的水平文本的自然顺序是从右到左。如果所有文本都具有统一的水平方向,则显示文本的排序是明确的。
但是,因为这些从右到左的脚本使用从左到右书写的数字,所以文本实际上是双向的:从右到左和从左到右文本的混合。除了数字之外,英语和其他脚本中的嵌入词也从左到右书写,也产生双向文本。如果没有明确的规范,当文本的水平方向不均匀时,在确定显示字符的排序时会出现歧义。
本附件描述了用于确定双向Unicode文本的方向性的算法。该算法扩展了许多现有实现当前使用的隐式模型,并为特殊情况添加了显式格式化字符。在大多数情况下,不需要在文本中包含其他信息以获得正确的显示顺序。
但是,在双向文本的情况下,存在隐式双向排序不足以产生可理解文本的情况。为了处理这些情况,定义了一组最小的方向格式化字符来控制渲染时字符的排序。这样可以精确控制清晰易读的显示顺序,并确保用于文件名或标签等简单项目的纯文本始终可以正确排序显示。
为什么要创建像this这样的算法?
bidi算法可以呈现阿拉伯语或希伯来语的序列 从右到左一个接一个的字符。
P.S。:我知道这不是最好的答案,但首先解决问题很有趣:P
答案 3 :(得分:4)
Chapter 3 of the language specification通过详细描述如何为Java程序完成词法翻译来提供解释。这个问题最重要的是:
程序以Unicode(§3.1)编写,但提供了词法翻译(§3.2),因此Unicode转义(§3.3)可用于包含仅使用ASCII字符的任何Unicode字符。
因此,程序是用Unicode字符编写的,如果文件编码不支持Unicode字符,作者可以使用\uxxxx
来转义它们,在这种情况下,它会被转换为适当的字符。在这种情况下,其中一个Unicode字符是\u202E
。它在代码段中没有直观显示,但如果您尝试切换浏览器的编码,则可能会显示隐藏的字符。
因此,词汇翻译会产生类声明:
class M\u202E{
表示类标识符为M\u202E
。 specification认为这是一个有效的标识符:
Identifier:
IdentifierChars but not a Keyword or BooleanLiteral or NullLiteral
IdentifierChars:
JavaLetter {JavaLetterOrDigit}
“Java letter-or-digit”是方法
Character.isJavaIdentifierPart(int)
返回true的字符。
答案 4 :(得分:0)
这实际上是因为Unicode双向支持。
U + 202E有权超越
U + 202D从左到右
因此,这些字符有些棘手。实际上,它们是为从右到左语言支持而定义的。真正的代码是
class M<U+202E>{public static void main(String[]a<U+202D>){System.out.print(new char[]
{'H','e','l','l','o',' ','W','o','r','l','d','!'});}}
(通过粘贴到cmd.exe来获得此权限)。希望这个答案可以帮助您了解其工作原理。