Здесь нужно учитывать несколько аспектов. Далее будет использоваться термин вложенный класс, поскольку он охватывает как не-1_ (также называемый внутренним классом), так и static
классы (источник).
Не относится к private
вложенным классам, но в JLS §8.2 есть интересный пример, который показывает, где могут быть полезны public
члены в классах package-private или protected
.
Исходный код
Методы переопределения
Когда ваш вложенный класс реализует интерфейс или расширяет класс и переопределяет один из его методов, то в соответствии с JLS §8.4.8.3:
Модификатор доступа переопределяющего или скрывающего метода должен предоставлять как минимум такой же доступ, как и переопределяемый или скрытый метод.
Например:
public class Outer {
private static class Nested implements Iterator<String> {
@Override
public boolean hasNext() {
...
}
@Override
public String next() {
...
}
}
}
Методы hasNext()
и next()
, переопределяющие методы Iterator
, должны быть public
, поскольку методы Iterator
общедоступны.
В качестве примечания: JLS §13.4.7 описывает, что класс может увеличить уровень доступа одного из своих методов, даже если подкласс переопределяет его, не вызывая ошибок компоновки.
Передача намерения
Ограничение доступа определяется в JLS. §6.6.1:
Член [...] ссылочного типа [...] доступен только в том случае, если тип доступен, а член или конструктор объявлен для разрешения доступа
[...]
В противном случае член или конструктор объявляется private
, и доступ разрешается тогда и только тогда, когда он происходит в теле типа верхнего уровня (§7.6), который содержит объявление члена или конструктора.
Поэтому к членам вложенного класса private
можно (с точки зрения исходного кода; см. также раздел «Отражение») получить доступ только из тела вложенного типа верхнего уровня. Интересно, что тело также охватывает другие вложенные классы:
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++;
}
}
}
Таким образом, с точки зрения ограничения доступа, предоставляемого компилятором, действительно нет смысла использовать член public
во вложенном классе private
.
Однако использование разных уровней доступа может быть полезно для передачи намерений, особенно (как указывали другие), когда в будущем вложенный класс может быть реорганизован в отдельный класс верхнего уровня. Рассмотрим этот пример:
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;
}
}
...
}
Скомпилированные файлы классов
Также интересно отметить, как компилятор Java обрабатывает доступ к членам вложенных классов. До JEP 181: Nest-Based Access Control (добавлен в Java 11) компилятор должен был создавать синтетические методы доступа, потому что файл класса не мог выразить логику управления доступом, связанную с вложенными классами. Рассмотрим этот пример:
class TopLevel {
private static class Nested {
private int i;
}
void doSomething(Nested n) {
n.i++;
}
}
При компиляции с Java 8 и проверке с помощью javap -p ./TopLevel$Nested.class
вы увидите, что был добавлен синтетический метод access$008
:
class TopLevel$Nested {
private int i;
private TopLevel$Nested();
static int access$008(TopLevel$Nested);
}
Это немного увеличило размер файлов классов и могло снизить производительность. Это одна из причин, по которой для членов вложенных классов часто выбирается закрытый для пакета (т.е. без модификатора доступа) доступ, чтобы предотвратить создание синтетических методов доступа.
С JEP 181 в этом больше нет необходимости (javap -v
вывод при компиляции с СДК 11):
class TopLevel$Nested
...
{
private int i;
...
private TopLevel$Nested();
...
}
...
NestHost: class TopLevel
...
Отражение
Еще один интересный аспект — отражение. К сожалению, JLS не является конкретным в этом отношении, но §15.12.4.3 содержит интересную подсказку:
Если T находится в другом пакете, чем D, и их пакеты находятся в одном модуле, а T равен public
или protected
, то T доступен.
[...]
Если T равно protected
, это обязательно вложенный тип, поэтому во время компиляции на его доступность влияет доступность типов, включающих его объявление. Однако во время компоновки на его доступность не влияет доступность типов, включающих его объявление. Более того, во время связывания protected
T так же доступен, как и public
T.
Аналогично AccessibleObject.setAccessible(...)
вообще не упоминает объемлющий тип. И действительно, можно получить доступ к членам вложенного типа public
или protected
внутри включающего типа, отличного от public
: 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);
}
}
Здесь отражение может изменить поле test1.TopLevel1.Nested1_1.Nested1_2.i
без необходимости делать его доступным, несмотря на то, что оно находится внутри private
вложенного класса внутри закрытого от пакета класса.
Когда вы пишете код для среды, в которой выполняется ненадежный код, вы должны помнить об этом, чтобы предотвратить вмешательство вредоносного кода во внутренние классы.
Поэтому, когда дело доходит до уровня доступа к вложенным типам, вы всегда должны выбирать наименьший разрешительный, в идеале private
или пакетный.
person
Marcono1234
schedule
25.10.2020