В программировании на Java, если член изменяется с использованием ключевого слова Private, можно использовать только класс, в котором находится этот член, и методы этого класса, и никакой другой класс не может получить доступ к этому закрытому члену.
Основные функции модификатора Private описаны выше. Сегодня мы изучим сбой функции Private.
Внутренние классы Java
Я считаю, что многие люди использовали внутренние классы в Java. Java позволяет определять один класс внутри другого класса. Класс внутри класса является внутренним классом, также называемым вложенным классом. Простую реализацию внутреннего класса можно скопировать следующим образом:
класс OuterClass {
класс ВнутреннийКласс {
}
}
Сегодняшний вопрос связан с внутренними классами Java и включает в себя только часть знаний о внутренних классах, связанных с исследованием этой статьи. В последующих статьях будут представлены конкретные статьи о внутренних классах Java.
Первый раз неудача?
Сценарий, который мы часто используем в программировании, — это доступ к закрытым переменным-членам или методам внешнего класса во внутреннем классе. Это нормально. Реализовано как следующий код.
Скопируйте код кода следующим образом:
общественный класс OuterClass {
частный язык String = "en";
частная строка региона = "США";
общественный класс InnerClass {
общественный недействительный printOuterClassPrivateFields () {
Строковые поля = "language=" + язык + ";region=" + регион;
System.out.println(поля);
}
}
public static void main(String[] args) {
OuterClass внешний = новый OuterClass();
OuterClass.InnerClass внутренний = внешний.новый InnerClass();
внутренний.printOuterClassPrivateFields();
}
}
Почему это не правда, что доступ к приватно-модифицированным членам может получить только тот класс, который они представляют? Действительно ли частное недействительно?
Компилятор вызывает проблемы?
Давайте воспользуемся командой javap для просмотра двух сгенерированных файлов классов.
Код копии результата декомпиляции OuterClass выглядит следующим образом:
15:30 $ javap -c Внешнийкласс
Скомпилировано из "OuterClass.java"
публичный класс OuterClass расширяет java.lang.Object{
общественный OuterClass();
Код:
0: aload_0
1: вызвать специальный #11 //Метод java/lang/Object."<init>":()V;
4: загрузка_0
5: ldc #13 //Строка en;
7: putfield #15 //Язык поля:Ljava/lang/String;
10: aload_0
11: ldc #17 //Строка США;
13: putfield #19 //Область поля:Ljava/lang/String;
16: возвращение
public static void main(java.lang.String[]);
Код:
0: новый #1 //класс OuterClass;
3: дубль
4: вызвать специальный #27 //Метод "<init>":()V;
7: astore_1
8: новый #28; //класс OuterClass$InnerClass;
11: дуп
12: загрузка_1
13: дуп
14: вызвать виртуальный #30; //Метод java/lang/Object.getClass:()Ljava/lang/Class;
17: Поп
18: вызвать специальный #34; //Метод OuterClass$InnerClass."<init>":(LOuterClass;)V
21: astore_2
22: загрузка_2
23: вызвать виртуальный #37 //Метод OuterClass$InnerClass.printOuterClassPrivateFields:()V;
26: возвращение
статический доступ java.lang.String$0(OuterClass);
Код:
0: aload_0
1: getfield #15 //Язык поля:Ljava/lang/String;
4: возврат
статический java.lang.String access$1(OuterClass);
Код:
0: aload_0
1: getfield #19 //Область поля:Ljava/lang/String;
4: возврат
}
Хм? Нет, мы не определяли эти два метода в OuterClass.
статический доступ java.lang.String$0(OuterClass);
Код:
0: aload_0
1: getfield #15 //Язык поля:Ljava/lang/String;
4: возврат
статический java.lang.String access$1(OuterClass);
Код:
0: aload_0
1: getfield #19 //Область поля:Ljava/lang/String;
4: возврат
}
Судя по приведенным комментариям, access$0 возвращает атрибут языка внешнего класса; доступ $1 возвращает атрибут региона внешнего класса; Оба метода принимают экземпляры OuterClass в качестве параметров. Почему создаются эти два метода и что они делают? Давайте посмотрим на результаты декомпиляции внутреннего класса и узнаем.
Результаты декомпиляции OuterClass$InnerClass
Скопируйте код кода следующим образом:
15:37 $ javap -c OuterClass/$InnerClass
Скомпилировано из "OuterClass.java"
публичный класс OuterClass$InnerClass расширяет java.lang.Object{
окончательный OuterClass this$0;
общественный OuterClass$InnerClass(OuterClass);
Код:
0: aload_0
1: загрузка_1
2: putfield #10 //Поле $0:LOuterClass;
5: загрузка_0
6: вызвать специальный #12 //Метод java/lang/Object."<init>":()V;
9: возвращение
общественный недействительный printOuterClassPrivateFields ();
Код:
0: новый #20; //класс java/lang/StringBuilder;
3: дубль
4: ldc #22; //Строковый язык=
6: вызвать специальный #24; //Метод java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
9: загрузка_0
10: getfield #10 //Поле $0:LOuterClass;
13: ignorestatic #27; //Метод OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
16: вызвать виртуальный #33; //Метод java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #37 //Строка ;регион=
21: вызвать виртуальный #33; //Метод java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: aload_0
25: getfield #10 //Поле $0:LOuterClass;
28: ignorestatic #39; //Метод OuterClass.access$1:(LOuterClass;)Ljava/lang/String;
31: вызвать виртуальный #33; //Метод java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: вызвать виртуальный # 42; //Метод java/lang/StringBuilder.toString:()Ljava/lang/String;
37: astore_1
38: getstatic #46; //Поле java/lang/System.out:Ljava/io/PrintStream;
41: загрузка_1
42: вызвать виртуальный #52; //Метод java/io/PrintStream.println:(Ljava/lang/String;)V
45: возвращение
}
Следующий код вызывает код доступа$0, целью которого является получение частного атрибута языка OuterClass.
Скопируйте код кода следующим образом:
13: ignorestatic #27; //Метод OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
Следующий код вызывает код доступа$1, целью которого является получение частного атрибута региона OuterClass.
Скопируйте код кода следующим образом:
28: ignorestatic #39; //Метод OuterClass.access$1:(LOuterClass;)Ljava/lang/String;
Примечание. При создании внутреннего класса ссылка на внешний класс будет передана и использована как атрибут внутреннего класса, поэтому внутренний класс будет содержать ссылку на свой внешний класс.
this$0 — это ссылка на внешний класс, хранящаяся во внутреннем классе. Ссылка передается и назначается через метод конструктора.
Скопируйте код кода следующим образом:
окончательный OuterClass this$0;
общественный OuterClass$InnerClass(OuterClass);
Код:
0: aload_0
1: загрузка_1
2: putfield #10 //Поле $0:LOuterClass;
5: загрузка_0
6: вызвать специальный #12 //Метод java/lang/Object."<init>":()V;
9: возвращение
краткое содержание
Эта часть Private кажется недействительной, но на самом деле она не является недействительной, поскольку, когда внутренний класс вызывает частное свойство внешнего класса, его реальное выполнение заключается в вызове статического метода свойства, сгенерированного компилятором (т.е. доступа $0, access$1 и т. д.), чтобы получить значения этих атрибутов. Это все специальная обработка компилятором.
На этот раз не получится?
Если описанный выше метод записи используется очень часто, то этот метод записи используется редко, но его можно использовать.
Скопируйте код кода следующим образом:
общественный класс AnotherOuterClass {
public static void main(String[] args) {
InnerClass внутренний = новый AnotherOuterClass().new InnerClass();
System.out.println("InnerClass Filed = " +inner.x);
}
класс ВнутреннийКласс {
частный интервал х = 10;
}
}
Как указано выше, используйте javap для декомпиляции и просмотра. Но на этот раз давайте посмотрим на результаты InnerClass. Скопируйте код. Код выглядит следующим образом:
16:03 $ javap -c AnotherOuterClass/$InnerClass
Скомпилировано из "AnotherOuterClass.java"
класс AnotherOuterClass$InnerClass расширяет java.lang.Object{
окончательный AnotherOuterClass this$0;
ДругойВнешнийКласс$ВнутреннийКласс(ДругойВнешнийКласс);
Код:
0: aload_0
1: загрузка_1
2: putfield #12 //Поле $0:LAnotherOuterClass;
5: загрузка_0
6: вызвать специальный #14 //Метод java/lang/Object."<init>":()V;
9: загрузка_0
10: два нажатия 10
12: поле ввода № 17 // Поле x:I;
15: возвращение
статический int access$0(AnotherOuterClass$InnerClass);
Код:
0: aload_0
1: получить поле № 17 // Поле x: I;
4: возвращение
}
Это произошло снова, и компилятор автоматически сгенерировал бэкдор-метод access$0 для получения частного атрибута один раз и получения значения x.
Код копии результата декомпиляции AnotherOuterClass.class выглядит следующим образом:
16:08 $ javap -c ДругойOuterClass
Скомпилировано из "AnotherOuterClass.java"
публичный класс AnotherOuterClass расширяет java.lang.Object{
общественный AnotherOuterClass();
Код:
0: aload_0
1: вызвать специальный #8; //Метод java/lang/Object."<init>":()V;
4: возвращение
public static void main(java.lang.String[]);
Код:
0: новый #16; //класс AnotherOuterClass$InnerClass;
3: дубль
4: новый #1 //класс AnotherOuterClass;
7: дубль
8: вызвать специальный #18 //Метод "<init>":()V;
11: дуп
12: вызвать виртуальный # 19; //Метод java/lang/Object.getClass:()Ljava/lang/Class;
15: поп
16: вызвать специальный #23; //Метод AnotherOuterClass$InnerClass."<init>":(LAnotherOuterClass;)V
19: astore_1
20: getstatic #26; //Поле java/lang/System.out:Ljava/io/PrintStream;
23: новый #32 //класс java/lang/StringBuilder;
26: дуп
27: ldc #34 // Строка InnerClass Filed =
29: вызвать специальный #36; //Метод java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
32: загрузка_1
33: ignorestatic #39; //Метод AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I
36: вызвать виртуальный # 43; //Метод java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
39: вызвать виртуальный # 47; //Метод java/lang/StringBuilder.toString:()Ljava/lang/String;
42: вызвать виртуальный #51; //Метод java/io/PrintStream.println:(Ljava/lang/String;)V
45: возвращение
}
Этот вызов представляет собой операцию внешнего класса по получению частного свойства x через экземпляр внутреннего класса. Скопируйте код следующим образом:
33: ignorestatic #39; //Метод AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I
Еще одно резюме
Среди них в официальной документации Java есть такое предложение. Скопируйте код следующим образом:
если член или конструктор объявлен закрытым, то доступ разрешен тогда и только тогда, когда он происходит внутри тела класса верхнего уровня (§7.6), который включает объявление члена или конструктора.
Это означает, что если для членов и конструкторов (внутреннего класса) установлены частные модификаторы, доступ разрешен тогда и только тогда, когда их внешний класс.
Как предотвратить доступ посторонних к частным членам внутренних классов
Я полагаю, что после прочтения двух предыдущих частей вы почувствуете, что внешнему классу очень сложно получить доступ к закрытым членам внутреннего класса. Кому хочется, чтобы компилятор был «любопытным»? На самом деле, это возможно? сделанный. То есть использовать анонимные внутренние классы.
Поскольку тип объекта mRunnable — Runnable, а не тип анонимного внутреннего класса (который мы не можем получить обычным способом), и в Runanble нет атрибута x, mRunnable.x не разрешен.
Скопируйте код кода следующим образом:
общественный класс PrivateToOuter {
Runnable mRunnable = новый Runnable() {
частный интервал х = 10;
@Override
общественный недействительный запуск () {
System.out.println(x);
}
};
public static void main(String[] args){
PrivateToOuter p = новый PrivateToOuter();
//System.out.println("anonymous class Private Filed="+ p.mRunnable.x); //не разрешено;
p.mRunnable.run(); // разрешено
}
}
Итоговое резюме
В этой статье свойство Private на первый взгляд кажется недопустимым, но на самом деле это не так. Вместо этого частные свойства получаются косвенными методами при вызове.
Внутренние классы Java при создании содержат приложения для внешних классов, а C++ этого не делает. В этом отличие от C++.