溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java11中如何解決基于嵌套關(guān)系的訪問控制優(yōu)化問題

發(fā)布時(shí)間:2022-01-20 09:15:46 來源:億速云 閱讀:159 作者:清風(fēng) 欄目:開發(fā)技術(shù)

這篇文章主要為大家展示了Java11中如何解決基于嵌套關(guān)系的訪問控制優(yōu)化問題,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶大家一起來研究并學(xué)習(xí)一下“Java11中如何解決基于嵌套關(guān)系的訪問控制優(yōu)化問題”這篇文章吧。

    Java 語(yǔ)言很強(qiáng)大,但是,有人的地方就有江湖,有猿的地方就有 bug,Java 的核心代碼并非十全十美。比如在 JDK 中居然也有反模式接口常量 中介紹的反模式實(shí)現(xiàn),以及本文說到的這個(gè)技術(shù)債務(wù):嵌套關(guān)系(NestMate)調(diào)用方式。

    在 Java 語(yǔ)言中,類和接口可以相互嵌套,這種組合之間可以不受限制的彼此訪問,包括訪問彼此的構(gòu)造函數(shù)、字段、方法等。即使是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類中使用。這種設(shè)計(jì)是為了更好的封裝,在用戶看來,這幾個(gè)彼此嵌套的類/接口是一體的,分開定義是為了更好的封裝自己,隔離不同特性,但是有因?yàn)楸舜耸且惑w,所以私有元素也應(yīng)該是共有的。

    Java11 之前的實(shí)現(xiàn)方式

    我們使用 Java8 編譯,然后借助javap -c命令分別查看OuterInner的結(jié)果。

    $ 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的編譯結(jié)果,這里需要注意的是,內(nèi)部類會(huì)使用特殊的命名方式定義Inner類,最終會(huì)將編譯結(jié)果存儲(chǔ)在兩個(gè)文件中:

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

    我們可以看到,OuterInner中多出了幾個(gè)方法,方法名格式是access$*00

    Outer中的access$200方法返回了屬性i,access$300access$400分別調(diào)用了print11print12方法。這些新增的方法都是靜態(tài)方法,作用域是默認(rèn)作用域,即包內(nèi)可用。這些方法最終被Inner類中的print3print4調(diào)用,相當(dāng)于間接調(diào)用Outer中的私有屬性或方法。

    我們稱這些生成的方法為“橋”方法(Bridge Method),是一種實(shí)現(xiàn)嵌套關(guān)系內(nèi)部互相訪問的方式。

    在編譯的時(shí)候,Java 為了保持類的單一特性,會(huì)將嵌套類編譯到多個(gè) class 文件中,同時(shí)為了保證嵌套類能夠彼此訪問,自動(dòng)創(chuàng)建了調(diào)用私有方法的“橋”方法,這樣,在保持原有定義不變的情況下,又實(shí)現(xiàn)了嵌套語(yǔ)法。

    技術(shù)債務(wù)

    “橋”方法的實(shí)現(xiàn)是比較巧妙的,但是這會(huì)造成源碼與編譯結(jié)果訪問控制權(quán)限不一致,比如,我們可以在Inner中調(diào)用Outer中的私有方法,按照道理來說,我們可以在Inner中通過反射調(diào)用Outer的方法,但實(shí)際上不行,會(huì)拋出IllegalAccessException異常。我們驗(yàn)證一下:

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

    定義測(cè)試用例:

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

    通過反射直接調(diào)用私有方法會(huì)失敗,但是可以直接的或者通過反射訪問這些“橋”方法,這樣就比較奇怪了。所以提出 JEP181 改進(jìn),修復(fù)這個(gè)技術(shù)債務(wù)的同時(shí),為后續(xù)的改進(jìn)鋪路。

    Java11 中的實(shí)現(xiàn)

    我們?cè)賮砜纯?Java11 編譯之后的結(jié)果:

    $ 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類的源碼結(jié)構(gòu)是一致的。我們?cè)倏纯?code>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
    }

    同樣干凈。

    我們?cè)谕ㄟ^測(cè)試用例驗(yàn)證一下反射調(diào)用:

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

    結(jié)果是正常運(yùn)行。

    這就是 JEP181 期望的結(jié)果,源碼和編譯結(jié)果一致,訪問控制一致。

    Nestmate 新增的 API

    在 Java11 中還新增了幾個(gè) API,用于嵌套關(guān)系的驗(yàn)證:

    getNestHost

    這個(gè)方法是返回嵌套主機(jī)(NestHost),轉(zhuǎn)成普通話就是找到嵌套類的外層類。對(duì)于非嵌套類,直接返回自身(其實(shí)也算是返回外層類)。

    我們看下用法:

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

    對(duì)于OuterInner都是返回了cn.howardliu.tutorials.java11.nest.Outer

    getNestMembers

    這個(gè)方法是返回嵌套類的嵌套成員數(shù)組,下標(biāo)是 0 的元素確定是 NestHost 對(duì)應(yīng)的類,其他元素順序沒有給出排序規(guī)則。我們看下使用:

    @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

    這個(gè)方法是用于判斷兩個(gè)類是否是彼此的 NestMate,彼此形成嵌套關(guān)系。判斷依據(jù)還是嵌套主機(jī),只要相同,兩個(gè)就是 NestMate。我們看下使用:

    @Test
    void checkIsNestmateOf() {
        assertTrue(Inner.class.isNestmateOf(Outer.class));
        assertTrue(Outer.class.isNestmateOf(Inner.class));
    }

    后續(xù)的改進(jìn)

    嵌套關(guān)系是作為 Valhalla 項(xiàng)目的一部分,這個(gè)項(xiàng)目的主要目標(biāo)之一是改進(jìn) JAVA 中的值類型和泛型。后續(xù)會(huì)有更多的改進(jìn):

    • 在泛型特化(generic specialization)中,每個(gè)特化類型(specialized type)可被創(chuàng)建為泛型的一個(gè) Nestmate。

    • 支持對(duì)Unsafe.defineAnonymousClass() API 的安全替換,實(shí)現(xiàn)將新類創(chuàng)建為已有類的 Nestmate。

    • 可能會(huì)影響“密封類”(sealed classes),僅允許 Nestmate 的子類作為密封類。

    • 可能會(huì)影響私有嵌套類型。私有嵌套類型當(dāng)前定義為包內(nèi)可訪問(package-access)。 

    Java有哪些集合類

    Java中的集合主要分為四類:1、List列表:有序的,可重復(fù)的;2、Queue隊(duì)列:有序,可重復(fù)的;3、Set集合:不可重復(fù);4、Map映射:無序,鍵唯一,值不唯一。

    以上就是關(guān)于“Java11中如何解決基于嵌套關(guān)系的訪問控制優(yōu)化問題”的內(nèi)容,如果該文章對(duì)您有所幫助并覺得寫得不錯(cuò),勞請(qǐng)分享給您的好友一起學(xué)習(xí)新知識(shí),若想了解更多相關(guān)知識(shí)內(nèi)容,請(qǐng)多多關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI