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

Синтетические конструкции в Java

Узнайте о добавленных компилятором фрагментах кода привязки, которые позволяют прозрачно использовать некоторые функции Java

Автор оригинала: Donato Rimenti.

1. Обзор

В этом уроке мы рассмотрим синтетические конструкции Java, код, введенный компилятором для прозрачной обработки доступа к элементам, которые в противном случае были бы недоступны из-за недостаточной видимости или отсутствующих ссылок.

Примечание: начиная с JDK 11 , синтетические методы и конструкторы больше не генерируются, так как они заменяются управление доступом на основе гнезд .

2. Синтетика в Java

Лучшее определение synthetic , которое мы могли бы найти, исходит непосредственно из спецификации языка Java ( JLS 13.1.7 ):

Любые конструкции, введенные компилятором Java, которые не имеют соответствующей конструкции в исходном коде, должны быть помечены как синтетические, за исключением конструкторов по умолчанию, метода инициализации класса, а также значений и методов valueOf класса Enum.

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

Без лишних слов давайте углубимся в каждый из них.

3. Синтетические поля

Давайте начнем с простого вложенного класса:

public class SyntheticFieldDemo {
    class NestedClass {}
}

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

Чтобы убедиться, что это именно то, что происходит, мы реализуем тест, который получает вложенные поля класса путем отражения и проверяет их с помощью метода isSynthetic() :

public void givenSyntheticField_whenIsSynthetic_thenTrue() {
    Field[] fields = SyntheticFieldDemo.NestedClass.class
      .getDeclaredFields();
    assertEquals("This class should contain only one field",
      1, fields.length);

    for (Field f : fields) {
        System.out.println("Field: " + f.getName() + ", isSynthetic: " +
          f.isSynthetic());
        assertTrue("All the fields of this class should be synthetic", 
          f.isSynthetic());
    }
}

Другой способ проверить это-запустить дизассемблер с помощью команды javap. В любом случае вывод показывает синтетическое поле с именем this$0.

4. Синтетические методы

Далее мы добавим частное поле в наш вложенный класс:

public class SyntheticMethodDemo {
    class NestedClass {
        private String nestedField;
    }

    public String getNestedField() {
        return new NestedClass().nestedField;
    }

    public void setNestedField(String nestedField) {
        new NestedClass().nestedField = nestedField;
    }
}

В этом случае компиляция создаст методы доступа к переменной. Без этих методов было бы невозможно получить доступ к закрытому полю из заключающего экземпляра.

Еще раз, мы можем проверить это с помощью той же техники, которая показывает два синтетических метода, называемых access$0 и access$1 :

public void givenSyntheticMethod_whenIsSynthetic_thenTrue() {
    Method[] methods = SyntheticMethodDemo.NestedClass.class
      .getDeclaredMethods();
    assertEquals("This class should contain only two methods",
      2, methods.length);

    for (Method m : methods) {
        System.out.println("Method: " + m.getName() + ", isSynthetic: " +
          m.isSynthetic());
        assertTrue("All the methods of this class should be synthetic",
          m.isSynthetic());
    }
}

Обратите внимание, что для создания кода поле должно быть фактически прочитано или записано в , в противном случае методы будут оптимизированы|/. Именно по этой причине мы также добавили геттер и сеттер.

Как упоминалось выше, эти синтетические методы больше не генерируются, начиная с JDK 11.

4.1. Мостовые методы

Частным случаем синтетических методов являются мостовые методы, которые обрабатывают стирание типов дженериков.

Например, рассмотрим простой Компаратор :

public class BridgeMethodDemo implements Comparator {
    @Override
    public int compare(Integer o1, Integer o2) {
        return 0;
    }
}

Хотя compare() принимает два аргумента Integer в исходном коде, после компиляции он будет принимать два аргумента Object вместо этого из-за стирания типа.

Чтобы управлять этим, компилятор создает синтетический мост, который заботится о приведении аргументов :

public int compare(Object o1, Object o2) {
    return compare((Integer) o1, (Integer) o2);
}

В дополнение к нашим предыдущим тестам, на этот раз мы также вызовем is Bridge() из Метода класса:

public void givenBridgeMethod_whenIsBridge_thenTrue() {
    int syntheticMethods = 0;
    Method[] methods = BridgeMethodDemo.class.getDeclaredMethods();
    for (Method m : methods) {
        System.out.println("Method: " + m.getName() + ", isSynthetic: " +
          m.isSynthetic() + ", isBridge: " + m.isBridge());
        if (m.isSynthetic()) {
            syntheticMethods++;
            assertTrue("The synthetic method in this class should also be a bridge method",
              m.isBridge());
        }
    }
    assertEquals("There should be exactly 1 synthetic bridge method in this class",
      1, syntheticMethods);
}

5. Синтетические конструкторы

Наконец, мы добавим частный конструктор:

public class SyntheticConstructorDemo {
    private NestedClass nestedClass = new NestedClass();

    class NestedClass {
        private NestedClass() {}
    }
}

На этот раз, как только мы запустим тест или дизассемблер, мы увидим, что на самом деле существует два конструктора, один из которых синтетический:

public void givenSyntheticConstructor_whenIsSynthetic_thenTrue() {
    int syntheticConstructors = 0;
    Constructor[] constructors = SyntheticConstructorDemo.NestedClass
      .class.getDeclaredConstructors();
    assertEquals("This class should contain only two constructors",
      2, constructors.length);

    for (Constructor c : constructors) {
        System.out.println("Constructor: " + c.getName() +
          ", isSynthetic: " + c.isSynthetic());

        if (c.isSynthetic()) {
            syntheticConstructors++;
        }
    }

    assertEquals(1, syntheticConstructors);
}

Подобно синтетическим полям, этот сгенерированный конструктор необходим для создания экземпляра вложенного класса с помощью частного конструктора из его заключающего экземпляра.

Как упоминалось выше, синтетический конструктор больше не генерируется, начиная с JDK 11.

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

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

Как всегда, весь код доступен на GitHub .