Автор оригинала: 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 .