Why make private inner class member public in Java?

前端 未结 7 1982
情深已故
情深已故 2020-12-02 15:54

What is the reason of declaring a member of a private inner class public in Java if it still can\'t be accessed outside of containing class? Or can it?

publi         


        
7条回答
  •  温柔的废话
    2020-12-02 16:15

    There are multiple aspects which have to be considered here. The following will use the term "nested class" because it covers both non-static (also called "inner class") and static classes (source).

    Not related to private nested classes, but JLS §8.2 has an interesting example which shows where public members in package-private or protected classes could be useful.

    Source code

    Overriding methods

    When your nested class implements an interface or extends a class and overrides one of its methods, then per JLS §8.4.8.3:

    The access modifier of an overriding or hiding method must provide at least as much access as the overridden or hidden method

    For example:

    public class Outer {
      private static class Nested implements Iterator {
        @Override
        public boolean hasNext() {
          ...
        }
        
        @Override
        public String next() {
          ...
        }
      }
    }
    

    The methods hasNext() and next() which override the Iterator methods have to be public because the Iterator methods are public.

    As a side note: JLS §13.4.7 describes that it is possible for a class to increase the access level of one of its methods, even if a subclass overrides it with, without causing linkage errors.

    Conveying intention

    Access restriction is defined in JLS §6.6.1:

    A member [...] of a reference type [...] is accessible only if the type is accessible and the member or constructor is declared to permit access

    [...]

    Otherwise, the member or constructor is declared private, and access is permitted if and only if it occurs within the body of the top level type (§7.6) that encloses the declaration of the member or constructor.

    Therefore members of a private nested class can (from a source code perspective; see also "Reflection" section) only be accessed from the body of the enclosing top level type. Interestingly the "body" also covers other nested classes:

    public class TopLevel {
      private static class Nested1 {
        private int i;
      }
    
      void doSomething(Nested1 n) {
        // Can access private member of nested class
        n.i++;
      }
    
      private static class Nested2 {
        void doSomething(Nested1 n) {
          // Can access private member of other nested class
          n.i++;
        }
      }
    }
    

    So from a compiler-provided access restriction perspective there is indeed no point in using a public member in a private nested class.

    However, using different access levels can be useful for conveying intention, especially (as pointed out by others) when the nested class might be refactored to a separate top level class in the future. Consider this example:

    public class Cache {
      private static class CacheEntry {
        private final T value;
        private long lastAccessed;
    
        // Signify that enclosing class may use this constructor
        public CacheEntry(T value) {
          this.value = value;
          updateLastAccessed();
        }
    
        // Signify that enclosing class must NOT use this method
        private void updateLastAccessed() {
          lastAccessed = System.nanoTime();
        }
    
        // Signify that enclosing class may use this method
        public T getValue() {
          updateLastAccessed();
          return value;
        }
      }
    
      ...
    }
    
    

    Compiled class files

    It is also interesting to note how the Java compiler treats access to members of nested classes. Prior to JEP 181: Nest-Based Access Control (added in Java 11) the compiler had to create synthetic accessor methods because the class file could not express the access control logic related to nested classes. Consider this example:

    class TopLevel {
      private static class Nested {
        private int i;
      }
        
      void doSomething(Nested n) {
        n.i++;
      }
    }
    

    When compiled with Java 8 and inspected with javap -p ./TopLevel$Nested.class you will see that a synthetic access$008 method has been added:

    class TopLevel$Nested {
      private int i;
      private TopLevel$Nested();
      static int access$008(TopLevel$Nested);
    }
    

    This slightly increased the size of the class files and might have decreased performance. This is one reason why package-private (i.e. no access modifier) access has often be chosen for members of nested classes to prevent creation of synthetic access methods.
    With JEP 181 this is no longer necessary (javap -v output when compiled with JDK 11):

    class TopLevel$Nested
    ...
    {
      private int i;
      ...
    
      private TopLevel$Nested();
      ...
    }
    ...
    NestHost: class TopLevel
    ...
    

    Reflection

    Another interesting aspect is reflection. The JLS is sadly not verify specific in that regard, but §15.12.4.3 contains an interesting hint:

    If T is in a different package than D, and their packages are in the same module, and T is public or protected, then T is accessible.

    [...]

    If T is protected, it is necessarily a nested type, so at compile time, its accessibility is affected by the accessibility of types enclosing its declaration. However, during linkage, its accessibility is not affected by the accessibility of types enclosing its declaration. Moreover, during linkage, a protected T is as accessible as a public T.

    Similarly AccessibleObject.setAccessible(...) does not mention the enclosing type at all. And indeed it is possible to access the members of a public or protected nested type within non-public enclosing type: test1/TopLevel1.java

    package test1;
    
    // package-private
    class TopLevel1 {
      private static class Nested1_1 {
        protected static class Nested1_2 {
          public static int i;
        }
      }
    }
    

    test2/TopLevel2.java

    package test2;
    
    import java.lang.reflect.Field;
    
    public class TopLevel2 {
      public static void main(String... args) throws Exception {
        Class nested1_2 = Class.forName("test1.TopLevel1$Nested1_1$Nested1_2");
        Field f = nested1_2.getDeclaredField("i");
        f.set(null, 1);
      }
    }
    

    Here reflection is able to modify the field test1.TopLevel1.Nested1_1.Nested1_2.i without having to make it accessible despite it being inside a private nested class inside a package-private class.

    When you are writing code for an environment where untrusted code is run you should keep that in mind to prevent malicious code from messing with internal classes.
    So when it comes to the access level of nested types you should always choose the least permissive one, ideally private or package-private.

提交回复
热议问题