«1. Введение
В этом руководстве мы рассмотрим гнезда, новый контекст управления доступом, представленный в Java 11.
2. До Java 11
2.1. Вложенные типы
Java позволяет вкладывать классы и интерфейсы друг в друга. Эти вложенные типы имеют неограниченный доступ друг к другу, в том числе к закрытым полям, методам и конструкторам.
Рассмотрим следующий пример вложенного класса:
public class Outer {
public void outerPublic() {
}
private void outerPrivate() {
}
class Inner {
public void innerPublic() {
outerPrivate();
}
}
}
Здесь, хотя метод externalPrivate() является закрытым, он доступен из метода innerPublic().
Мы можем описать тип верхнего уровня, а также все типы, вложенные в него, как образующие гнездо. Два члена гнезда описываются как соседи по гнезду.
Таким образом, в приведенном выше примере Outer и Inner вместе образуют гнездо и являются соседями друг друга.
2.2. Метод моста
Правила доступа JVM не разрешают частный доступ между соседями. В идеале мы должны получить ошибку компиляции для приведенного выше примера. Однако компилятор исходного кода Java разрешает доступ, вводя уровень косвенности.
Например, вызов закрытого члена компилируется в вызов сгенерированного компилятором закрытого для пакета связующего метода в целевом классе, который, в свою очередь, вызывает предполагаемый закрытый метод.
Это происходит за кулисами. Этот метод соединения немного увеличивает размер развернутого приложения и может запутать пользователей и инструменты.
2.3. Использование отражения
Еще одним следствием этого является то, что основное отражение также запрещает доступ. Это удивительно, учитывая, что рефлексивные вызовы должны вести себя так же, как и вызовы исходного кода.
Например, если мы попытаемся вызвать externalPrivate() рефлексивно из класса Inner:
public void innerPublicReflection(Outer ob) throws Exception {
Method method = ob.getClass().getDeclaredMethod("outerPrivate");
method.invoke(ob);
}
Мы получим исключение:
java.lang.IllegalAccessException:
Class com.baeldung.Outer$Inner can not access a member of class com.baeldung.Outer with modifiers "private"
Java 11 пытается решить эти проблемы.
3. Управление доступом на основе гнезда
Java 11 привносит понятие соплеменников и связанных с ними правил доступа в JVM. Это упрощает работу компиляторов исходного кода Java.
Для этого формат файла класса теперь содержит два новых атрибута:
- One nest member (typically the top-level class) is designated as the nest host. It contains an attribute (NestMembers) to identify the other statically known nest members.
- Each of the other nest members has an attribute (NestHost) to identify its nest host.
Таким образом, чтобы типы C и D были соседями по гнезду, они должны иметь один и тот же узел гнезда. Тип C утверждает, что является членом гнезда, размещенного D, если он перечисляет D в своем атрибуте NestHost. Членство подтверждается, если D также перечисляет C в своем атрибуте NestMembers. Кроме того, тип D неявно является членом гнезда, которое он размещает.
Теперь компилятору не нужно генерировать мостовые методы.
Наконец, управление доступом на основе гнезда устраняет неожиданное поведение основного отражения. Поэтому метод innerPublicReflection(), показанный в предыдущем разделе, будет выполняться без каких-либо исключений.
4. Nestmate Reflection API
Java 11 предоставляет средства для запроса новых атрибутов файла класса с использованием основного отражения. Класс java.lang.Class содержит следующие три новых метода.
4.1. getNestHost()
Это возвращает узел гнезда, которому принадлежит этот объект Class:
@Test
public void whenGetNestHostFromOuter_thenGetNestHost() {
is(Outer.class.getNestHost().getName()).equals("com.baeldung.Outer");
}
@Test
public void whenGetNestHostFromInner_thenGetNestHost() {
is(Outer.Inner.class.getNestHost().getName()).equals("com.baeldung.Outer");
}
Классы Outer и Inner принадлежат хосту гнезда com.baeldung.Outer.
4.2. isNestmateOf()
Определяет, является ли данный класс соседним по отношению к этому объекту класса:
@Test
public void whenCheckNestmatesForNestedClasses_thenGetTrue() {
is(Outer.Inner.class.isNestmateOf(Outer.class)).equals(true);
}
4.3. getNestMembers()
Это возвращает массив, содержащий объекты класса, представляющие всех членов гнезда, к которому принадлежит этот объект класса:
@Test
public void whenGetNestMembersForNestedClasses_thenGetAllNestedClasses() {
Set<String> nestMembers = Arrays.stream(Outer.Inner.class.getNestMembers())
.map(Class::getName)
.collect(Collectors.toSet());
is(nestMembers.size()).equals(2);
assertTrue(nestMembers.contains("com.baeldung.Outer"));
assertTrue(nestMembers.contains("com.baeldung.Outer$Inner"));
}
5. Детали компиляции
5.1. Метод моста до Java 11
Давайте углубимся в детали метода моста, сгенерированного компилятором. Мы можем увидеть это, разобрав результирующий файл класса:
$ javap -c Outer
Compiled from "Outer.java"
public class com.baeldung.Outer {
public com.baeldung.Outer();
Code:
0: aload_0
1: invokespecial #2 // Method java/lang/Object."<init>":()V
4: return
public void outerPublic();
Code:
0: return
static void access$000(com.baeldung.Outer);
Code:
0: aload_0
1: invokespecial #1 // Method outerPrivate:()V
4: return
}
Здесь, кроме конструктора по умолчанию и открытого метода externalPublic(), обратите внимание на метод access$000(). Компилятор генерирует это как метод соединения.
innerPublic() проходит через этот метод, чтобы вызвать externalPrivate():
$ javap -c Outer\$Inner
Compiled from "Outer.java"
class com.baeldung.Outer$Inner {
final com.baeldung.Outer this$0;
com.baeldung.Outer$Inner(com.baeldung.Outer);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:Lcom/baeldung/Outer;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
public void innerPublic();
Code:
0: aload_0
1: getfield #1 // Field this$0:Lcom/baeldung/Outer;
4: invokestatic #3 // Method com/baeldung/Outer.access$000:(Lcom/baeldung/Outer;)V
7: return
}
Обратите внимание на комментарий в строке #19. Здесь innerPublic() вызывает метод моста access$000().
5.2. Nestmates с Java 11
Компилятор Java 11 сгенерирует следующий дизассемблированный файл класса Outer:
$ javap -c Outer
Compiled from "Outer.java"
public class com.baeldung.Outer {
public com.baeldung.Outer();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void outerPublic();
Code:
0: return
}
«
$ javap -c Outer\$Inner.class
Compiled from "Outer.java"
class com.baeldung.Outer$Inner {
final com.baeldung.Outer this$0;
com.baeldung.Outer$Inner(com.baeldung.Outer);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:Lcom/baeldung/Outer;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
public void innerPublic();
Code:
0: aload_0
1: getfield #1 // Field this$0:Lcom/baeldung/Outer;
4: invokevirtual #3 // Method com/baeldung/Outer.outerPrivate:()V
7: return
}
«Обратите внимание, что нет метода соединения, сгенерированного компилятором. Кроме того, класс Inner теперь может напрямую вызывать метод externalPrivate(): обычно фрагменты кода можно найти на GitHub.
«
As usual, code snippets can be found over on GitHub.