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<String> {
@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<T> {
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.