“This is the 21st day of my participation in the First Challenge 2022. For details: First Challenge 2022”
Hello, I’m looking at the mountains.
Java language is very powerful, but, where there are people, there are rivers and lakes, where there are apes, there are bugs, Java core code is not perfect. For example, there are anti-pattern implementations in the JDK as described in anti-pattern Interface Constants, as well as the technical liability discussed in this article: NestMate calls.
In the Java language, classes and interfaces can be nested within each other, allowing unrestricted access to each other’s constructors, fields, methods, and so on. Even if they are private, they can access each other. For example, the following definition:
public class Outer {
private int i;
public void print1(a) {
print11();
print12();
}
private void print11(a) {
System.out.println(i);
}
private void print12(a) {
System.out.println(i);
}
public void callInnerMethod(a) {
final Inner inner = new Inner();
inner.print4();
inner.print5();
System.out.println(inner.j);
}
public class Inner {
private int j;
public void print3(a) {
System.out.println(i);
print1();
}
public void print4(a) {
System.out.println(i);
print11();
print12();
}
private void print5(a) { System.out.println(i); print11(); print12(); }}}Copy the code
In the example above, the field I in Outer and the methods print11 and print12 are private, but can be accessed directly from Inner. The field J in Inner and the method print5 are private and can also be used in Outer. This design is for better encapsulation. From the user’s point of view, these nested classes/interfaces are one and the same, and they are defined separately to better encapsulate themselves and isolate different features, but since they are one and the same, private elements should also be common.
Java11 prior to implementation
We compiled using Java8 and then looked at the results of Outer and Inner respectively with the javap -c command.
$ 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(a);
Code:
0: aload_0
1: invokespecial #2 // Method print11:()V
4: aload_0
5: invokespecial #1 // Method print12:()V
8: return
public void callInnerMethod(a);
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."
":(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
}
Copy the code
The Inner class defines the Inner class with a special name and stores the compiled result in two files:
$ 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(a);
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(a);
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
}
Copy the code
As you can see, there are a few more methods in Outer and Inner. The method name format is access$*00.
The Access $200 method in Outer returns the attribute I, and access$300 and Access $400 call the print11 and print12 methods, respectively. These new methods are static and have a default scope, that is, available in packages. These methods are eventually called by print3 and print4 in the Inner class, which is equivalent to indirectly calling private properties or methods in Outer.
We call these generated methods “Bridge methods” and they are a way of enabling mutual access within nested relationships.
At compile time, Java compiles nested classes into multiple class files in order to maintain a single class feature. At the same time, to ensure that nested classes can access each other, Java automatically creates “bridge” methods that call private methods. Thus, the nested syntax is implemented without leaving the original definition unchanged.
Technical debt
The implementation of the bridge method is tricky, but it would create a mismatch in access control between the source code and the compiled result. For example, we could call Outer’s private methods from Inner. An IllegalAccessException is thrown. Let’s verify:
public class Outer {
// omit other methods
public void callInnerReflectionMethod(a)
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
final Inner inner = new Inner();
inner.callOuterPrivateMethod(this);
}
public class Inner {
// omit other methods
public void callOuterPrivateMethod(Outer outer)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
final Method method = outer.getClass().getDeclaredMethod("print12"); method.invoke(outer); }}}Copy the code
Define test cases:
@Test
void gotAnExceptionInJava8(a) {
final Outer outer = new Outer();
final Exception e = assertThrows(IllegalAccessException.class, outer::callInnerReflectionMethod);
e.printStackTrace();
assertDoesNotThrow(outer::callInnerMethod);
}
Copy the code
The printed exception information is as follows:
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)
Copy the code
Calling private methods directly through reflection will fail, but these “bridge” methods can be accessed directly or through reflection, which is somewhat strange. So the JEP181 improvements are proposed to repair this technical debt while paving the way for subsequent improvements.
Implementation in Java11
Let’s take a look at the result of compiling 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(a);
Code:
0: aload_0
1: invokevirtual #2 // Method print11:()V
4: aload_0
5: invokevirtual #3 // Method print12:()V
8: return
public void callInnerMethod(a);
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."
":(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
}
Copy the code
Is it clean? It’s consistent with Outer’s source code structure. Let’s see if there are any changes to 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(a);
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(a);
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
}
Copy the code
Equally clean.
We are verifying the reflection call with a test case:
@Test
void doesNotGotAnExceptionInJava11(a) {
final Outer outer = new Outer();
assertDoesNotThrow(outer::callInnerReflectionMethod);
assertDoesNotThrow(outer::callInnerMethod);
}
Copy the code
The result is normal operation.
This is the desired result of JEP181, source code and compilation results are consistent, and access control is consistent.
New API for Nestmate
Several new apis have been added to Java11 for validation of nested relationships:
getNestHost
This method returns the nested host (NestHost), which translates into Mandarin to find the outer class of the nested class. For non-nested classes, return itself (or, in effect, the outer class).
Let’s look at the usage:
@Test
void checkNestHostName(a) {
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);
}
Copy the code
For the Outer and Inner are returned to the cn. Howardliu. Tutorials. Java11. Nest. The Outer.
getNestMembers
This method returns an array of nested members of a nested class. The element subscript 0 is determined to be the class corresponding to NestHost. Let’s take a look at the use:
@Test
void getNestMembers(a) {
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"));
}
Copy the code
isNestmateOf
This method is used to determine if two classes are nestmates of each other and form nested relationships with each other. If the two hosts are the same, they are nestmates. Let’s take a look at the use:
@Test
void checkIsNestmateOf(a) {
assertTrue(Inner.class.isNestmateOf(Outer.class));
assertTrue(Outer.class.isNestmateOf(Inner.class));
}
Copy the code
Subsequent improvements
Nesting is done as part of the Valhalla project, one of the main goals of which is to improve value types and generics in JAVA. More improvements will follow:
- In Generic Specialization, each specialized Type can be created as a Nestmate of a generic type.
- Support for
Unsafe.defineAnonymousClass()
A secure replacement API that implements Nestmate to create a new class as an existing class. - May affect Sealed Classes, allowing only subclasses of Nestmate as sealed classes.
- Private nested types may be affected. Private nested types are currently defined as package-access.
At the end of the article to summarize
This paper describes the access control optimization based on nesting relation, which involves NestMate, NestHost, NestMember and other concepts. This optimization is part of the Valhalla project to improve value types and generics in Java. The source code involved in the article are uploaded on GitHub, pay attention to the public number “see mountain hut” reply “Java” to obtain the source code.
Green hills never change, green waters always flow. See you next time.
Recommended reading
- This article describes 24 operations for Java8 Stream Collectors
- Java8 Optional 6 kinds of operations
- Use Lambda expressions to achieve super sorting functions
- Java8 Time Library (1) : Describes the time class and common apis in Java8
- Java8 time library (2) : Convert Date to LocalDate or LocalDateTime
- Java8 Time Library (3) : Start using Java8 time classes
- Java8 time library (4) : check if the date string is valid
- New features in Java8
- New features in Java9
- New features in Java10
- Optimization of access control based on nested relationships in Java11
- New features for Java11
- New features in Java12
Hello, I’m looking at the mountains. Swim in the code, play to enjoy life. If this article is helpful to you, please like, bookmark, follow.