Составление вложенные классы: javac и есс

Составление вложенные классы: javac и есс

417
ПОДЕЛИТЬСЯ

Как вы понимаете, в Java имеются вложенные (nested) классы, объявленный снутри другого класса. Все работы по трансформации вложенные классы в обыденных ложится на компилятор. И тут любопытно поглядеть, как различные компиляторы могут совладать с ней. С ее точки зрения это нормально, классы, расположенные в одном пакете, и наружного класса. Они даже 4 видов — статические вложенные, внутренние , локальные и анонимные (в данной статье, мы не подвержены лямбда-выражения, введенные в Java 8). Все они имеют одну увлекательную изюминка: виртуальная машинка Java имеет никакого представления о особенном статусе этих классов. Мы смотрим на поведение javac 1.8.0.20 и есс компилятор Eclipse JDT ядро 3.10 (поставляется в комплекте с Eclipse Luna).
Вот главные трудности, связанные с составления вложенных классов:

Права доступа;
Проходя ссылки на объект наружного класса (не статические вложенные классы);
Прохождение локальных переменных из наружного контекста (подобная схема).
В данной статье мы побеседуем о первых 2-ух заморочек.
Права доступа
Права доступа могут появиться крупная неурядица. Мы можем объявить поле либо способ вложенного класса личных, и согласно спецификации Java, чтоб это поле либо способ по-прежнему могут быть доступны из наружного класса. Способы доступа, нужные для чтения полей, поля ввода и вызова способов. Чтоб обойти это ограничение, компиляторы делают особые способы доступа. Можно и напротив: для доступа к отдельным полям либо способам наружного класса из вложенной либо один из вложенного класса, чтоб применять иной. То же самое касается доступа к защищенным членам родительского класса, расположенный в иной пакет. Но, с точки зрения Java-машинки для доступа к закрытым членам другого класса неприемлимо. Не считая того, Европейский трибунал именует их просто доступ$0, access,$1, и т.д. и javac добавляет минимум из 3-х цифр, где крайние два кодировки определенной операции (read = 00, прибытие в = 02) и изначальное поле либо способ. Все они статические, иметь доступ package-private, и именуются, так как доступ$.
В есс написать способы возвращают void, и в javac — новое значение (2-ой параметр). Возьмем, к примеру, этот код: Способы доступа для чтения поля имеют один параметр — объект, и способы регистрации полей — 2-ух характеристик (объект, и новое значение).
public class Outer {
private int a;

статический вложенный класс {
int b;

void способ(наружное я) {
b = i.а;
я.a = 5;
}
}
}
Раз б-код, генерируемый Javac, перевести обратно в Java, получить это:
public class Outer {
private int a;

static int access$000(Внешний obj) {
return obj.а;
}

static int access$002(Внешний obj, int val) {
return (obj.a = val);
}
}

класс Outer$вложенных {
int b;

void способ(наружное я) {
b = Внешняя.доступ$000(я);
Внешние.доступ$002(i, 5);
}
}

Код есс похожие, лишь способы именуются доступа$0, доступ к$1, а 2-ой возвращает void. Все станет еще проще, раз вы удалите слово private: тогда способы доступа не будет неотклонимым, и поля могут быть доступны впрямую.
Компилятор есс во всех этих вариантах будет 1-ый вызов способа read, а потом добавить либо вычесть единицу и вызвать способ write. К примеру, скомпилировать последующий код:
public class Outer {
private int a;

статический вложенный класс {
void inc(наружное я) {
я.a++;
}
}
}
Javac будет дано приблизительно последующее:
public class Outer {
private int a;

static int access$008(Внешний obj) {
return obj.a++;
}
}

