Почему внешние классы Java могут обращаться к закрытым членам внутреннего класса?

177

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

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??
    }
}

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

Хариш
источник
Этот вопрос смущал меня довольно долго, пока я не увидел комментарий ... который объясняет, почему я не могу получить доступ к xx.x на моей машине ...
Ван Шэн,
4
Комментарии смутили меня, я запускаю приведенный выше код в Java 8, он компилируется и запускается. Доступ к xx.x
Леон
Хариш, не могли бы вы, пожалуйста, не принять принятый ответ (который не отвечает на заданный вами вопрос) и вместо этого принять ответ Мартина Андерссона внизу, который очень подробно отвечает на него?
временное_пользователь_имение
FWIW этот синтаксис абсолютно ужасен:new ABC().new XYZ()
Джош М.

Ответы:

80

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

  1. Некоторая часть функциональности в вашем внешнем классе была бы наиболее понятна, если бы она была реализована в отдельном классе.
  2. Несмотря на то, что он находится в отдельном классе, функциональность очень тесно связана с тем, как работает внешний класс.

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

Калеб Бразе
источник
217
Этот ответ объясняет, почему вложенные классы имеют доступ к закрытым членам своего внешнего класса. Но вопрос в том, почему внешний класс имеет доступ к закрытым членам вложенных классов.
Андрей
13
Просто добавьте «и наоборот». Учитывая эти требования, внутренние классы имеют полный доступ к своему внешнему классу, и теперь он отвечает на вопрос.
антропомо
13
Этот не правильный ответ на эту проблему, здесь делает: stackoverflow.com/questions/19747812/…
Колин Су
4
@anthropomo: нет, это не так. Оба требования вполне выполнимы без внешнего класса, имеющего доступ к закрытым членам внутренних классов.
ИЛИ Mapper
Один хороший пример, где это особенно полезно, - это шаблон Builder, stackoverflow.com/a/1953567/1262000 . Родительскому классу просто нужен конструктор, который принимает Builder и получает доступ ко всем его переменным-членам. В противном случае вам понадобится приватный конструктор в родительском классе со всеми приватными переменными-членами.
vikky.rk
62

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

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

    private static 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
    }
}
Ich
источник
Это блестящий фрагмент кода. Как раз то, что мне было нужно. Спасибо!
Кевинарпе
Пожалуйста, предоставьте код, который может быть запущен. Более того, пожалуйста, причина, по которой закрытая переменная не может быть разрешена для доступа.
androidyue
7
Но тогда ... внутренний класс является анонимным. Вы не можете создать несколько экземпляров этого внутреннего класса или использовать этот внутренний класс для любых объявлений переменных и т. Д.
ИЛИ Mapper
@OR Mapper, поэтому, даже если xздесь публикуется, это запрещено mMember.x.
MAC
54

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

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

Тило
источник
17

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

В нем говорится, что есть определение частного определения области JLS - Определение доступности :

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

Колин Су
источник
Я думаю, что этот фрагмент может приятно ответить на мой вопрос.
Шен
5

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

В отличие от 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);
    }
}
thomasfr
источник
1
Хороший комментарий, но нет ответа.
выступление
@cerving Это на самом деле единственный ответ, который позволил мне увидеть реальное практическое использование этого странного дизайнерского решения. Вопрос заключался в том, почему это было решено именно таким образом, и это отличная аргументация - демонстрация разницы между тем, что вы могли бы хотеть, чтобы внешний класс имел доступ во внутреннем классе, и тем, что вы хотите, чтобы другие несвязанные классы имели доступ.
et_l
3

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

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

TofuBeer
источник
3

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

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

abyx
источник
2
Внешний класс может также обращаться к закрытым членам статических внутренних классов, так что это не имеет ничего общего со статическим . Вы говорите: «Нет смысла, чтобы классы могли видеть внутреннюю работу друг друга», но это не обязательно так - что, если для внутреннего класса имеет смысл видеть внутреннюю работу внешнего класса, а не наоборот. Versa?
ИЛИ Mapper
3

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

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

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

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

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

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

Мартин Андерссон
источник
1
Это должен быть принятый ответ. Так ясно и тщательно. Вместо этого принятый ответ даже не касается вопроса.
временное_пользователь_имение
IMO, это все еще не отвечает на вопрос, почему мы не можем легко добавить приватные поля во внутренний класс, к которому внешний класс не может получить доступ напрямую. Если я не ошибаюсь, это разрушает один из основных случаев для внутренних классов - создание недолговечных «структуроподобных» неизменяемых типов. FWIW C # радостно поддерживает это: repl.it/repls/VengefulCheeryInverse
Джош М.
-1

Внутренний класс рассматривается как атрибут Внешнего класса. Следовательно, независимо от того, является ли переменная экземпляра класса Inner частной или нет, Outer класс может получить доступ без каких-либо проблем, точно так же, как и к другим его закрытым атрибутам (переменным).

class Outer{

private int a;

class Inner{
private int b=0;
}

void outMethod(){
a = new Inner().b;
}
}
MonMoonkey
источник
-2

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

aberrant80
источник
2
Вопрос не в том, могут ли члены ABCкласса получать доступ к классам, вложенным в ABCкласс, а в том , почему они могут получить доступ к закрытым членам классов, вложенных в ABCкласс в Java.
ИЛИ Mapper
Я ответил на вопрос в тот же день, когда его спросили. Кто-то отредактировал вопрос 2 года спустя, а отрицательный ответ появился через 3 года. Я почти уверен, кто бы ни редактировал вопрос, он слишком сильно изменил формулировку вопроса.
aberrant80