Рубрики
Без рубрики

Java 11 Управление доступом на основе гнезда

Узнайте о правилах доступа к вложенным типам Java и изменениях, внесенных в Java 11

Автор оригинала: Ganesh Pagade.

1. Введение

В этом учебнике мы изумим гнезда , новый контекст управления доступом, представленный в Java 11.

2. Перед Java 11

2.1. Вложенные типы

Java позволяет создавать классы и вложенные друг в друга . Эти вложенные типы имеют неограниченный доступ друг к другу, в том числе к частным полям, методам и конструкторам.

Рассмотрим следующий пример вложенного класса:

public class Outer {

    public void outerPublic() {
    }

    private void outerPrivate() {
    }

    class Inner {

        public void innerPublic() {
            outerPrivate();
        }
    }
}

Здесь, хотя метод внешнийПриват () это частные , он доступен из метода innerPublic () .

Мы можем описать тип верхнего уровня, плюс все типы, вложенные в него, как формирование гнезда. Два члена гнезда описаны как соседи по гнезду.

Таким образом, в приведенной выше примере Внешние и Внутренний вместе образуют гнездо и являются гнездами друг друга.

2.2. Метод моста

Правила доступа JVM не разрешают частный доступ между соседами по гнездам. В идеале, мы должны получить ошибку компиляции для приведенного выше примера. Тем не менее, компилятор исходных кодов Java позволяет получить доступ, вводя уровень косвенности.

Например, вызов частного члена компилируются в вызов компилятора, генерируемого пакетом-частным методом преодоления в целевом классе, который, в свою очередь, вызывает предполагаемый частный метод .

Это происходит за кулисами. Этот метод преодоления немного увеличивает размер развернутого приложения и может запутать пользователей и инструменты.

2.3. Использование отражения

Еще одним последствием этого является то, что основное отражение также лишает доступа. Это удивительно, учитывая, что рефлексивные призывы должны вести себя так же, как вызовы уровня источника.

Например, если мы попытаемся назвать внешнийПриват () отражающий от Внутренний класс:

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 приводит понятие nestmates и связанные с ними правила доступа в . Это упрощает работу компиляторов исходных кодов Java.

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

  1. Один член гнезда (обычно класс верхнего уровня) обозначен как хозяин гнезда. Он содержит атрибут (NestMembers) для идентификации других статически известных членов гнезда.
  2. Каждый из других членов гнезда имеет атрибут (NestHost), чтобы определить его хост гнезда.

Таким образом, для типов C и D чтобы быть nestmates они должны иметь тот же хозяин гнезда. Тип C утверждает, что является членом гнезда, организованного D , если он перечисляет D в атрибуте NestHost. Членство проверяется, если D также списки C в атрибуте NestMembers. Кроме того, D неявно является членом гнезда, которое оно принимает.

Теперь есть нет необходимости в компиляторе для генерации методов моста .

Наконец, элемент управления доступом на основе гнезда удаляет удивительное поведение из основного отражения. Таким образом, метод innerPublicReflection () показано в предыдущем разделе будет выполняться без каких-либо исключений.

4. API отражения Nestmate

Java 11 предоставляет средства для запроса атрибутов нового файла класса с помощью основных отражений . Классная java.lang.Class содержит следующие три новых метода.

4.1. getNestHost()

Это возвращает гнездо хозяина гнезда, к которому этот Класс объект принадлежит:

@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");
}

Оба Внешние и Внутренний классы принадлежат к гнезду принимающей com.baeldung.Внешний .

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 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."":()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
}

Здесь, кроме конструктора по умолчанию и общедоступного метода внешняяПублика () , обратите внимание на метод доступ $000 () . Компилятор генерирует это как метод преодоления.

innerPublic () проходит через этот метод, чтобы вызвать внешнийПриват () :

$ 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."":()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 () называет метод моста доступ $000 () .

5.2. Нестматы с Java 11

Компилятор Java 11 будет генерировать следующие разобранные Внешние класс файла:

$ 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."":()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."":()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
}

6. Заключение

В этой статье мы исследовали управление доступом на основе гнезда, введенное в Java 11.

Как обычно, фрагменты кода можно найти более на GitHub .