Java 11 – Nest-Based Access Control

This JEP 181: Nest-Based Access Control, supports private access within nest members directly, no more via an auto-generated bridge method access$000. Furthermore, new nest APIs for validation and allowed private reflection access within nest members.

P.S No need to change the code, it is a Java compiler’s optimization to remove the bridge method access.

1. Before Java 11, bridge method access$000

1.1 Review the following nested class. All nested classes are try to access a private member of Alphabet, aka NestHost.

Alphabet.java

public class Alphabet {

    private String name = "I'm Alphabet!";

    public class A {
        public void printName() {
            System.out.println(name);       // access Alphabet's private member!
        }
    }

    public class B {
        public void printName() {
            System.out.println(name);       // access Alphabet's private member!
        }

        public class B1 {
            public void printName() {
                System.out.println(name);   // access Alphabet's private member!
            }
        }
    }
}

1.2 If we compile the above source, it will generate four classes, Alphabet, Alphabet$A, Alphabet$B, and Alphabet$B.B1, even a nested class is a typical class with a unique name. The JVM access rule will not allow private access within different classes.

However, Java allowed private access within nest members, so the Java compiler creates a bridge method access$000 to apply on the JVM access rule.

Terminal

$ javac Alphabet.java

Alphabet$A.class
Alphabet$B.class
Alphabet$B.B1.class
Alphabet.class

1.3 The source code of the bridge method access is similar to this:


public class Alphabet {
  private String name = "I'm Alphabet!";
  String access$000(){
    return name;
  }
}

public class Alphabet$A {
  final Alphabet obj;
  public void printName(){
    System.out.println(obj.access$000());
  }
}

public class Alphabet$B {
  final Alphabet obj;
  public void printName(){
    System.out.println(obj.access$000());
  }
}

public class Alphabet$B$B1 {
  final Alphabet$B this$1;
  public void printName(){
    System.out.println(obj.access$000());
  }
}

2. Disassemble the code

We can use javap -c to disassemble the above four classes to see the differential before and after bridge method access.

2.1 Before Java 11, it creates a new access$000 bridge method for the nested private access automatically.

Terminal

$ javac Alphabet.java

$ javap -c Alphabet
Compiled from "Alphabet.java"
public class Alphabet {
  public Alphabet();
    Code:
       0: aload_0
       1: invokespecial #2                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: ldc           #3                  // String I'm Alphabet!
       7: putfield      #1                  // Field name:Ljava/lang/String;
      10: return

  static java.lang.String access$000(Alphabet);
    Code:
       0: aload_0
       1: getfield      #1                  // Field name:Ljava/lang/String;
       4: areturn
}

$ javap -c Alphabet$A
Compiled from "Alphabet.java"
public class Alphabet$A {
  final Alphabet this$0;

  public Alphabet$A(Alphabet);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:LAlphabet;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: return

  public void printName();
    Code:
       0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #1                  // Field this$0:LAlphabet;
       7: invokestatic  #4                  // Method Alphabet.access$000:(LAlphabet;)Ljava/lang/String;
      10: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      13: return
}

$ javap -c Alphabet$B
Compiled from "Alphabet.java"
public class Alphabet$B {
  final Alphabet this$0;

  public Alphabet$B(Alphabet);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:LAlphabet;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: return

  public void printName();
    Code:
       0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #1                  // Field this$0:LAlphabet;
       7: invokestatic  #4                  // Method Alphabet.access$000:(LAlphabet;)Ljava/lang/String;
      10: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      13: return
}

$ javap -c Alphabet$B$B1
Compiled from "Alphabet.java"
public class Alphabet$B$B1 {
  final Alphabet$B this$1;

  public Alphabet$B$B1(Alphabet$B);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$1:LAlphabet$B;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: return

  public void printName();
    Code:
       0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #1                  // Field this$1:LAlphabet$B;
       7: getfield      #4                  // Field Alphabet$B.this$0:LAlphabet;
      10: invokestatic  #5                  // Method Alphabet.access$000:(LAlphabet;)Ljava/lang/String;
      13: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      16: return
}

2.2 In Java 11, it supports private access within nest members directly, with NO bridge method.

Tested with Java 11.

Terminal

$ javac Alphabet.java

$ javap -c Alphabet
Compiled from "Alphabet.java"
public class Alphabet {
  public Alphabet();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: ldc           #2                  // String I\'m Alphabet!
       7: putfield      #3                  // Field name:Ljava/lang/String;
      10: return
}

$ javap -c Alphabet$A
Compiled from "Alphabet.java"
public class Alphabet$A {
  final Alphabet this$0;

  public Alphabet$A(Alphabet);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:LAlphabet;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: return

  public void printName();
    Code:
       0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #1                  // Field this$0:LAlphabet;
       7: getfield      #4                  // Field Alphabet.name:Ljava/lang/String;
      10: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      13: return
}

$ javap -c Alphabet$B
Compiled from "Alphabet.java"
public class Alphabet$B {
  final Alphabet this$0;

  public Alphabet$B(Alphabet);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:LAlphabet;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: return

  public void printName();
    Code:
       0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #1                  // Field this$0:LAlphabet;
       7: getfield      #4                  // Field Alphabet.name:Ljava/lang/String;
      10: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      13: return
}

$ javap -c Alphabet$B$B1
Compiled from "Alphabet.java"
public class Alphabet$B$B1 {
  final Alphabet$B this$1;

  public Alphabet$B$B1(Alphabet$B);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$1:LAlphabet$B;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: return

