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

你好,我是看山。
Java 語言很強(qiáng)大,但是,有人的地方就有江湖,有猿的地方就有 bug,Java 的核心代碼并非十全十美。比如在 JDK 中居然也有反模式接口常量 中介紹的反模式實(shí)現(xiàn),以及本文說到的這個(gè)技術(shù)債務(wù):嵌套關(guān)系(NestMate)調(diào)用方式。
在 Java 語言中,類和接口可以相互嵌套,這種組合之間可以不受限制的彼此訪問,包括訪問彼此的構(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、方法print11和print12都是私有的,但是可以在Inner類中直接訪問,Inner類的字段j、方法print5是私有的,也可以在Outer類中使用。這種設(shè)計(jì)是為了更好的封裝,在用戶看來,這幾個(gè)彼此嵌套的類/接口是一體的,分開定義是為了更好的封裝自己,隔離不同特性,但是有因?yàn)楸舜耸且惑w,所以私有元素也應(yīng)該是共有的。
Java11 之前的實(shí)現(xiàn)方式
我們使用 Java8 編譯,然后借助javap -c命令分別查看Outer和Inner的結(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
}我們可以看到,Outer和Inner中多出了幾個(gè)方法,方法名格式是access$*00。
Outer中的access$200方法返回了屬性i,access$300和access$400分別調(diào)用了print11和print12方法。這些新增的方法都是靜態(tài)方法,作用域是默認(rèn)作用域,即包內(nèi)可用。這些方法最終被Inner類中的print3和print4調(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)了嵌套語法。
技術(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ì)于Outer和Inner都是返回了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)。
到此這篇關(guān)于Java11 中基于嵌套關(guān)系的訪問控制優(yōu)化的文章就介紹到這了,更多相關(guān)Java訪問控制優(yōu)化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis教程之增刪改查_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了mybatis教程之增刪改查,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09
在Java中實(shí)現(xiàn)可見性(visibility)的主要方法詳解
這篇文章主要介紹了在Java中實(shí)現(xiàn)可見性(visibility)的主要方法詳解,在Java中,使用關(guān)鍵字volatile和使用鎖(如synchronized關(guān)鍵字或 java.util.concurrent包中的鎖)來確保對(duì)共享變量的修改在多線程環(huán)境中能夠正確地被其他線程所觀察到,需要的朋友可以參考下2023-08-08
Java實(shí)現(xiàn)雪花算法(snowflake)
這篇文章主要介紹了Java實(shí)現(xiàn)雪花算法(snowflake),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
mybatis中insert主鍵ID獲取和多參數(shù)傳遞的示例代碼
這篇文章主要介紹了mybatis中insert主鍵ID獲取和多參數(shù)傳遞的示例代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
SpringBoot注冊(cè)第三方Bean的方法總結(jié)
眾所周知,SpringBoot默認(rèn)會(huì)掃描啟動(dòng)類所在的包及其子包,一般我們都是在需要的類上通過注解的方式去將Bean注冊(cè)交給IOC進(jìn)行管理,但是注冊(cè)第三方Bean的方案卻不支持,所以本文給大家介紹了SpringBoot注冊(cè)第三方Bean的方法,需要的朋友可以參考下2024-01-01
Java中策略設(shè)計(jì)模式的實(shí)現(xiàn)及應(yīng)用場(chǎng)景
策略設(shè)計(jì)模式是Java中一種常用的設(shè)計(jì)模式,它通過定義一系列算法并將其封裝成獨(dú)立的策略類,從而使得算法可以在不影響客戶端的情況下隨時(shí)切換。策略設(shè)計(jì)模式主要應(yīng)用于系統(tǒng)中存在多種相似的算法、需要靈活調(diào)整算法邏輯或者需要擴(kuò)展新的算法等場(chǎng)景2023-04-04
使用kotlin集成springboot開發(fā)的超詳細(xì)教程
目前大多數(shù)都在使用java集成 springboot進(jìn)行開發(fā),本文演示僅僅將 java換成 kotlin,其他不變的情況下進(jìn)行開發(fā),需要的朋友可以參考下2021-09-09
基于Springboot+Netty實(shí)現(xiàn)rpc的方法 附demo
這篇文章主要介紹了基于Springboot+Netty實(shí)現(xiàn)rpc功能,在父項(xiàng)目中引入相關(guān)依賴結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02

