Чому зовнішні класи 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
    }
}

Це блискучий фрагмент коду. Тільки те, що мені було потрібно. Дякую!
kevinarpe

Будь ласка, надайте код, який можна було б запустити. Більше того, будь ласка, причину, через яку приватну змінну не можна було отримати доступ.
androidyue

7
Але тоді… внутрішній клас анонімний. Ви не можете створити кілька примірників цього внутрішнього класу, або використовувати цей внутрішній клас для будь-яких декларацій змінних і т.д.
АБО Mapper

@OR Mapper, тому навіть якщо xтут публічно, це не дозволяється mMember.x.
MAC

54

Внутрішній клас (для контролю доступу) вважається частиною класу, що містить. Це означає повний доступ до всіх приватних осіб.

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


17

На інше питання, подібне до цього, з’являється правильна відповідь: Чому приватний член вкладеного класу може отримати доступ методами класу, що додається?

В ньому йдеться про визначення приватного обстеження на JLS - Визначення доступності :

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


Я думаю, що цей фрагмент може чудово відповісти на моє запитання.
шень

5

Важливим випадком використання ІМХО для внутрішніх класів є заводський зразок. Клас, що додається, може підготувати екземпляр внутрішнього класу без обмежень доступу та передати екземпляр у зовнішній світ, де буде дотримано приватний доступ.

На відміну від абіксису, який оголошує клас статичним, не змінює обмеження доступу до класу, що додається , як показано нижче. Також діють обмеження доступу між статичними класами в одному класі, що обгороджує. Я був здивований ...

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);
    }
}

1
Приємний коментар, але відповіді немає.
1313

@cerving Це насправді єдина відповідь, яка дозволила мені побачити практичне використання цього інакше химерного дизайнерського рішення. Питання полягало в тому, чому було вирішено саме так, і це чудове міркування - демонстрація різниці між тим, що ви хочете, щоб зовнішній клас отримав доступ у внутрішньому класі, і тим, що ви хочете отримати доступ до інших непов'язаних класів.
et_l

3

Обмеження доступу здійснюються на основі кожного класу. Немає способу, щоб заявлений у класі метод не мав доступу до всіх членів екземпляра / класу. Це означає, що внутрішні класи також мають безперебійний доступ до членів зовнішнього класу, а зовнішній клас має безперешкодний доступ до членів внутрішнього класу.

Вводячи клас в інший клас, ви змушуєте його тісно прив'язатись до реалізації, і все, що є частиною реалізації, повинно мати доступ до інших частин.


3

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

Якщо у вашому випадку для класів немає сенсу бачити внутрішню роботу один одного - це, в основному, означає, що внутрішній клас міг просто бути звичайним класом, ви можете оголосити внутрішній клас як static class XYZ. Використання staticозначатиме, що вони не поділять стан (і, наприклад new ABC().new XYZ(), не працюватимуть, і вам потрібно буде скористатися new ABC.XYZ().
Але, якщо це так, ви повинні подумати про те, чи XYZповинен насправді бути внутрішнім класом, і що, можливо, він заслуговує свого Файл. Іноді має сенс створити статичний внутрішній клас (наприклад, якщо вам потрібен невеликий клас, який реалізує інтерфейс, який використовує ваш зовнішній клас, і це не буде корисно ніде більше). Але приблизно в половині часу це повинен був зробити зовнішній клас.


2
Зовнішній клас так само добре може отримати доступ до приватних членів статичних внутрішніх класів, тому це не має нічого спільного зі статичним . Ви говорите, що "немає сенсу для класів мати можливість бачити внутрішню роботу один одного", але це не обов'язково так - а якщо внутрішній клас має сенс бачити внутрішню роботу зовнішнього класу, але не віце- навпаки?
АБО Mapper

3

Тіло додав гарну відповідь на ваше перше запитання "Як це можливо?". Я хотів би трохи детальніше зупинитися на другому заданому питанні: Чому така поведінка дозволена?

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

Отже, чому тоді? Я думаю, що приклад ілюструє кращу точку.

Подумайте про своє тіло та свій мозок. Якщо ви вводите героїн в руку, ваш мозок стає високим. Якщо в районі міндалі вашого мозку побачите, що він вважає, це загроза вашій особистої безпеки, скажіть, наприклад, оса, він змусить ваше тіло повернутись навпаки і бігти за пагорби, не ви «думати» про це двічі.

Отже, мозок - це невід'ємна частина тіла - і, як не дивно, навпаки. Використання контролю доступу між такими тісно пов’язаними суб’єктами втрачає їх вимогу про відносини. Якщо вам потрібен контроль доступу, то вам потрібно більше розділити класи на справді окремі одиниці. До цього часу вони є одним і тим же підрозділом. Рушійним прикладом для подальших досліджень буде подивитися, як Iteratorзазвичай реалізується Java .

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


1
Це дійсно має бути прийнятою відповіддю. Так чітко і ретельно. Натомість прийнята відповідь навіть не стосується цього питання.
тимчасове_користувач

IMO це все ще не відповідає на питання, чому ми не можемо легко додати приватні поля до внутрішнього класу, до якого зовнішній клас не може отримати прямий доступ. Якщо я не помиляюся, це руйнує один з первинних випадків для внутрішніх класів - створення недовговічних "структурних", незмінних типів. FWIW C # із задоволенням підтримує це: repl.it/repls/VengefulCheeryInverse
Джош М.

-1

Внутрішній клас розглядається як атрибут зовнішнього класу. Таким чином, незалежно від змінної примірника Внутрішній клас приватної чи ні, Зовнішній клас може отримати доступ без будь-яких проблем, як і доступ до інших його приватних атрибутів (змінних).

class Outer{

private int a;

class Inner{
private int b=0;
}

void outMethod(){
a = new Inner().b;
}
}

-2

Тому що ваш main()метод знаходиться в ABCкласі, який може отримати доступ до власного внутрішнього класу.


2
Питання полягає не в тому, чи можуть члени ABCкласу отримати доступ до класів, вкладених у ABCклас, а в тому, чому вони можуть отримати доступ до приватних членів класів, які вкладені в ABCкласі на Java.
АБО Mapper

Я відповів на запитання того ж дня, коли його задали. Хтось відредагував це питання через 2 роки, а низовий запис прийшов через 3 роки. Я впевнений, що той, хто редагував питання, занадто сильно змінив формулювання питання.
aberrant80
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.