  public void printName();
    Code:
       0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #1                  // Field this$1:LAlphabet$B;
       7: getfield      #4                  // Field Alphabet$B.this$0:LAlphabet;
      10: getfield      #5                  // Field Alphabet.name:Ljava/lang/String;
      13: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      16: return
}

3. New Nest Classes APIs.

3.1 Some new APIs to validate the nested members.

  • getNestHost
  • getNestMembers
  • isNestmateOf

For example, review the following nested classes. The Alphabet is NestHost, and all others are NestMembers or NestmateOf to each other.

Alphabet.java

public class Alphabet {

    public class A {
    }

    public class B {
        public class B1 {
        }
    }
}

3.2 The below source code shows the use of the new nested APIs. The only trick is the B.B1.class.getNestHost() returns Alphabet, not B. The other results are obvious, read the comments.

Alphabet.java

package com.mkyong.java11.jep181;

import java.util.Arrays;

public class Alphabet {

    private String name = "I'm Alphabet!";

    public class A {
        public void printName() {
            System.out.println(name);       // access Alphabet's private member!
        }
    }

    public class B {
        public void printName() {
            System.out.println(name);       // access Alphabet's private member!
        }

        public class B1 {
            public void printName() {
                System.out.println(name);   // access Alphabet's private member!
            }
        }
    }

    public static void main(String[] args) {

        A objA = new Alphabet().new A();
        objA.printName();

        B objB = new Alphabet().new B();
        objB.printName();

        B.B1 objB1 = new Alphabet().new B().new B1();
        objB1.printName();

        System.out.println(Alphabet.class.getNestHost());       // Alphabet
        System.out.println(A.class.getNestHost());              // Alphabet
        System.out.println(B.class.getNestHost());              // Alphabet
        System.out.println(B.B1.class.getNestHost());           // Alphabet!, not B

        System.out.println("---");

        System.out.println(Arrays.toString(Alphabet.class.getNestMembers()));   // Alphabet, Alphabet$A, Alphabet$B, Alphabet$B$B1
        System.out.println(Arrays.toString(A.class.getNestMembers()));          // Alphabet, Alphabet$A, Alphabet$B, Alphabet$B$B1
        System.out.println(Arrays.toString(B.class.getNestMembers()));          // Alphabet, Alphabet$A, Alphabet$B, Alphabet$B$B1
        System.out.println(Arrays.toString(B.B1.class.getNestMembers()));       // Alphabet, Alphabet$A, Alphabet$B, Alphabet$B$B1

        System.out.println("---");

        System.out.println(Alphabet.class.isNestmateOf(Alphabet.class));        // true
        System.out.println(Alphabet.class.isNestmateOf(A.class));               // true
        System.out.println(Alphabet.class.isNestmateOf(B.class));               // true
        System.out.println(Alphabet.class.isNestmateOf(B.B1.class));            // true

        System.out.println("---");

        System.out.println(A.class.isNestmateOf(Alphabet.class));               // true
        System.out.println(A.class.isNestmateOf(A.class));                      // true
        System.out.println(A.class.isNestmateOf(B.class));                      // true
        System.out.println(A.class.isNestmateOf(B.B1.class));                   // true

        System.out.println("---");

        System.out.println(B.class.isNestmateOf(Alphabet.class));               // true
        System.out.println(B.class.isNestmateOf(A.class));                      // true
        System.out.println(B.class.isNestmateOf(B.class));                      // true
        System.out.println(B.class.isNestmateOf(B.B1.class));                   // true

        System.out.println("---");

        System.out.println(B.B1.class.isNestmateOf(Alphabet.class));            // true
        System.out.println(B.B1.class.isNestmateOf(A.class));                   // true
        System.out.println(B.B1.class.isNestmateOf(B.class));                   // true
        System.out.println(B.B1.class.isNestmateOf(B.B1.class));                // true

    }
}

4. Reflection Access

3.1 The InnerA tries to access the InnerB private method via reflection API.

Outer.java

package com.mkyong.java11.jep181;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Outer {

    public static class InnerA {
        // InnerA access InnerB private method, via reflection!
        // Before Java 11 - IllegalAccessException
        // Java 11 - OK
        public void printName() throws NoSuchMethodException,
            InvocationTargetException, IllegalAccessException {

            InnerB obj = new InnerB();
            final Method m = InnerB.class.getDeclaredMethod("printName");
            m.invoke(obj);
        }
    }

    public static class InnerB {
        // private!!!
        private void printName() {
            System.out.println("I'm InnerB!");
        }
    }

    public static void main(String[] args) throws NoSuchMethodException,
        IllegalAccessException, InvocationTargetException {

        InnerA obj = new InnerA();
        obj.printName();
    }
}

Before Java 11, it throws java.lang.IllegalAccessException.

Terminal

Exception in thread "main" java.lang.IllegalAccessException: Class com.mkyong.java11.jep181.Outer$InnerA can not access a member of class com.mkyong.java11.jep181.Outer$InnerB with modifiers "private"

at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.mkyong.java11.jep181.Outer$InnerA.printName(Outer.java:14)
at com.mkyong.java11.jep181.Outer.main(Outer.java:28)

Run with Java 11, no problem. The new nest-based access control allows private access to nested members, even via reflection API.

Terminal

I'm InnerB!

Download Source Code

$ git clone https://github.com/mkyong/core-java
$ cd java-11
$ cd src/main/java/com/mkyong/java11/jep181

References

author image

mkyong

Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities. Read all published posts by

Comments

avatar
newest oldest most voted
Jhon tiger
Guest
Jhon tiger

This article is very interesting

Jhon tiger
Guest
Jhon tiger

This channel is making tutorials in french for java certification https://bit.ly/2XpG9Q8