En programmation Java, si un membre est modifié à l'aide du mot clé private, seule la classe dans laquelle se trouve le membre et les méthodes de cette classe peuvent être utilisées, et aucune autre classe ne peut accéder à ce membre privé.
Les fonctions de base du modificateur privé sont décrites ci-dessus. Aujourd'hui, nous allons étudier l'échec de la fonction privée.
Classes internes Java
Je pense que de nombreuses personnes ont utilisé des classes internes en Java. Java permet de définir une classe dans une autre classe. La classe à l'intérieur de la classe est une classe interne, également appelée classe imbriquée. Une simple implémentation de classe interne peut être copiée comme suit :
classe ClasseExterne {
classe ClasseInner{
}
}
La question d'aujourd'hui concerne les classes internes Java et n'implique qu'une partie des connaissances sur les classes internes liées à la recherche de cet article. Des articles ultérieurs spécifiques sur les classes internes Java seront présentés.
Un premier échec ?
Un scénario que nous utilisons souvent en programmation consiste à accéder à des variables membres privées ou à des méthodes d'une classe externe dans une classe interne. Implémenté comme le code suivant.
Copiez le code comme suit :
classe publique OuterClass {
langage de chaîne privé = "en" ;
région de chaîne privée = "US" ;
classe publique InnerClass {
public void printOuterClassPrivateFields() {
Champs de chaîne = "langue=" + langue + ";region=" + région ;
System.out.println(champs);
}
}
public static void main (String[] arguments) {
OuterClass externe = new OuterClass();
OuterClass.InnerClass inner = external.new InnerClass();
inner.printOuterClassPrivateFields();
}
}
Pourquoi ? N'est-il pas vrai que les membres privés modifiés ne sont accessibles que par la classe qu'ils représentent ? Le privé est-il vraiment invalide ?
Le compilateur pose-t-il des problèmes ?
Utilisons la commande javap pour afficher les deux fichiers de classe générés.
Le code de copie du résultat de la décompilation d'OuterClass est le suivant :
15:30 $ javap -c Classe externe
Compilé à partir de "OuterClass.java"
la classe publique OuterClass étend java.lang.Object{
public OuterClass();
Code:
0 : aload_0
1 : invocationspecial #11 ; //Méthode java/lang/Object."<init>":()V
4 : aload_0
5 : ldc #13 ; //String fr
7 : putfield #15 ; //Langage du champ :Ljava/lang/String ;
10 : aload_0
11 : ldc #17 ; //Chaîne US
13 : putfield #19 ; //Région du champ :Ljava/lang/String ;
16 : retour
public static void main(java.lang.String[]);
Code:
0 : nouveau #1 ; //classeOuterClass
3 : dupé
4 : invocationspecial #27 ; //Méthode "<init>":()V
7 : astore_1
8 : nouveau #28 ; //classeOuterClass$InnerClass
11 : dupé
12 : aload_1
13 : dupé
14 : invoquervirtual #30 ; //Méthode java/lang/Object.getClass:()Ljava/lang/Class ;
17 : Pop
18 : invocationspecial #34 ; //Méthode OuterClass$InnerClass."<init>":(LOuterClass;)V
21 : astore_2
22 : aload_2
23 : invoquervirtual #37 ; //Méthode OuterClass$InnerClass.printOuterClassPrivateFields :()V
26 : retour
statique java.lang.String access$0(OuterClass);
Code:
0 : aload_0
1 : getfield #15 ; //Langage du champ :Ljava/lang/String ;
4 : retour
statique java.lang.String access$1(OuterClass);
Code:
0 : aload_0
1 : getfield #19 ; //Région du champ :Ljava/lang/String ;
4 : retour
}
Hein? Non, nous n'avons pas défini ces deux méthodes dans OuterClass
statique java.lang.String access$0(OuterClass);
Code:
0 : aload_0
1 : getfield #15 ; //Langage du champ :Ljava/lang/String ;
4 : retour
statique java.lang.String access$1(OuterClass);
Code:
0 : aload_0
1 : getfield #19 ; //Région du champ :Ljava/lang/String ;
4 : retour
}
À en juger par les commentaires donnés, access$0 renvoie l'attribut de langue de externalClass ; access$1 renvoie l'attribut de région de externalClass. Et les deux méthodes acceptent les instances d'OuterClass comme paramètres. Pourquoi ces deux méthodes sont-elles générées et que font-elles ? Jetons un coup d'œil aux résultats de décompilation de la classe interne et nous le saurons.
Résultats de la décompilation de OuterClass$InnerClass
Copiez le code comme suit :
15:37 $ javap -c Classe externe/$Classe interne
Compilé à partir de "OuterClass.java"
la classe publique OuterClass$InnerClass étend java.lang.Object{
classe extérieure finale this$0 ;
public OuterClass$InnerClass(OuterClass);
Code:
0 : aload_0
1 : aload_1
2 : putfield #10 ; //Champ this$0:LOuterClass ;
5 : aload_0
6 : invocationspecial #12 ; //Méthode java/lang/Object."<init>":()V
9 : retour
public void printOuterClassPrivateFields();
Code:
0 : nouveau #20 ; //classe java/lang/StringBuilder
3 : dupé
4 : ldc #22 ; //Langage de chaîne=
6 : invocationspecial #24 ; //Méthode java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
9 : aload_0
10 : getfield #10 ; //Champ this$0:LOuterClass ;
13 : invoquerstatique #27 ; //Méthode OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
16 : invoquervirtual #33 ; //Méthode java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19 : ldc #37 ; //Chaîne ;region=
21 : invoquervirtual #33 ; //Méthode java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24 : aload_0
25 : getfield #10 ; //Champ this$0:LOuterClass ;
28 : invoquerstatique #39 ; //Méthode OuterClass.access$1 :(LOuterClass;)Ljava/lang/String ;
31 : invoquervirtual #33 ; //Méthode java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34 : invoquervirtual #42 ; //Méthode java/lang/StringBuilder.toString:()Ljava/lang/String;
37 : astore_1
38 : getstatic #46 ; //Champ java/lang/System.out:Ljava/io/PrintStream ;
41 : aload_1
42 : invoquervirtual #52 ; //Méthode java/io/PrintStream.println:(Ljava/lang/String;)V
45 : retour
}
Le code suivant appelle le code d'accès$0, dont le but est d'obtenir l'attribut privé de langage d'OuterClass.
Copiez le code comme suit :
13 : invoquerstatique #27 ; //Méthode OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
Le code suivant appelle le code d'access$1, dont le but est d'obtenir l'attribut privé région d'OutherClass.
Copiez le code comme suit :
28 : invoquerstatique #39 ; //Méthode OuterClass.access$1 :(LOuterClass;)Ljava/lang/String ;
Remarque : Lorsque la classe interne est construite, la référence de la classe externe sera transmise et utilisée comme attribut de la classe interne, de sorte que la classe interne contiendra une référence à sa classe externe.
this$0 est la référence de classe externe détenue par la classe interne. La référence est transmise et attribuée via la méthode constructeur.
Copiez le code comme suit :
classe extérieure finale this$0 ;
public OuterClass$InnerClass(OuterClass);
Code:
0 : aload_0
1 : aload_1
2 : putfield #10 ; //Champ this$0:LOuterClass ;
5 : aload_0
6 : invocationspecial #12 ; //Méthode java/lang/Object."<init>":()V
9 : retour
résumé
Cette partie de private semble invalide, mais en fait elle ne l'est pas, car lorsque la classe interne appelle la propriété privée de la classe externe, sa véritable exécution consiste à appeler la méthode statique de la propriété générée par le compilateur (c'est-à-dire l'accès $0, access$1, etc.) pour obtenir ces valeurs d'attribut. Tout cela est un traitement spécial par le compilateur.
Est-ce que ça ne marche pas cette fois ?
Si la méthode d'écriture ci-dessus est très couramment utilisée, alors cette méthode d'écriture est rarement utilisée, mais elle peut s'exécuter.
Copiez le code comme suit :
classe publique AnotherOuterClass {
public static void main (String[] arguments) {
InnerClass inner = new AnotherOuterClass().new InnerClass();
System.out.println("InnerClass Filed = " + inner.x);
}
classe Classe Intérieure {
int privé x = 10 ;
}
}
Comme ci-dessus, utilisez javap pour décompiler et jetez un œil. Mais cette fois, jetons un œil aux résultats d'InnerClass. Copiez le code. Le code est le suivant :
16:03 $ javap -c AnotherOuterClass/$InnerClass
Compilé à partir de "AnotherOuterClass.java"
la classe AnotherOuterClass$InnerClass étend java.lang.Object{
finale AnotherOuterClass this$0 ;
AnotherOuterClass$InnerClass(AnotherOuterClass);
Code:
0 : aload_0
1 : aload_1
2 : putfield #12 ; //Champ this$0:LAnotherOuterClass ;
5 : aload_0
6 : invocationspecial #14 ; //Méthode java/lang/Object."<init>":()V
9 : aload_0
10 : bipousse 10
12 : champ de put #17 ; //Champ x : I
15 : retour
static int access$0(AnotherOuterClass$InnerClass);
Code:
0 : aload_0
1 : getfield #17 ; //Champ x : I
4 : retour
}
Cela s'est reproduit et le compilateur a automatiquement généré une méthode de porte dérobée access$0 pour obtenir l'attribut privé une fois pour obtenir la valeur de x.
Le code de copie du résultat de la décompilation de AnotherOuterClass.class est le suivant :
16:08 $ javap -c Une autreClasseExtérieure
Compilé à partir de "AnotherOuterClass.java"
la classe publique AnotherOuterClass étend java.lang.Object{
public AnotherOuterClass();
Code:
0 : aload_0
1 : invocationspecial #8 ; //Méthode java/lang/Object."<init>":()V
4 : retour
public static void main(java.lang.String[]);
Code:
0 : nouveau #16 ; //classe AnotherOuterClass$InnerClass
3 : dupé
4 : nouveau #1 ; //classe AnotherOuterClass
7 : dupé
8 : invocationspecial #18 ; //Méthode "<init>":()V
11 : dupé
12 : invoquervirtual #19 ; //Méthode java/lang/Object.getClass:()Ljava/lang/Class ;
15:pop
16 : invocationspecial #23 ; //Méthode AnotherOuterClass$InnerClass."<init>":(LAnotherOuterClass;)V
19 : astore_1
20 : getstatic #26 ; //Champ java/lang/System.out:Ljava/io/PrintStream ;
23 : nouveau #32 ; //classe java/lang/StringBuilder
26 : dupé
27 : ldc #34 ; //Chaîne InnerClass déposée =
29 : invocationspecial #36 ; //Méthode java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
32 : aload_1
33 : invoquerstatique #39 ; //Méthode AnotherOuterClass$InnerClass.access$0 :(LAnotherOuterClass$InnerClass ;)I
36 : invoquervirtual #43 ; //Méthode java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
39 : invoquervirtual #47 ; //Méthode java/lang/StringBuilder.toString:()Ljava/lang/String;
42 : invoquervirtual #51 ; //Méthode java/io/PrintStream.println:(Ljava/lang/String;)V
45 : retour
}
Cet appel est l'opération de la classe externe pour obtenir la propriété privée x via l'instance de la classe interne. Copiez le code comme suit :
33 : invoquerstatique #39 ; //Méthode AnotherOuterClass$InnerClass.access$0 :(LAnotherOuterClass$InnerClass ;)I
Un autre résumé
Parmi eux, la documentation officielle Java contient cette phrase. Copiez le code comme suit :
si le membre ou le constructeur est déclaré privé, alors l'accès est autorisé si et seulement s'il se produit dans le corps de la classe de niveau supérieur (§7.6) qui contient la déclaration du membre ou du constructeur.
Cela signifie que si les membres et les constructeurs (de la classe interne) sont définis sur des modificateurs privés, l'accès est autorisé si et seulement si leur classe externe.
Comment empêcher les membres privés des classes internes d'être accessibles par des étrangers
Je crois qu'après avoir lu les deux parties ci-dessus, vous aurez l'impression qu'il est très difficile pour les membres privés de la classe interne d'être accessibles par la classe externe. Qui veut que le compilateur soit « fouineur » ? fait. Il s'agit d'utiliser des classes internes anonymes.
Étant donné que le type de l'objet mRunnable est Runnable, pas le type de la classe interne anonyme (que nous ne pouvons pas obtenir normalement), et qu'il n'y a pas d'attribut x dans Runanble, mRunnable.x n'est pas autorisé.
Copiez le code comme suit :
classe publique PrivateToOuter {
Exécutable mRunnable = nouveau Runnable(){
privé int x=10 ;
@Outrepasser
public void run() {
System.out.println(x);
}
} ;
public static void main (String[] args){
PrivateToOuter p = new PrivateToOuter();
//System.out.println("anonymous class private file= "+ p.mRunnable.x //non autorisé);
p.mRunnable.run(); // autorisé
}
}
Résumé final
Dans cet article, private semble invalide en apparence, mais en fait ce n'est pas le cas, les propriétés privées sont obtenues par des méthodes indirectes lors de l'appel.
Les classes internes de Java contiennent des applications pour les classes externes lors de leur construction, mais pas le C++. Ceci est différent du C++.