点击蓝字
关注我们
嵌套类和私有方法
当你在另一个类中有一个类时,他们可以看到对方private方法。然而,这个事实在Java开发人员中并不为人所知。很多应聘者在面试时都会说private是一个可见性,它让代码看到一个成员,就好像它在同一个类中一样。这实际上是正确的,但是更准确地说,有一个代码和成员都在其中的类。当我们有嵌套类和内部类时,private成员和使用它的代码可以在同一个类中,同时,它们也在不同的类中。
例如,如果在顶级类中有两个嵌套类,那么其中一个嵌套类中的代码可以看到private另一个嵌套类的成员。
当我们查看生成的代码时,它开始变得有趣了。JVM不关心其他类中的类。它处理JVM“顶级”类。编译器将创建.class具有名称的文件,如A$B.class,当您有一个名为B在类内A。有一个private方法B可从A,然后jvm看到A.class并调用A$B.class。JVM检查访问控制。当我们与初级讨论这个问题时,有人建议JVM不关心修饰符。那不是真的。试编译A.java和B.java中包含一些代码的两个顶级类。A叫.public方法B。当你有A.class和B.class修改B.java不存在public成为private和重新编译B新的B.class。启动应用程序后,您将看到JVM非常关心访问修饰符。仍然可以在上面的示例中从A.class方法中的A$B.class.
为了解决这个冲突,Java生成额外的合成方法,这些方法本质上是公共的,在同一个类中调用原始的私有方法,并且就JVM访问控制而言是可调用的。另一方面,如果计算出生成方法的名称并试图直接从Java源代码调用,Java编译器将不会编译代码。我写过这,这个更详细的是四年多前。
如果您是一个经验丰富的开发人员,那么您可能认为这是一个奇怪和令人厌恶的黑客。除了这次黑客攻击之外,Java是如此干净、优雅、简洁和纯净。也许还有Integer缓存变小Integer对象典型的测试值)等于==,而更大的值只是equals)但不是==典型的生产值)。但是,除了合成类和IntegerCache Hack,Java是干净、优雅、简洁和纯净的您可能会意识到我是MontyPython的粉丝)。
这样做的原因是嵌套类不是原始Java的一部分;它只是添加到版本1.1中。解决方案是一次黑客攻击,但当时还有更重要的事情要做,比如介绍JIT编译器、JDBC、RMI、反射以及其他一些我们现在认为是理所当然的事情。那个时候,问题不是解决方案是否好和干净,而是,问题是Java是否能生存下来,以及它是否会成为主流编程语言。在此期间,我仍然是一名销售代表,编码只是一种爱好,因为在东欧,编码工作仍然很少。
20年后,我们的JAR文件稍大,Java执行速度稍慢除非JIT优化了调用链),以及IDE中令人讨厌的警告,这表明我们最好在嵌套类中使用包保护方法private当我们从顶层或其他嵌套类中使用它时。
巢宿主
现在看来,这20年的技术债务将得到解决。这,这个,那,那个http://openjdk.java.net/jeps/181进入Java 11,它将通过引入一个新的概念来解决这个问题:Nest。目前,Java字节码包含一些关于类之间关系的信息。JVM拥有的信息是某个类是另一个类的嵌套类,这不仅仅是名称。这个信息可以让jvm决定是否允许一个类中的一段代码访问private另一个类的成员,但是jep 181的开发有一些更一般的东西。随着时代的发展,JVM不再是Java虚拟机了。嗯,是的,至少是名字。但是,它是一个虚拟机,碰巧执行从Java或其他语言编译的字节码。有许多语言以JVM为目标,记住,Jep-181不希望将JVM的新访问控制特性与Java语言的特定特性联系起来。
jep-181定义了NestHost和NestMembers作为类的属性。编译器填充这些字段,当可以从不同的类访问类的私有成员时,JVM访问控制可以检查:这两个类是否在同一个Nest中?如果它们在同一个巢穴中,则允许访问。否则就不是了。我们将方法添加到反射访问中,这样我们就可以获得嵌套中的类的列表。
简单巢例
使用以下方法:
我们可以创建一个简单的类:
很简单,却什么也没做。私人方法互相调用。没有这一点,编译器就会发现它们根本就什么都不做,也不需要它们。因此,字节码不包含它们。
下面的类读取嵌套信息:
打印结果与预期相符:
注意,嵌套主机也列在Nest成员中,尽管这些信息应该是相当明显和多余的。然而,这种使用可能允许某些语言从嵌套主机本身的访问权限和私有成员中公开,只允许雏鸟访问。
字节码
使用JDK 11编译器编译生成以下文件。
没有变化。另一方面,如果我们使用javap然后,我们将看到以下内容:
如果我们使用JDK 10编译器编译同一个类,那么反汇编行如下:
Java 10编译器生成access$100方法。Java 11编译器没有。相反,它在类文件中有一个嵌套主机字段。我们最终摆脱了那些在某些框架代码反射中列出所有方法时引起意外的合成方法。
黑巢
让我们玩一会儿布谷鸟。我们可以稍微修改代码,以便它现在可以执行如下操作:
我们还可以创建一个简单的测试类:
首先,删除所有//从行开始并编译项目。它就像一个符咒和打印出来的。hallo。在这个副本之后,生成的类被转移到一个安全的地方,比如项目的根。
让我们编译这个项目。这一次,让我们用注释来完成它,在这个副本之后,返回上一次编译中的两个类文件:
现在,我们有一个NestingHost知道它只有一个雏鸟:NestedClass2。然而,测试代码认为还有另一个嵌套。NestedClass1,它还有一个可以调用的公共方法。这样的话,我们就会偷偷地把一个额外的窝藏进巢里。如果执行代码,则会得到一个错误:
重要的是要从代码中认识到,导致错误的行是我们希望调用私有方法的行。Java运行时只在这一点上进行检查,而不是更快。
我们喜欢还是不喜欢?抗故障原则在哪里?为什么Java运行时只在非常需要时才开始执行类并检查Nest结构?原因,在Java的情况下,很多次,向后兼容。JVM可以在加载所有类时检查嵌套结构的一致性。只有在使用类时才加载这些类。可以更改Java 11中的类加载,并与嵌套主机一起加载所有嵌套类,但这会破坏向后兼容性。如果没有其他的话,懒惰的单身模式就会分裂,我们不想这样。
结语
jep-181是Java中的一个小变化。大多数开发人员甚至不会注意到。这是一个消除了技术债务的问题,如果核心Java项目没有消除技术债务,那么我们应该从普通的开发人员那里得到什么呢?
正如一句古老的拉丁语所说:“DebitumTechnica必须删除。”
长按左边二维码
○
关注Java高级部落
更多精彩内容,尽在阅读原文