Java11中基于嵌套关系的访问控制优化详解
目录
前言
Java11 之前的实现方式
技术债务
Java11 中的实现
Nestmate 新增的 API
getNestHost
getNestMembers
isNestmateOf
后续的改进
文末总结
前言
Java 语言很强大,但是,有人的地方就有江湖,有猿的地方就有 bug,Java 的核心代码并非十全十美。比如在JDK 中居然也有反模式接口常量 中介绍的反模式实现,以及本文说到的这个技术债务:嵌套关系(NestMate)调用方式。
在 Java 语言中,类和接口可以相互嵌套,这种组合之间可以不受限制的彼此访问,包括访问彼此的构造函数、字段、方法等。即使是private私有的,也可以彼此访问。比如下面这样定义:
public class Outer {
private int i;
public void print1() {
print11();
print12();
}
private void print11() {
System.out.println(i);
}
private void print12() {
System.out.println(i);
}
public void callInnerMethod() {
final Inner inner = new Inner();
inner.print4();
inner.print5();
System.out.println(inner.j);
}
public class Inner {
private int j;
public void print3() {
System.out.println(i);
print1();
}
public void print4() {
System.out.println(i);
print11();
print12();
}
private void print5() {
System.out.println(i);
print11();
print12();
}
}
}
上例中,Outer类中的字段i、方法print11和print12都是私有的,但是可以在Inner类中直接访问,Inner类的字段j、方法print5是私有的,也可以在Outer类中使用。这种设计是为了更好的封装,在用户看来,这几个彼此嵌套的类/接口是一体的,分开定义是为了更好的封装自己,隔离不同特性,但是有因为彼此是一体,所以私有元素也应该是共有的。
Java11 之前的实现方式
我们使用 Java8 编译,然后借助javap -c命令分别查看Outer和Inner的结果。
$ javap -c Outer.class
Compiled from “Outer.java”
public class cn.howardliu.tutorials.java8.nest.Outer {
public cn.howardliu.tutorials.java8.nest.Outer();
Code:
0: aload_0
1: invokespecial #4 // Method java/lang/Object.”<init>“:()V
4: return
public void print1();
Code:
0: aload_0
1: invokespecial #2 // Method print11:()V
4: aload_0
5: invokespecial #1 // Method print12:()V
8: return
public void callInnerMethod();
Code:
0: new #7 // class cn/howardliu/tutorials/java8/nest/Outer$Inner
3: dup
4: aload_0
5: invokespecial #8 // Method cn/howardliu/tutorials/java8/nest/Outer$Inner.”<init>“:(Lcn/howardliu/tutorials/java8/nest/Outer;)V
8: astore_1
9: aload_1
10: invokevirtual #9 // Method cn/howardliu/tutorials/java8/nest/Outer$Inner.print4:()V
13: aload_1
14: invokestatic #10 // Method cn/howardliu/tutorials/java8/nest/Outer$Inner.access$000:(Lcn/howardliu/tutorials/java8/nest/Outer$Inner;)V
17: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
20: aload_1
21: invokestatic #11 // Method cn/howardliu/tutorials/java8/nest/Outer$Inner.access$100:(Lcn/howardliu/tutorials/java8/nest/Outer$Inner;)I
24: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
27: return
static int access$200(cn.howardliu.tutorials.java8.nest.Outer);
Code:
0: aload_0
1: getfield #3 // Field i:I
4: ireturn
static void access$300(cn.howardliu.tutorials.java8.nest.Outer);
Code:
0: aload_0
1: invokespecial #2 // Method print11:()V
4: return
static void access$400(cn.howardliu.tutorials.java8.nest.Outer);
Code:
0: aload_0
1: invokespecial #1 // Method print12:()V
4: return
}
再来看看Inner的编译结果,这里需要注意的是,内部类会使用特殊的命名方式定义Inner类,最终会将编译结果存储在两个文件中:
$ javap -c Outer\$Inner.class
Compiled from “Outer.java”
public class cn.howardliu.tutorials.java8.nest.Outer$Inner {
final cn.howardliu.tutorials.java8.nest.Outer this$0;
public cn.howardliu.tutorials.java8.nest.Outer$Inner(cn.howardliu.tutorials.java8.nest.Outer);
Code:
0: aload_0
1: aload_1
2: putfield #3 // Field this$0:Lcn/howardliu/tutorials/java8/nest/Outer;
5: aload_0
6: invokespecial #4 // Method java/lang/Object.”<init>“:()V
9: return
public void print3();
Code:
0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #3 // Field this$0:Lcn/howardliu/tutorials/java8/nest/Outer;
7: invokestatic #6 // Method cn/howardliu/tutorials/java8/nest/Outer.access$200:(Lcn/howardliu/tutorials/java8/nest/Outer;)I
10: invokevirtual #7 // Method java/io/PrintStream.println:(I)V
13: aload_0
14: getfield #3 // Field this$0:Lcn/howardliu/tutorials/java8/nest/Outer;
17: invokevirtual #8 // Method cn/howardliu/tutorials/java8/nest/Outer.print1:()V
20: return
public void print4();
Code:
0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #3 // Field this$0:Lcn/howardliu/tutorials/java8/nest/Outer;
7: invokestatic #6 // Method cn/howardliu/tutorials/java8/nest/Outer.access$200:(Lcn/howardliu/tutorials/java8/nest/Outer;)I
10: invokevirtual #7 // Method java/io/PrintStream.println:(I)V
13: aload_0
14: getfield #3 // Field this$0:Lcn/howardliu/tutorials/java8/nest/Outer;
17: invokestatic #9 // Method cn/howardliu/tutorials/java8/nest/Outer.access$300:(Lcn/howardliu/tutorials/java8/nest/Outer;)V
20: aload_0
21: getfield #3 // Field this$0:Lcn/howardliu/tutorials/java8/nest/Outer;
24: invokestatic #10 // Method cn/howardliu/tutorials/java8/nest/Outer.access$400:(Lcn/howardliu/tutorials/java8/nest/Outer;)V
27: return
static void access$000(cn.howardliu.tutorials.java8.nest.Outer$Inner);
Code:
0: aload_0
1: invokespecial #2 // Method print5:()V
4: return
static int access$100(cn.howardliu.tutorials.java8.nest.Outer$Inner);
Code:
0: aload_0
1: getfield #1 // Field j:I
4: ireturn
}
我们可以看到,Outer和Inner中多出了几个方法,方法名格式是access$*00。
Outer中的access$200方法返回了属性i,access$300和access$400分别调用了print11和print12方法。这些新增的方法都是静态方法,作用域是默认作用域,即包内可用。这些方法最终被Inner类中的print3和print4调用,相当于间接调用Outer中的私有属性或方法。
我们称这些生成的方法为“桥”方法(Bridge Method),是一种实现嵌套关系内部互相访问的方式。
在编译的时候,Java 为了保持类的单一特性,会将嵌套类编译到多个 class 文件中,同时为了保证嵌套类能够彼此访问,自动创建了调用私有方法的“桥”方法,这样,在保持原有定义不变的情况下,又实现了嵌套语法。
技术债务
“桥”方法的实现是比较巧妙的,但是这会造成源码与编译结果访问控制权限不一致,比如,我们可以在Inner中调用Outer中的私有方法,按照道理来说,我们可以在Inner中通过反射调用Outer的方法,但实际上不行,会抛出IllegalAccessException异常。我们验证一下:
public class Outer {
// 省略其他方法
public void callInnerReflectionMethod()
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
final Inner inner = new Inner();
inner.callOuterPrivateMethod(this);
}
public class Inner {
// 省略其他方法
public void callOuterPrivateMethod(Outer outer)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
final Method method = outer.getClass().getDeclaredMethod("print12");
method.invoke(outer);
}
}
}
定义测试用例:
@Test
void gotAnExceptionInJava8() {
final Outer outer = new Outer();
final Exception e = assertThrows(IllegalAccessException.class, outer::callInnerReflectionMethod);
e.printStackTrace();
assertDoesNotThrow(outer::callInnerMethod);
}
打印的异常信息是:
java.lang.IllegalAccessException: class cn.howardliu.tutorials.java8.nest.Outer$Inner cannot access a member of class cn.howardliu.tutorials.java8.nest.Outer with modifiers “private”
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:591)
at java.base/java.lang.reflect.Method.invoke(Method.java:558)
at cn.howardliu.tutorials.java8.nest.Outer$Inner.callOuterPrivateMethod(Outer.java:62)
at cn.howardliu.tutorials.java8.nest.Outer.callInnerReflectionMethod(Outer.java:36)
通过反射直接调用私有方法会失败,但是可以直接的或者通过反射访问这些“桥”方法,这样就比较奇怪了。所以提出 JEP181 改进,修复这个技术债务的同时,为后续的改进铺路。
Java11 中的实现
我们再来看看 Java11 编译之后的结果:
$ javap -c Outer.class
Compiled from “Outer.java”
public class cn.howardliu.tutorials.java11.nest.Outer {
public cn.howardliu.tutorials.java11.nest.Outer();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object.”<init>“:()V
4: return
public void print1();
Code:
0: aload_0
1: invokevirtual #2 // Method print11:()V
4: aload_0
5: invokevirtual #3 // Method print12:()V
8: return
public void callInnerMethod();
Code:
0: new #7 // class cn/howardliu/tutorials/java11/nest/Outer$Inner
3: dup
4: aload_0
5: invokespecial #8 // Method cn/howardliu/tutorials/java11/nest/Outer$Inner.”<init>“:(Lcn/howardliu/tutorials/java11/nest/Outer;)V
8: astore_1
9: aload_1
10: invokevirtual #9 // Method cn/howardliu/tutorials/java11/nest/Outer$Inner.print4:()V
13: aload_1
14: invokevirtual #10 // Method cn/howardliu/tutorials/java11/nest/Outer$Inner.print5:()V
17: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
20: aload_1
21: getfield #11 // Field cn/howardliu/tutorials/java11/nest/Outer$Inner.j:I
24: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
27: return
}
是不是很干净,与Outer类的源码结构是一致的。我们再看看Inner有没有什么变化:
$ javap -c Outer\$Inner.class
Compiled from “Outer.java”
public class cn.howardliu.tutorials.java11.nest.Outer$Inner {
final cn.howardliu.tutorials.java11.nest.Outer this$0;
public cn.howardliu.tutorials.java11.nest.Outer$Inner(cn.howardliu.tutorials.java11.nest.Outer);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:Lcn/howardliu/tutorials/java11/nest/Outer;
5: aload_0
6: invokespecial #2 // Method java/lang/Object.”<init>“:()V
9: return
public void print3();
Code:
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #1 // Field this$0:Lcn/howardliu/tutorials/java11/nest/Outer;
7: getfield #4 // Field cn/howardliu/tutorials/java11/nest/Outer.i:I
10: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
13: aload_0
14: getfield #1 // Field this$0:Lcn/howardliu/tutorials/java11/nest/Outer;
17: invokevirtual #6 // Method cn/howardliu/tutorials/java11/nest/Outer.print1:()V
20: return
public void print4();
Code:
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #1 // Field this$0:Lcn/howardliu/tutorials/java11/nest/Outer;
7: getfield #4 // Field cn/howardliu/tutorials/java11/nest/Outer.i:I
10: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
13: aload_0
14: getfield #1 // Field this$0:Lcn/howardliu/tutorials/java11/nest/Outer;
17: invokevirtual #7 // Method cn/howardliu/tutorials/java11/nest/Outer.print11:()V
20: aload_0
21: getfield #1 // Field this$0:Lcn/howardliu/tutorials/java11/nest/Outer;
24: invokevirtual #8 // Method cn/howardliu/tutorials/java11/nest/Outer.print12:()V
27: return
}
同样干净。
我们在通过测试用例验证一下反射调用:
@Test
void doesNotGotAnExceptionInJava11() {
final Outer outer = new Outer();
assertDoesNotThrow(outer::callInnerReflectionMethod);
assertDoesNotThrow(outer::callInnerMethod);
}
结果是正常运行。
这就是 JEP181 期望的结果,源码和编译结果一致,访问控制一致。
Nestmate 新增的 API
在 Java11 中还新增了几个 API,用于嵌套关系的验证:
getNestHost
这个方法是返回嵌套主机(NestHost),转成普通话就是找到嵌套类的外层类。对于非嵌套类,直接返回自身(其实也算是返回外层类)。
我们看下用法:
@Test
void checkNestHostName() {
final String outerNestHostName = Outer.class.getNestHost().getName();
assertEquals(“cn.howardliu.tutorials.java11.nest.Outer”, outerNestHostName);
final String innerNestHostName = Inner.class.getNestHost().getName();
assertEquals("cn.howardliu.tutorials.java11.nest.Outer", innerNestHostName);
assertEquals(outerNestHostName, innerNestHostName);
final String notNestClass = NotNestClass.class.getNestHost().getName();
assertEquals("cn.howardliu.tutorials.java11.nest.NotNestClass", notNestClass);
}
对于Outer和Inner都是返回了cn.howardliu.tutorials.java11.nest.Outer。
getNestMembers
这个方法是返回嵌套类的嵌套成员数组,下标是 0 的元素确定是 NestHost 对应的类,其他元素顺序没有给出排序规则。我们看下使用:
@Test
void getNestMembers() {
final List<String> outerNestMembers = Arrays.stream(Outer.class.getNestMembers())
.map(Class::getName)
.collect(Collectors.toList());
assertEquals(2, outerNestMembers.size());
assertTrue(outerNestMembers.contains("cn.howardliu.tutorials.java11.nest.Outer"));
assertTrue(outerNestMembers.contains("cn.howardliu.tutorials.java11.nest.Outer$Inner"));
final List<String> innerNestMembers = Arrays.stream(Inner.class.getNestMembers())
.map(Class::getName)
.collect(Collectors.toList());
assertEquals(2, innerNestMembers.size());
assertTrue(innerNestMembers.contains("cn.howardliu.tutorials.java11.nest.Outer"));
assertTrue(innerNestMembers.contains("cn.howardliu.tutorials.java11.nest.Outer$Inner"));
}
isNestmateOf
这个方法是用于判断两个类是否是彼此的 NestMate,彼此形成嵌套关系。判断依据还是嵌套主机,只要相同,两个就是 NestMate。我们看下使用:
@Test
void checkIsNestmateOf() {
assertTrue(Inner.class.isNestmateOf(Outer.class));
assertTrue(Outer.class.isNestmateOf(Inner.class));
}
后续的改进
嵌套关系是作为 Valhalla 项目的一部分,这个项目的主要目标之一是改进 JAVA 中的值类型和泛型。后续会有更多的改进:
在泛型特化(generic specialization)中,每个特化类型(specialized type)可被创建为泛型的一个 Nestmate。
支持对Unsafe.defineAnonymousClass() API 的安全替换,实现将新类创建为已有类的 Nestmate。
可能会影响“密封类”(sealed classes),仅允许 Nestmate 的子类作为密封类。
可能会影响私有嵌套类型。私有嵌套类型当前定义为包内可访问(package-access)。
文末总结
本文阐述了基于嵌套关系的访问控制优化,其中涉及NestMate、NestHost、NestMember等概念。这次优化是 Valhalla 项目中一部分,主要改进 Java 中的值类型和泛型等。
到此这篇关于Java11中基于嵌套关系的访问控制优化的文章就介绍到这了,更多相关Java11嵌套关系的访问控制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!
时间: 2022-01-06
Java JDK11基于嵌套的访问控制的实现
Java(和其他语言)通过内部类支持嵌套类.要使其正常工作,需要编译器执行一些技巧.这是一个例子: public class Outer { private int outerInt; class Inner { public void printOuterInt() { System.out.println(“Outer int = “ + outerInt); } } } 在执行编译之前,编译器会修改它以创建类似的东西: public class Outer { private i
java实现基于SMTP发送邮件的方法
本文实例讲述了java实现基于SMTP发送邮件的方法.分享给大家供大家参考.具体实现方法如下: import java.util.Date; import java.util.Properties; import javax.mail.Authenticator; import javax.mail.Message; import javax.mail.PasswordAuthentication; import javax.mail.Session; import javax.mail.Tra
详解Java 中的嵌套类与内部类
详解Java 中的嵌套类与内部类 在Java中,可以在一个类内部定义另一个类,这种类称为嵌套类(nested class).嵌套类有两种类型:静态嵌套类和非静态嵌套类.静态嵌套类较少使用,非静态嵌套类使用较多,也就是常说的内部类.其中内部类又分为三种类型: 1.在外部类中直接定义的内部类. 2.在函数中定义的内部类. 3.匿名内部类. 对于这几种类型的访问规则, 示例程序如下: package lxg; //定义外部类 public class OuterClass { //外部类静态成员变量
Java中基于maven实现zxing二维码功能
maven所需jar <dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>com.google.zxing</groupId> <artifac
Java Web基于Session的登录实现方法
本文实例讲述了Java Web基于Session的登录实现方法.分享给大家供大家参考,具体如下: package cn.com.login; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpSer
java实现基于SGIP协议开发联通短信的方法
本文实例讲述了java实现基于SGIP协议开发联通短信的方法.分享给大家供大家参考.具体如下: 近段时间,由于公司的业务需要,开发出了联通短信.此文章的编写也是根据网上的一些示例来完成的.闲话少说,下面来看代码:(运行此程序的时候需要导入华为的开发包,此包可以到网上下载) 下行: public class Mt { private static String SPNumber = “**“; //接入号码 private static String ChargeNumb
java开发中嵌套类的详解及实例
java开发中嵌套类的详解 在java语言规范里面,嵌套类(Nested Classes)定义是: A nested class is any class whose declaration occurs within the body of another class or interface. A top level class is a class that is not a nested class. 说的简单一点,就是定义在类里面的类.一般把定义内部类的外围类成为包装类(enclos
Java并发之嵌套管程锁死详解
·嵌套管程死锁是如何发生的 ·具体的嵌套管程死锁的例子 ·嵌套管程死锁 vs 死锁 嵌套管程锁死类似于死锁, 下面是一个嵌套管程锁死的场景: Thread 1 synchronizes on A Thread 1 synchronizes on B (while synchronized on A) Thread 1 decides to wait for a signal from another thread before continuing Thread 1 calls B.wait()
Java语言基于无向有权图实现克鲁斯卡尔算法代码示例
所谓有权图,就是图中的每一条边上都会有相应的一个或一组值.通常情况下,这个值只是一个数字 如:在交通运输网中,边上的权值可能表示的是路程,也可能表示的是运输费用(显然二者都是数字).不过,边上的权值也有可能是其它东西,比如说是一个字符串,甚至是一个更加复杂的数据包,里面集合了更多的数据 克鲁斯卡尔算法的核心思想是:在带权连通图中,不断地在边集合中找到最小的边,如果该边满足得到最小生成树的条件,就将其构造,直到最后得到一颗最小生成树. 克鲁斯卡尔算法的执行步骤: 第一步:在带权连通图中,将边的权值
JAVA实现基于皮尔逊相关系数的相似度详解
最近在看<集体智慧编程>,相比其他机器学习的书籍,这本书有许多案例,更贴近实际,而且也很适合我们这种准备学习machinelearning的小白. 这本书我觉得不足之处在于,里面没有对算法的公式作讲解,而是直接用代码去实现,所以给想具体了解该算法带来了不便,所以想写几篇文章来做具体的说明.以下是第一篇,对皮尔逊相关系数作讲解,并采用了自己比较熟悉的java语言做实现. 皮尔逊数学公式如下,来自*. 其中,E是数学期望,cov表示协方差,\sigma_X和\sigma_Y是标准差. 化简后
上一篇: 消息系统设计与实现 - 珩~