Пазл видимости внутреннего класса Java
Рассмотрим следующий случай:
public class A {
public A() { b = new B(); }
B b;
private class B { }
}
Из предупреждения в Eclipse я цитирую, что: java complier эмулирует конструктор A.B() синтетическим методом доступа. Я предполагаю, что компилятор теперь идет вперед и создает дополнительный конструктор "under water" для B.
Я чувствую, что это довольно странно: почему класс B не будет виден как a.k.o. поле в A?
И: означает ли это, что класс B больше не является закрытым во время выполнения?
И: почему поведение ключевого слова protected для класса B отличается?
public class A {
public A() { b = new B(); }
B b;
protected class B { }
}
Ответы
Ответ 1
Внутренние классы - это, по сути, взлом, введенный в Java 1.1. JVM на самом деле не имеет понятия внутреннего класса, поэтому компилятор должен его использовать. Компилятор генерирует класс B "снаружи" класса A, но в том же пакете, а затем добавляет к нему синтетические аксессоры/конструкторы, чтобы позволить A получить к нему доступ.
Когда вы предоставляете B защищенный конструктор, A может получить доступ к этому конструктору, поскольку он находится в том же пакете, без необходимости добавления синтетического конструктора.
Ответ 2
Я знаю, что этот вопрос сейчас почти три года, но я считаю, что часть вопроса все еще не ответила:
И: означает ли это, что класс B больше не является закрытым во время выполнения?
В заявлении Carlos Heubergers о подсказках skaffmans, класс B
по-прежнему private
для других классов в пакете.
Он, вероятно, прав для языка программирования Java, т.е. нельзя ссылаться на класс B
из другого класса. По крайней мере, не без использования рефлексии (с которой также можно получить доступ к частным членам класса извне), но это еще одна проблема.
Но поскольку у JVM нет понятия внутреннего класса (как состояния скаффмана), я спросил себя, как на уровне байт-кода реализуется видимость "доступный только одним классом". Ответ: он вообще не реализован, поскольку JVM внутренний класс выглядит как обычный пакетный частный пакет. Это, если вы пишете байт-код для себя (или модифицируете один из них, сгенерированный компилятором), вы можете без проблем получить доступ к классу B
.
Вы также можете получить доступ ко всем синтетическим методам доступа всех классов в одном пакете. Поэтому, если вы присваиваете значение частному полю класса A
в методе класса B
, в классе A
генерируется синтетический метод доступа с видимостью по умолчанию (то есть с закрытым пакетом), что называется access$000
), который устанавливает для вас значение. Предполагается, что этот метод вызывается только из класса B
(и действительно его можно вызывать только там, используя язык Java). Но с точки зрения JVM это просто метод, как любой другой, и может быть вызван любым классом.
Итак, чтобы ответить на вопрос:
- С точки зрения языков Java класс
B
является закрытым.
- С точки зрения JVM класс
B
(или лучше: class A$B
) не является приватным.
Ответ 3
Доступ к class B
и его конструктору не обязательно должен быть одним и тем же. У вас может быть закрытый внутренний класс с конструктором scope-scope, и это то, что я обычно делаю.
public class A {
public A() { b = new B(); }
B b;
private class B {
B() { }
}
}
Ответ 4
Вам нужно использовать
this.new B();