класс Outer$вложенных {
void inc(наружное я) {
Внешние.доступ$008(я);
}
}
Схожее поведение наблюдается, когда декремент (имя способа закончится на 10), и когда предынкрементным и преддекрементное (04 и 06). Любопытно, что javac ведет себя умнее, в поле шаг. Раз кому-то любопытно, где Нечетные числа, они будут употребляться для прямого доступа к защищенным полям родительского наружного класса (к примеру, Внешний.супер.x = 2, Не имею, где это может понадобиться!).
В javac 1.8 эта функциональность была нарушена, и кажется, что случаем: соответственный код находится в компилятор начального. Особый способ был сотворен, даже раз вы недосягаемы для строкового поля употребляется +=. Кстати, интересно, что javac 1.7 вел себя еще умнее, генерирующая особые способы для хоть какой операции присваивания+=,<<=, и так дальше (правая часть была рассчитана и передается сгенерированный способ-это отдельная функция).
Компилятор есс расслабленно переносит конфликтов, просто повышение счетчика до тех пор, пока вы отыскать свободное имя способа. Раз программер будет сделать способ, с соответственной подписью (к примеру, доступ$000, никогда, никогда не делать!), javac откажется компилировать файл с сообщением «знак (способ) конфликты с компилятором-синтезированный блок знак в классе».
Как сделать конструктор, который является точно, подпись не конфликтует с существующими? При проектировании объекта нужно вызвать его конструктор. Наиболее увлекательная ситуация является внедрение личного конструктора. При попытке вызова недоступен, способ делает вспомогательный статический способ, который имеет те же характеристики и тип возвращаемого значения, лишь добавив доп параметр, чтоб сдать объект. Javac для данной цели делает новейший класс! Таковым образом, компиляторы генерируют новейшие neprijatnyj конструктор, который вызывает надлежащие личным. Разглядим этот код:
public class Outer {
личные Outer() {}

статический вложенный класс {
void create() {
new Outer();
}
}
}
При компиляции будет сотворен не лишь Outer.class и Outer$Nested.class но другого класса Outer$1.class. Компилятор есс решил обойтись без экстра-класса и добавить фиктивный параметр, чтоб один и тот же класс:
public class Outer {
личные Outer() {}

Наружный(Внешний игнорировать) {
this();
}
}

класс Outer$вложенных {
void create() {
новейший Внешний (наружный)null);
}
}

В случае конфликта с имеющейся дизайн-добавлены новейшие характеристики манекена. Код, сгенерированный компилятором смотрится так:
public class Outer {
личные Outer() {}

Внешняя(Наружная$1 ignore) {
this();
}
}

класс Наружная$1 {} // этот класс не конструктор, даже личные, не инстанцировать

класс Outer$вложенных {
void create() {
новейший наружный (Внешний$1)null);
}
}

Решение просто в том смысле, что конфликт в конструкторе подписи не гарантируется. К примеру, у вас есть три конструктора:
личные Outer() {}
отдельный наружный(Внешний i1) {}
отдельный наружный(Внешний i1, наружный i2) {}

Раз каждый из их будет применять вложенный класс, есс будет сделать три новейших, три, четыре и 5 наружных характеристик.
Для заслуги этого внутреннего класса, компилятор добавляет новейший final-поле (традиционно с именованием$0), который содержит ссылку на окружающий класс. Проходя ссылки на объект наружного класса
Внутренние классы (в том числе локальные и анонимные) привязан к определенным объектом наружного класса. Из-за этого, раз вы переопределяете способ вызывается конструктор родительского класса, это$0, для вас уже будет инициализирован, и вы можете получить доступ к полям и способам наружного класса. Раз вы примите этот обычный код:
public class Outer {
класс Nested {}

void test() {
новейший вложенный();
}
}
Компиляторы (тут поведение есс и javac, кажется) сделает таковой код (помните, что я вручную б-код навести порядок было понятнее):
public class Outer {
void test() {
новейший Внешний$вложенных(this);
}
}

