我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Java11中如何解决基于嵌套关系的访问控制优化问题

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Java11中如何解决基于嵌套关系的访问控制优化问题

这篇文章主要为大家展示了Java11中如何解决基于嵌套关系的访问控制优化问题,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带大家一起来研究并学习一下“Java11中如何解决基于嵌套关系的访问控制优化问题”这篇文章吧。

    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、方法print11print12都是私有的,但是可以在Inner类中直接访问,Inner类的字段j、方法print5是私有的,也可以在Outer类中使用。这种设计是为了更好的封装,在用户看来,这几个彼此嵌套的类/接口是一体的,分开定义是为了更好的封装自己,隔离不同特性,但是有因为彼此是一体,所以私有元素也应该是共有的。

    Java11 之前的实现方式

    我们使用 Java8 编译,然后借助javap -c命令分别查看OuterInner的结果。

    $ 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.classCompiled 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}

    我们可以看到,OuterInner中多出了几个方法,方法名格式是access$*00

    Outer中的access$200方法返回了属性iaccess$300access$400分别调用了print11print12方法。这些新增的方法都是静态方法,作用域是默认作用域,即包内可用。这些方法最终被Inner类中的print3print4调用,相当于间接调用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);        }    }}

    定义测试用例:

    @Testvoid 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.classCompiled 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}

    同样干净。

    我们在通过测试用例验证一下反射调用:

    @Testvoid doesNotGotAnExceptionInJava11() {    final Outer outer = new Outer();    assertDoesNotThrow(outer::callInnerReflectionMethod);    assertDoesNotThrow(outer::callInnerMethod);}

    结果是正常运行。

    这就是 JEP181 期望的结果,源码和编译结果一致,访问控制一致。

    Nestmate 新增的 API

    在 Java11 中还新增了几个 API,用于嵌套关系的验证:

    getNestHost

    这个方法是返回嵌套主机(NestHost),转成普通话就是找到嵌套类的外层类。对于非嵌套类,直接返回自身(其实也算是返回外层类)。

    我们看下用法:

    @Testvoid 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);}

    对于OuterInner都是返回了cn.howardliu.tutorials.java11.nest.Outer

    getNestMembers

    这个方法是返回嵌套类的嵌套成员数组,下标是 0 的元素确定是 NestHost 对应的类,其他元素顺序没有给出排序规则。我们看下使用:

    @Testvoid 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。我们看下使用:

    @Testvoid 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)。 

    Java有哪些集合类

    Java中的集合主要分为四类:1、List列表:有序的,可重复的;2、Queue队列:有序,可重复的;3、Set集合:不可重复;4、Map映射:无序,键唯一,值不唯一。

    以上就是关于“Java11中如何解决基于嵌套关系的访问控制优化问题”的内容,如果该文章对您有所帮助并觉得写得不错,劳请分享给您的好友一起学习新知识,若想了解更多相关知识内容,请多多关注编程网行业资讯频道。

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    Java11中如何解决基于嵌套关系的访问控制优化问题

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档

    猜你喜欢

    Java11中如何解决基于嵌套关系的访问控制优化问题

    这篇文章主要为大家展示了Java11中如何解决基于嵌套关系的访问控制优化问题,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带大家一起来研究并学习一下“Java11中如何解决基于嵌套关系的访问控制优化问题”这篇文章吧。Java
    2023-06-26

    如何解决Linux系统中关于KVM虚拟机迁移出现的问题

    这篇文章主要介绍“如何解决Linux系统中关于KVM虚拟机迁移出现的问题”,在日常操作中,相信很多人在如何解决Linux系统中关于KVM虚拟机迁移出现的问题问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何解
    2023-06-12

    编程热搜

    • Python 学习之路 - Python
      一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
      Python 学习之路 - Python
    • chatgpt的中文全称是什么
      chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
      chatgpt的中文全称是什么
    • C/C++中extern函数使用详解
    • C/C++可变参数的使用
      可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
      C/C++可变参数的使用
    • css样式文件该放在哪里
    • php中数组下标必须是连续的吗
    • Python 3 教程
      Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
      Python 3 教程
    • Python pip包管理
      一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
      Python pip包管理
    • ubuntu如何重新编译内核
    • 改善Java代码之慎用java动态编译

    目录