Пазл видимости внутреннего класса 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();