«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.

Для этого формат файла класса теперь содержит два новых атрибута:

  1. 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.
  2. 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.