En la programación Java, si un miembro se modifica usando la palabra clave privada, solo se pueden usar la clase en la que se encuentra el miembro y los métodos de esta clase, y ninguna otra clase puede acceder a este miembro privado.
Las funciones básicas del modificador privado se describen arriba. Hoy estudiaremos la falla de la función privada.
Clases internas de Java
Creo que mucha gente ha usado clases internas en Java. Java permite definir una clase dentro de otra clase. La clase dentro de la clase es una clase interna, también llamada clase anidada. Se puede copiar una implementación de clase interna simple de la siguiente manera:
clase clase exterior {
clase Clase Interna{
}
}
La pregunta de hoy está relacionada con las clases internas de Java y solo involucra parte del conocimiento de las clases internas relacionado con la investigación de este artículo. Se presentarán artículos posteriores específicos sobre las clases internas de Java.
¿Fracaso por primera vez?
Un escenario que usamos a menudo en programación es acceder a variables miembro privadas o métodos de una clase externa en una clase interna. Implementado como el siguiente código.
Copie el código de código de la siguiente manera:
clase pública clase exterior {
idioma de cadena privada = "es";
región de cadena privada = "EE. UU.";
clase pública clase interna {
public void printOuterClassPrivateFields() {
Campos de cadena = "idioma=" + idioma + ";región=" + región;
System.out.println(campos);
}
}
público estático vacío principal (String [] argumentos) {
OuterClass exterior = nueva OuterClass();
OuterClass.InnerClass interior = exterior.new InnerClass();
inside.printOuterClassPrivateFields();
}
}
¿Por qué es esto? ¿No es cierto que solo la clase que representan puede acceder a los miembros modificados de forma privada? ¿Es privado realmente inválido?
¿El compilador está causando problemas?
Usemos el comando javap para ver los dos archivos de clase generados.
El código de copia del resultado de la descompilación de OuterClass es el siguiente:
15:30 $ javap -c clase externa
Compilado de "OuterClass.java"
la clase pública OuterClass extiende java.lang.Object{
clase externa pública();
Código:
0: carga_0
1: invokespecial #11; //Método java/lang/Object."<init>":()V
4: carga_0
5: ldc #13; //Cadena es
7: putfield #15; //Campo idioma:Ljava/lang/String;
10: carga_0
11: ldc #17; //Cadena EE. UU.
13: putfield #19; //Región del campo:Ljava/lang/String;
16: regreso
principal vacío estático público (java.lang.String []);
Código:
0: nuevo #1; //clase Clase exterior
3: doble
4: invocar especial #27; //Método "<init>":()V
7: astore_1
8: nuevo #28; //clase ClaseExterna$ClaseInterior
11: doble
12: carga_1
13: doble
14: invocarvirtual #30; //Método java/lang/Object.getClass:()Ljava/lang/Class;
17: pop
18: invocar especial #34; //Método OuterClass$InnerClass."<init>":(LOuterClass;)V
21: astore_2
22: carga_2
23: invocarvirtual #37; //Método OuterClass$InnerClass.printOuterClassPrivateFields:()V
26: regreso
acceso estático java.lang.String$0(OuterClass);
Código:
0: carga_0
1: getfield #15; //Campo idioma:Ljava/lang/String;
4: un regreso
acceso estático java.lang.String$1(OuterClass);
Código:
0: carga_0
1: getfield #19; //Campo región:Ljava/lang/String;
4: un regreso
}
¿Eh? No, no definimos estos dos métodos en OuterClass
acceso estático java.lang.String$0(OuterClass);
Código:
0: carga_0
1: getfield #15; //Campo idioma:Ljava/lang/String;
4: un regreso
acceso estático java.lang.String$1(OuterClass);
Código:
0: carga_0
1: getfield #19; //Campo región:Ljava/lang/String;
4: un regreso
}
A juzgar por los comentarios dados, access$0 devuelve el atributo de idioma de externalClass; access$1 devuelve el atributo de región de externalClass. Y ambos métodos aceptan instancias de OuterClass como parámetros. ¿Por qué se generan estos dos métodos y qué hacen? Echemos un vistazo a los resultados de la descompilación de la clase interna y lo sabremos.
Resultados de descompilación de OuterClass$InnerClass
Copie el código de código de la siguiente manera:
15:37 $ javap -c Clase exterior/$ Clase interior
Compilado de "OuterClass.java"
clase pública OuterClass$InnerClass extiende java.lang.Object{
final OuterClass este$0;
clase exterior pública $ clase interior (clase exterior);
Código:
0: carga_0
1: carga_1
2: putfield #10; //Campo esto$0:LOuterClass;
5: carga_0
6: invokespecial #12; //Método java/lang/Object."<init>":()V
9: regreso
printOuterClassPrivateFields() vacío público;
Código:
0: nuevo #20; //clase java/lang/StringBuilder
3: doble
4: ldc #22; //Idioma de cadena=
6: invokespecial #24; //Método java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
9: carga_0
10: getfield #10; //Campo esto$0:LOuterClass;
13: invokestatic #27; //Método OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
16: invocarvirtual #33; //Método java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #37; //Cadena ;región=
21: invocarvirtual #33; //Método java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: carga_0
25: getfield #10; //Campo esto$0:LOuterClass;
28: invokestatic #39; //Método OuterClass.access$1:(LOuterClass;)Ljava/lang/String;
31: invocarvirtual #33; //Método java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: invocarvirtual #42; //Método java/lang/StringBuilder.toString:()Ljava/lang/String;
37: astore_1
38: getstatic #46; //Campo java/lang/System.out:Ljava/io/PrintStream;
41: carga_1
42: invocarvirtual #52; //Método java/io/PrintStream.println:(Ljava/lang/String;)V
45: regreso
}
El siguiente código llama al código de acceso$0, cuyo propósito es obtener el atributo privado de idioma de OuterClass.
Copie el código de código de la siguiente manera:
13: invokestatic #27; //Método OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
El siguiente código llama al código de acceso$1, cuyo propósito es obtener el atributo privado de región de OutherClass.
Copie el código de código de la siguiente manera:
28: invokestatic #39; //Método OuterClass.access$1:(LOuterClass;)Ljava/lang/String;
Nota: Cuando se construye la clase interna, la referencia de la clase externa se pasará y se usará como un atributo de la clase interna, por lo que la clase interna contendrá una referencia a su clase externa.
this$0 es la referencia de clase externa mantenida por la clase interna. La referencia se pasa y asigna a través del método constructor.
Copie el código de código de la siguiente manera:
final OuterClass este$0;
clase exterior pública $ clase interior (clase exterior);
Código:
0: carga_0
1: carga_1
2: putfield #10; //Campo esto$0:LOuterClass;
5: carga_0
6: invokespecial #12; //Método java/lang/Object."<init>":()V
9: regreso
resumen
Esta parte de privado parece no ser válida, pero en realidad no lo es, porque cuando la clase interna llama a la propiedad privada de la clase externa, su ejecución real es llamar al método estático de la propiedad generada por el compilador (es decir, acceder $0, acceso$1, etc.) para obtener estos valores de atributos. Todo esto es un procesamiento especial por parte del compilador.
¿No funciona esta vez?
Si el método de escritura anterior se usa con mucha frecuencia, entonces este método de escritura rara vez se usa, pero se puede ejecutar.
Copie el código de código de la siguiente manera:
clase pública OtraClaseExterna {
público estático vacío principal (String [] argumentos) {
InnerClass interior = nueva Otra Clase Exterior().nueva Clase Interior();
System.out.println("InnerClass Filed = " + interior.x);
}
clase Clase Interna {
privado int x = 10;
}
}
Como arriba, use javap para descompilar y echar un vistazo. Pero esta vez echemos un vistazo a los resultados de InnerClass. Copie el código. El código es el siguiente.
16:03 $ javap -c OtraClaseExterna/$ClaseInterior
Compilado de "AnotherOuterClass.java"
clase OtraClaseExterna$ClaseInterior extiende java.lang.Object{
final Otra Clase Externa este $ 0;
OtraClaseExterna$ClaseInterior(OtraClaseExterna);
Código:
0: carga_0
1: carga_1
2: putfield #12; //Campo esto$0:LAnotherOuterClass;
5: carga_0
6: invokespecial #14; //Método java/lang/Object."<init>":()V
9: carga_0
10: biempuje 10
12: campo put #17; //Campo x:I
15: regreso
acceso int estático$0(AnotherOuterClass$InnerClass);
Código:
0: carga_0
1: obtener campo #17; //Campo x:I
4: retorno
}
Sucedió nuevamente, y el compilador generó automáticamente un método de puerta trasera acceso $0 para obtener el atributo privado una vez para obtener el valor de x.
El código de copia del resultado de la descompilación de AnotherOuterClass.class es el siguiente:
16:08 $ javap -c Otra clase externa
Compilado de "AnotherOuterClass.java"
la clase pública AnotherOuterClass extiende java.lang.Object{
pública OtraClaseExterna();
Código:
0: carga_0
1: invokespecial #8; //Método java/lang/Object."<init>":()V
4: regreso
principal vacío estático público (java.lang.String []);
Código:
0: nuevo #16; //clase OtraClaseExterna$ClaseInterior
3: doble
4: nuevo #1; //clase OtraClaseExterna
7: doble
8: invocar especial #18; //Método "<init>":()V
11: doble
12: invocarvirtual #19; //Método java/lang/Object.getClass:()Ljava/lang/Class;
15: pop
16: invokespecial #23; //Método OtraClaseExterna$InnerClass."<init>":(LAnotherOuterClass;)V
19: astore_1
20: getstatic #26; //Campo java/lang/System.out:Ljava/io/PrintStream;
23: nuevo #32; //clase java/lang/StringBuilder
26: doble
27: ldc #34; //Cadena InnerClass Archivada =
29: invokespecial #36; //Método java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
32: carga_1
33: invokestatic #39; //Método AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I
36: invocarvirtual #43; //Método java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
39: invocarvirtual #47; //Método java/lang/StringBuilder.toString:()Ljava/lang/String;
42: invocarvirtual #51; //Método java/io/PrintStream.println:(Ljava/lang/String;)V
45: regreso
}
Esta llamada es la operación de la clase externa para obtener la propiedad privada x a través de la instancia de la clase interna. Copie el código de la siguiente manera:
33: invokestatic #39; //Método AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I
Otro resumen
Entre ellos, la documentación oficial de Java tiene esta oración. Copie el código de la siguiente manera:
si el miembro o constructor se declara privado, entonces se permite el acceso si y sólo si ocurre dentro del cuerpo de la clase de nivel superior (§7.6) que incluye la declaración del miembro o constructor.
Esto significa que si los miembros y constructores (de la clase interna) están configurados con modificadores privados, se permite el acceso si y solo si su clase externa.
Cómo evitar que personas externas accedan a miembros privados de clases internas
Creo que después de leer las dos partes anteriores, sentirá que es muy difícil que la clase externa acceda a los miembros privados de la clase interna. ¿Quién quiere que el compilador sea "entrometido"? hecho. Eso es usar clases internas anónimas.
Dado que el tipo de objeto mRunnable es Runnable, no el tipo de clase interna anónima (que normalmente no podemos obtener), y no hay ningún atributo x en Runanble, mRunnable.x no está permitido.
Copie el código de código de la siguiente manera:
clase pública PrivateToOuter {
Ejecutable mRunnable = nuevo Ejecutable(){
privado int x=10;
@Anular
ejecución pública vacía() {
System.out.println(x);
}
};
principal vacío estático público (String [] argumentos) {
PrivateToOuter p = nuevo PrivateToOuter();
//System.out.println("clase anónima privada archivada= "+ p.mRunnable.x //no permitido);
p.mRunnable.run(); // permitido
}
}
Resumen final
En este artículo, la propiedad privada parece no ser válida en la superficie, pero en realidad no lo es. En cambio, las propiedades privadas se obtienen mediante métodos indirectos al llamar.
Las clases internas de Java contienen aplicaciones para clases externas cuando se construyen, pero C++ no. Esto es diferente de C++.