Синтетический элемент - это любой элемент, который присутствует в скомпилированном файле класса, но отсутствует в исходном коде, из которого он скомпилирован. Проверяя элемент на предмет его синтетичности, вы разрешаете различать такие элементы для инструментов, которые обрабатывают код рефлексивно. Это, конечно, в первую очередь относится к библиотекам, использующим отражение, но также относится и к другим инструментам, таким как IDE, которые не позволяют вызывать синтетические методы или работать с синтетическими классами. Наконец, для компилятора Java также важно проверять код во время его компиляции, чтобы никогда напрямую не использовать синтетические элементы. Синтетические элементы используются только для того, чтобы сделать среду выполнения Java счастливой, которая просто обрабатывает (и проверяет) доставленный код, где он обрабатывает синтетические элементы идентично любому другому элементу.
Вы уже упоминали внутренние классы в качестве примера, где синтетические элементы вставляются компилятором Java, поэтому давайте посмотрим на такой класс:
class Foo {
private String foo;
class Bar {
private Bar() { }
String bar() {
return foo;
}
}
Bar bar() {
return new Bar();
}
}
Это прекрасно компилируется, но без синтетических элементов, это будет отклонено JVM, которая ничего не знает о внутренних классах. Компилятор Java desugares вышеприведенного класса примерно так:
class Foo {
private String foo;
String access$100() { // synthetic method
return foo;
}
Foo$Bar bar() {
return new Foo$Bar(this, (Foo$1)null);
}
Foo() { } // NON-synthetic, but implicit!
}
class Foo$Bar {
private final Foo $this; // synthetic field
private Foo$Bar(Foo $this) { // synthetic parameter
this.$this = $this;
}
Foo$Bar(Foo $this, Foo$1 unused) { // synthetic constructor
this($this);
}
String bar() {
return $this.access$100();
}
}
class Foo$1 { /*empty, no constructor */ } // synthetic class
Как уже говорилось, JVM не знает о внутренних классах, но обеспечивает частный доступ к членам, то есть внутренний класс не сможет получить доступ к закрытым свойствам своих включающих классов. Таким образом, компилятору Java необходимо добавить так называемые методы доступа к доступному классу, чтобы раскрыть его невидимые свойства:
Поле foo
является закрытым и поэтому доступно только изнутри Foo
. Метод access$100
предоставляет это поле своему пакету, в котором всегда находится внутренний класс. Этот метод является синтетическим, так как он добавлен компилятором.
Конструктор Bar
является закрытым и поэтому может вызываться только из своего собственного класса. Чтобы создать экземпляр Bar
, другой (синтетический) конструктор должен предоставить конструкцию экземпляра. Однако конструкторы имеют фиксированное имя (внутренне они все называются <init>
), поэтому мы не можем применить технику для методов доступа к методам, где мы просто назвали их access$xxx
. Вместо этого мы делаем конструктор доступа уникальным, создавая синтетический тип Foo$1
.
Чтобы получить доступ к своему внешнему экземпляру, внутренний класс должен хранить ссылку на этот экземпляр, которая хранится в синтетическом поле $this
. Эту ссылку необходимо передать внутреннему экземпляру с помощью синтетического параметра в конструкторе.
Другими примерами для синтетических элементов являются классы, представляющие лямбда-выражения, методы-мосты при переопределении методов с расходящимися по типу сигнатурами, создание Proxy
классов или классов, которые создаются другими инструментами, такими как сборки Maven или генераторы кода времени выполнения, например как Byte Buddy (бесстыдная вилка).