Na programação Java, se um membro for modificado usando a palavra-chave private, apenas a classe na qual o membro está localizado e os métodos desta classe poderão ser usados, e nenhuma outra classe poderá acessar esse membro privado.
As funções básicas do modificador privado são descritas acima. Hoje estudaremos a falha da função privada.
Classes internas Java
Acredito que muitas pessoas usaram classes internas em Java. Java permite que uma classe seja definida dentro de outra classe. A classe dentro da classe é uma classe interna, também chamada de classe aninhada. Uma implementação simples de classe interna pode ser copiada da seguinte forma:
classeOuterClass {
classeInnerClass{
}
}
A questão de hoje está relacionada às classes internas do Java e envolve apenas parte do conhecimento da classe interna relacionado à pesquisa deste artigo. Artigos subsequentes específicos sobre classes internas do Java serão apresentados.
Falha na primeira vez?
Um cenário que costumamos usar na programação é acessar variáveis ou métodos de membros privados de uma classe externa em uma classe interna. Implementado como o código a seguir.
Copie o código do código da seguinte forma:
classe pública OuterClass {
private String idioma = "en";
private String região = "EUA";
classe pública InnerClass {
public void printOuterClassPrivateFields() {
Campos de string = "idioma=" + idioma + ";região=" + região;
System.out.println(campos);
}
}
public static void main(String[] args) {
OuterClass exterior = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();
inner.printOuterClassPrivateFields();
}
}
Por que isso acontece? Não é verdade que membros modificados por private só podem ser acessados pela classe que representam? Privado é realmente inválido?
O compilador está causando problemas?
Vamos usar o comando javap para visualizar os dois arquivos de classe gerados.
O código de cópia do resultado da descompilação de OuterClass é o seguinte:
15:30 $ javap -c Classe Externa
Compilado de "OuterClass.java"
classe pública OuterClass estende java.lang.Object{
publicOuterClass();
Código:
0: aload_0
1: invocaespecial #11; //Método java/lang/Object."<init>":()V
4: aload_0
5: ldc #13; //String en
7: putfield #15; //Idioma do campo:Ljava/lang/String;
10: aload_0
11: ldc #17; //String EUA
13: putfield #19; //Campo região:Ljava/lang/String;
16: retorno
public static void main(java.lang.String[]);
Código:
0: novo #1; //classe OuterClass
3: idiota
4: invocar especial #27; //Método "<init>":()V
7: uma loja_1
8: novo #28; //classe OuterClass$InnerClass
11: dup
12: aload_1
13: dup
14: invocarvirtual #30; //Método java/lang/Object.getClass:()Ljava/lang/Class;
17: Pop
18: invocarspecial #34; //Método OuterClass$InnerClass."<init>":(LOuterClass;)V
21: uma loja_2
22: aload_2
23: invocarvirtual #37; //Método OuterClass$InnerClass.printOuterClassPrivateFields:()V
26: retorno
acesso java.lang.String estático$0(OuterClass);
Código:
0: aload_0
1: getfield #15; //Idioma do campo:Ljava/lang/String;
4: um retorno
acesso java.lang.String estático$1(OuterClass);
Código:
0: aload_0
1: getfield #19; //Campo região:Ljava/lang/String;
4: um retorno
}
Huh? Não, não definimos esses dois métodos em OuterClass
acesso java.lang.String estático$0(OuterClass);
Código:
0: aload_0
1: getfield #15; //Idioma do campo:Ljava/lang/String;
4: um retorno
acesso java.lang.String estático$1(OuterClass);
Código:
0: aload_0
1: getfield #19; //Campo região:Ljava/lang/String;
4: um retorno
}
A julgar pelos comentários fornecidos, access$0 retorna o atributo de idioma de outerClass; access$1 retorna o atributo de região de outerClass; E ambos os métodos aceitam instâncias de OuterClass como parâmetros. Por que esses dois métodos são gerados e o que eles fazem? Vamos dar uma olhada nos resultados da descompilação da classe interna e saberemos.
Resultados de descompilação de OuterClass$InnerClass
Copie o código do código da seguinte forma:
15:37 $ javap -c OuterClass/$InnerClass
Compilado de "OuterClass.java"
classe pública OuterClass$InnerClass estende java.lang.Object{
final OuterClass this$0;
public OuterClass$InnerClass(OuterClass);
Código:
0: aload_0
1: aload_1
2: putfield #10; //Campo este$0:LOuterClass;
5: aload_0
6: invocaespecial #12; //Método java/lang/Object."<init>":()V
9: retorno
public void printOuterClassPrivateFields();
Código:
0: novo #20; //class java/lang/StringBuilder
3: idiota
4: ldc #22; //linguagem da string=
6: invocaespecial #24; //Método java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
9: aload_0
10: getfield #10; //Campo este$0:LOuterClass;
13: invocaestático #27; //Método OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
16: invocavirtual #33; //Método java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #37; //String ;região=
21: invocavirtual #33; //Método java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: aload_0
25: getfield #10; //Campo este$0:LOuterClass;
28: invocaestático #39; //Método OuterClass.access$1:(LOuterClass;)Ljava/lang/String;
31: invocavirtual #33; //Método java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: invocavirtual #42; //Método java/lang/StringBuilder.toString:()Ljava/lang/String;
37: uma loja_1
38: getstatic #46; //Campo java/lang/System.out:Ljava/io/PrintStream;
41: aload_1
42: invocarvirtual #52; //Método java/io/PrintStream.println:(Ljava/lang/String;)V
45: retorno
}
O código a seguir chama o código de acesso$0, cujo objetivo é obter o atributo privado de linguagem de OuterClass.
Copie o código do código da seguinte forma:
13: invocaestático #27; //Método OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
O código a seguir chama o código de acesso$1, cujo objetivo é obter o atributo região privada de OuterClass.
Copie o código do código da seguinte forma:
28: invocaestático #39; //Método OuterClass.access$1:(LOuterClass;)Ljava/lang/String;
Nota: Quando a classe interna é construída, a referência da classe externa será passada e usada como um atributo da classe interna, portanto a classe interna manterá uma referência à sua classe externa.
this$0 é a referência de classe externa mantida pela classe interna. A referência é passada e atribuída por meio do método construtor.
Copie o código do código da seguinte forma:
final OuterClass this$0;
public OuterClass$InnerClass(OuterClass);
Código:
0: aload_0
1: aload_1
2: putfield #10; //Campo este$0:LOuterClass;
5: aload_0
6: invocarspecial #12; //Método java/lang/Object."<init>":()V
9: retorno
resumo
Esta parte de private parece inválida, mas na verdade não é inválida, pois quando a classe interna chama a propriedade privada da classe externa, sua execução real é chamar o método estático da propriedade gerada pelo compilador (ou seja, acessar $0, acesso$1, etc.) para obter esses valores de atributos. Tudo isso é um processamento especial do compilador.
Não funciona desta vez?
Se o método de escrita acima for usado com muita frequência, esse método de escrita raramente será usado, mas pode ser executado.
Copie o código do código da seguinte forma:
classe pública OutraOuterClass {
public static void main(String[] args) {
InnerClass inner = new AnotherOuterClass().new InnerClass();
System.out.println("InnerClass Arquivado = " + inner.x);
}
classeInnerClass {
privado int x = 10;
}
}
Como acima, use javap para descompilar e dar uma olhada. Mas desta vez vamos dar uma olhada nos resultados do InnerClass. Copie o código:
16:03 $ javap -c OutraOuterClass/$InnerClass
Compilado de "AnotherOuterClass.java"
classe OutraOuterClass$InnerClass estende java.lang.Object{
final AnotherOuterClass this$0;
OutraOuterClass$InnerClass(OutraClasseOuter);
Código:
0: aload_0
1: aload_1
2: putfield #12; //Campo este$0:LAnotherOuterClass;
5: aload_0
6: invocarspecial #14; //Método java/lang/Object."<init>":()V
9: aload_0
10: bipush 10
12: putfield #17; //Campo x:I
15: retorno
static int access$0(AnotherOuterClass$InnerClass);
Código:
0: aload_0
1: getfield #17; //Campo x:I
4: retorno
}
Aconteceu novamente, e o compilador gerou automaticamente um método backdoor access$0 para obter o atributo privado uma vez para obter o valor de x.
O código de cópia do resultado da descompilação de AnotherOuterClass.class é o seguinte:
16:08 $ javap -c OutraOuterClass
Compilado de "AnotherOuterClass.java"
classe pública AnotherOuterClass estende java.lang.Object{
public AnotherOuterClass();
Código:
0: aload_0
1: invocar especial #8; //Método java/lang/Object."<init>":()V
4: retorno
public static void main(java.lang.String[]);
Código:
0: novo #16; //classe OutraOuterClass$InnerClass
3: idiota
4: novo #1; //classe OutraOuterClass
7: dup
8: invocar especial #18; //Método "<init>":()V
11: dup
12: invocarvirtual #19; //Método java/lang/Object.getClass:()Ljava/lang/Class;
15: pop
16: invocarspecial #23; //Método AnotherOuterClass$InnerClass."<init>":(LAnotherOuterClass;)V
19: uma loja_1
20: getstatic #26; //Campo java/lang/System.out:Ljava/io/PrintStream;
23: novo #32; //class java/lang/StringBuilder
26: dup
27: ldc #34; //String InnerClass arquivado =
29: invocaespecial #36; //Método java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
32: aload_1
33: invocaestático #39; //Método AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I
36: invocavirtual #43; //Método java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
39: invocavirtual #47; //Método java/lang/StringBuilder.toString:()Ljava/lang/String;
42: invocarvirtual #51; //Método java/io/PrintStream.println:(Ljava/lang/String;)V
45: retorno
}
Esta chamada é a operação da classe externa para obter a propriedade privada x através da instância da classe interna. Copie o código da seguinte forma:
33: invocaestático #39; //Método AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I
Outro resumo
Entre eles, a documentação oficial do Java traz esta frase. Copie o código da seguinte forma:
se o membro ou construtor for declarado privado, então o acesso será permitido se e somente se ocorrer dentro do corpo da classe de nível superior (§7.6) que inclui a declaração do membro ou construtor.
Isso significa que se os membros e construtores (da classe interna) estiverem configurados para modificadores privados, o acesso será permitido se e somente se sua classe externa.
Como evitar que membros privados de classes internas sejam acessados por pessoas de fora
Acredito que depois de ler as duas partes acima, você sentirá que é muito difícil para os membros privados da classe interna serem acessados pela classe externa. Quem quer que o compilador seja "intrometido"? feito. Isso é usar classes internas anônimas.
Como o tipo do objeto mRunnable é Runnable, não o tipo da classe interna anônima (que não podemos obter normalmente), e não há atributo x no Runanble, mRunnable.x não é permitido.
Copie o código do código da seguinte forma:
classe pública PrivateToOuter {
Executável mRunnable = new Runnable(){
privado int x=10;
@Substituir
execução void pública() {
System.out.println(x);
}
};
public static void main(String[] args){
PrivateToOuter p = new PrivateToOuter();
//System.out.println("classe anônima private arquivada= "+ p.mRunnable.x);
p.mRunnable.run();
}
}
Resumo final
Neste artigo, private parece ser inválido superficialmente, mas na verdade não é. Em vez disso, as propriedades privadas são obtidas por meio de métodos indiretos durante a chamada.
As classes internas de Java contêm aplicativos para classes externas quando construídas, mas C++ não. Isso é diferente de C++.