класс Outer$вложенных {
окончательный наружный this$0;

Внешний$вложенных(Внешний obj) {
это.это$0 = obj;
super();
}
}
Любопытно, это присвоение$0 происходит до вызова конструктора родительского класса. В каждый конструктор будет добавлен соответственный параметр. В обыденный Java-код, Вы не сможете присвоить значение в поле, чтоб завершить родительский конструктор, но б-код не вмешиваться.
Раз вы создаете конфликт по именам, начиная с вложенным классом поле с именованием$0 (никогда, никогда не делать!), это дозволит не путать составители: они будут призывать их внутреннее поле это$0$.
Раз оказалось, что в остальных null, исключение будет происходить еще до сотворения вложенного объекта. Неплохой для вас необходимо попасть в это место с NullPointerException. Составители опять предстоит выбраться: они вставляют липовый вызов, делая таковой код:
public class Outer {
void test(наружные остальные) {
остальных.getClass();
новейший Внешний$вложенных(остальных);
}
}
Вызов getClass() сейф: для хоть какого объекта обязано завершиться удачно и занимает чрезвычайно не достаточно времени. Как правило, виртуальная машинка сама по для себя гарантирует, что Вы не razmenjivali null, но это на самом деле разыменования не будет, раз вы используете внешнюю класса, вложенный в объект, который может произойти существенно позднее либо не произойти вообщем. Язык Java дозволяет создавать экземпляр внутреннего класса, не лишь на базе этого, но основанные на иной объект того же типа:
public class Outer {
класс Nested {}

void test(наружные остальные) {
остальных.новейший вложенный();
}
}
В данной связи возникает увлекательный момент: опосля того, как все, иной может быть null.
В качестве примера можно разглядеть это: Раз уровень вложенности классов больше, чем один, то в большинстве новейшие внутренние переменные:$1 и так дальше.
public class Outer {
класс Nested {
класс SubNested {
{test();}
}
}

void test() {
новейший вложенный().новейшие SubNested();
}
}
Тут javac будет сделать приблизительно таковой код:
Но трибунал, как правило, нежданно, сгенерированный доступа-способ: public class Outer {
void test() {
Внешний$вложенных tmp = new Outer$вложенных(this);
tmp.getClass(); // очевидно очень, но хорошо
новейший Внешний$вложенных$SubNested(tmp);
}
}

класс Outer$вложенных {
окончательный наружный this$0;

Внешний$вложенных(Внешний obj) {
это.это$0 = obj;
super();
}
}

класс Outer$вложенных$SubNested {
итоговый наружный$this вложенный$1;

Внешний$вложенных$SubNested(Внешний$вложенных obj) {
это.этот$1 = obj;
super();
это.это$1.это$0.test();
}
}
Вызов getClass() может быть изъята, так как мы лишь что сделали этот объект, но компилятор не тревожить.
С иной стороны, есс-поразмыслил priispolzovanii obj заместо того, чтоб доступ к этому полю.это$1. класс Outer$вложенных {
окончательный наружный this$0;

Внешний$вложенных(Внешний obj) {
это.это$0 = obj;
super();
}

статический наружный доступ$0(Внешний$вложенных obj) {
return obj.это$0;
}
}

класс Outer$вложенных$SubNested {
итоговый наружный$this вложенный$1;

Внешний$вложенных$SubNested(Внешний$вложенных obj) {
это.этот$1 = obj;
super();
Внешний$Вложенных.доступ$0(obj).test();
}
}
Чрезвычайно удивительно, беря во внимание, что это$0 имеет свой флаг.
Выводы
Вложенные классы представляют некие головная боль для компиляторов. Естественно, современные виртуальные машинки практически постоянно thinline, но все эти способы требуют больше памяти, надувается бассейн константы класса, что расширяет стек-следы и добавляет доп шаги при отладке. Не брезгуйте доступа package-private: в этом случае компилятор будет делать без автогенераторного способы.
Различные компиляторы могут генерировать чрезвычайно различные Кодекса в схожих ситуациях: даже количество сделанных классов могут различаться. habrahabr.ru Раз вы пишете инструменты для анализа б-кода, нужно разглядеть поведение различных компиляторов.