
你好,我是看山,
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 中的值型別和泛型等,文中涉及原始碼都上傳在 GitHub 上,關注公號「看山的小屋」回復“java”獲取原始碼,
青山不改,綠水長流,咱們下次見,
推薦閱讀
- 一文掌握 Java8 Stream 中 Collectors 的 24 個操作
- 一文掌握 Java8 的 Optional 的 6 種操作
- 使用 Lambda 運算式實作超強的排序功能
- Java8 的時間庫(1):介紹 Java8 中的時間類及常用 API
- Java8 的時間庫(2):Date 與 LocalDate 或 LocalDateTime 互相轉換
- Java8 的時間庫(3):開始使用 Java8 中的時間類
- Java8 的時間庫(4):檢查日期字串是否合法
- Java8 的新特性
- Java9 的新特性
你好,我是看山,游于碼界,戲享人生,如果文章對您有幫助,請點贊、收藏、關注,我還整理了一些精品學習資料,關注公眾號「看山的小屋」,回復“資料”即可獲得,
個人主頁:https://www.howardliu.cn
個人博文:Java11 中基于嵌套關系的訪問控制優化
CSDN 主頁:https://kanshan.blog.csdn.net/
CSDN 博文:Java11 中基于嵌套關系的訪問控制優化
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/404341.html
標籤:java
