Почему внешние классы Java получают доступ к закрытым членам внутреннего класса?

Я заметил, что внешние классы могут обращаться к внутренним классам переменных частного экземпляра. Как это возможно? Вот пример кода, демонстрирующий то же самое:

class ABC{
    class XYZ{
        private int x=10;
    }

    public static void main(String... args){
        ABC.XYZ xx = new ABC().new XYZ();
        System.out.println("Hello :: "+xx.x); ///Why is this allowed??
    }
}

Почему такое поведение разрешено?

Ответ 1

Внутренний класс - это просто способ четко отделить некоторые функциональные возможности, которые действительно относятся к исходному внешнему классу. Они предназначены для использования, если у вас есть 2 требования:

  • Некоторая часть функциональности вашего внешнего класса была бы наиболее ясна, если бы она была реализована в отдельном классе.
  • Несмотря на то, что это в отдельном классе, функциональность очень тесно связана с тем, что работает внешний класс.

Учитывая эти требования, внутренние классы имеют полный доступ к своему внешнему классу. Поскольку они в основном являются членами внешнего класса, имеет смысл, что они имеют доступ к методам и атрибутам внешнего класса, включая рядовых.

Ответ 2

Внутренний класс (для целей контроля доступа) считается частью содержащего класса. Это означает полный доступ ко всем частным лицам.

Как это реализовано, используются синтетические методы, защищенные пакетом. Внутренний класс будет скомпилирован в отдельный класс в одном пакете (ABC $XYZ). JVM не поддерживает этот уровень изоляции напрямую, так что на уровне байт-кода ABC $XYZ будут иметь защищенные пакетом методы, которые внешний класс использует для доступа к приватным методам/полям.

Ответ 3

Если вам нравится скрывать частные члены вашего внутреннего класса, вы можете определить интерфейс с публичными членами и создать анонимный внутренний класс, который реализует этот интерфейс. Пример ниже:

class ABC{
    private interface MyInterface{
         void printInt();
    }

    private MyInterface mMember = new MyInterface(){
        private int x=10;

        public void printInt(){
            System.out.println(String.valueOf(x));
        }
    }

    public static void main(String... args){
        System.out.println("Hello :: "+mMember.x); ///not allowed
        mMember.printInt(); // allowed
    }
}

Ответ 4

Там есть правильный ответ, похожий на другой вопрос: Почему доступ к закрытому члену вложенного класса осуществляется с помощью методов входящего класса?

В нем указано определение частного охвата JLS - Определение доступности:

В противном случае, если член или конструктор объявлен приватным, , тогда доступ разрешен только в том случае, если он встречается внутри тела класса верхнего уровня (§7.6), который включает объявление члена или конструктора.

Ответ 5

Важным вариантом использования IMHO для внутренних классов является шаблон factory. Закрывающий класс может подготовить экземпляр внутреннего класса без ограничений доступа и передать экземпляр во внешний мир, где будет выполняться конфиденциальный доступ.

В противовес abyx объявление статического класса не изменяет ограничений доступа к окружающему классу, как показано ниже. Также работают ограничения доступа между статическими классами в одном классе. Я был удивлен...

class MyPrivates {
    static class Inner1 { private int test1 = 2; }
    static class Inner2 { private int test2 = new Inner1().test1; }

    public static void main(String[] args) {
        System.out.println("Inner : "+new Inner2().test2);
    }
}

Ответ 6

Ограничения доступа выполняются для каждого класса. Невозможно, чтобы метод, объявленный в классе, не мог получить доступ ко всем членам экземпляра/класса. Это означает, что внутренние классы также имеют неограниченный доступ к членам внешнего класса, а внешний класс имеет свободный доступ к членам внутреннего класса.

Помещая класс внутри другого класса, вы делаете его жестко привязанным к реализации, и все, что является частью реализации, должно иметь доступ к другим частям.

Ответ 7

Логика внутренних классов заключается в том, что если вы создаете внутренний класс во внешнем классе, это потому, что им нужно будет поделиться несколькими вещами, и, следовательно, имеет смысл для них иметь больше гибкости, чем "регулярные", классы имеют.

Если в вашем случае нет смысла, чтобы классы могли видеть внутреннюю работу друг друга, что в основном означает, что внутренний класс мог бы просто быть обычным классом, вы можете объявить внутренний класс как static class XYZ. Использование static означает, что они не будут делить состояние (и, например, new ABC().new XYZ() не будет работать, и вам нужно будет использовать new ABC.XYZ().
Но если это так, вы должны подумать о том, должен ли XYZ быть внутренним классом и, возможно, он заслуживает собственного файла. Иногда имеет смысл создать статический внутренний класс (например, если вам нужен небольшой класс, который реализует интерфейс, используемый вашим внешним классом, и это нигде не поможет). Но примерно в половине случаев он должен был стать внешним классом.

Ответ 8

Thilo добавил хороший ответ для вашего первого вопроса "Как это возможно?". Я хочу немного рассказать о втором заданном вопросе: почему это поведение разрешено?

Для начала давайте просто совершенно ясно, что это поведение допускается не только для внутренних классов, которые по определению являются нестатическими вложенными типами. Такое поведение разрешено для всех вложенных типов, включая вложенные перечисления и интерфейсы, которые должны быть статическими и не могут иметь закрытый экземпляр. В принципе, модель упрощается до следующего утверждения: вложенный код имеет полный доступ к охватывающему код - и наоборот.

Итак, почему тогда? Я думаю, что пример лучше иллюстрирует эту точку.

Подумайте о своем теле и своем мозгу. Если вы вводите героин в руку, ваш мозг становится высоким. Если регион миндалины вашего мозга видит то, что, по его мнению, угрожает вашей личной безопасности, скажем, например, оса, он заставит ваше тело повернуться в другую сторону и бежать за холмами, если вы "не подумаете" дважды об этом.

Итак, мозг является неотъемлемой частью тела - и, как ни странно, наоборот. Использование контроля доступа между такими тесно связанными объектами утрачивает свою претензию на отношения. Если вам нужен контроль доступа, вам нужно отделить классы больше от действительно разных единиц. До тех пор они являются одной и той же единицей. Приводным примером для дальнейших исследований было бы посмотреть, как обычно реализуется Java Iterator.

Неограниченный доступ от включения кода во вложенный код делает его, по большей части, бесполезным для добавления модификаторов доступа в поля и методы вложенного типа. Это добавляет беспорядок и может обеспечить ложное чувство безопасности для новых пользователей языка программирования Java.

Ответ 9

Поскольку ваш метод main() находится в классе ABC, который может получить доступ к собственному внутреннему классу.