Синтетический элемент — это любой элемент, который присутствует в скомпилированном файле класса, но отсутствует в исходном коде, из которого он скомпилирован. Проверяя элемент на предмет того, что он синтетический, вы позволяете различать такие элементы для инструментов, которые обрабатывают код рефлексивно. Это, конечно, в первую очередь относится к библиотекам, которые используют отражение, но это также относится и к другим инструментам, таким как IDE, которые не позволяют вам вызывать синтетические методы или работать с синтетическими классами. Наконец, для компилятора Java также важно проверять код во время его компиляции, чтобы никогда напрямую не использовать синтетические элементы. Синтетические элементы используются только для того, чтобы сделать среду выполнения Java счастливой, которая просто обрабатывает (и проверяет) доставленный код, где он обрабатывает синтетические элементы идентично любому другому элементу.
Вы уже упомянули внутренние классы в качестве примера, где синтетические элементы вставляются компилятором Java, поэтому давайте рассмотрим такой класс:
class Foo {
private String foo;
class Bar {
private Bar() { }
String bar() {
return foo;
}
}
Bar bar() {
return new Bar();
}
}
Это прекрасно компилируется, но без синтетических элементов JVM отказалась бы от него, ничего не зная о внутренних классах. Компилятор Java обезуглероживает вышеприведенный класс во что-то вроде следующего:
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 (бессовестный плагин).
person
Rafael Winterhalter
schedule
26.09.2014