I. Bases des classes internes
En Java, vous pouvez définir une classe à l'intérieur d'une autre classe ou à l'intérieur d'une méthode. Une telle classe est appelée classe interne. Au sens large, les classes internes comprennent généralement les quatre types suivants : les classes internes membres, les classes internes locales, les classes internes anonymes et les classes internes statiques. Commençons par comprendre l'utilisation de ces quatre types de classes internes.
1. Classe interne membre
Une classe interne membre est le type de classe interne le plus courant. Elle est définie à l'intérieur d'une autre classe, comme illustré ci-dessous :
class Circle {
double radius = 0;
public Circle(double radius) {
this.radius = radius;
}
class Draw { //内部类
public void drawSahpe() {
System.out.println("drawshape");
}
}
}
De cette manière, la classe Draw semble être un membre de la classe Circle, et Circle est appelée la classe externe. Une classe interne membre peut accéder inconditionnellement à toutes les propriétés membres et méthodes membres de la classe externe, y compris les membres private et static.
class Circle {
private double radius = 0;
public static int count =1;
public Circle(double radius) {
this.radius = radius;
}
class Draw { //内部类
public void drawSahpe() {
System.out.println(radius); //外部类的private成员
System.out.println(count); //外部类的静态成员
}
}
}
Cependant, notez que lorsqu'une classe interne membre possède une variable membre ou une méthode portant le même nom que la classe externe, un masquage se produit. Autrement dit, par défaut, on accède au membre de la classe interne membre. Pour accéder au membre de même nom de la classe externe, vous devez y accéder sous la forme suivante :
外部类.this.成员变量 外部类.this.成员方法
Bien qu'une classe interne membre puisse accéder aux membres de la classe externe, la classe externe ne peut pas accéder aussi librement aux membres d'une classe interne membre. Dans la classe externe, pour accéder aux membres d'une classe interne membre, vous devez d'abord créer un objet de la classe interne membre, puis y accéder via une référence pointant vers cet objet :
class Circle {
private double radius = 0;
public Circle(double radius) {
this.radius = radius;
getDrawInstance().drawSahpe(); //必须先创建成员内部类的对象,再进行访问
}
private Draw getDrawInstance() {
return new Draw();
}
class Draw { //内部类
public void drawSahpe() {
System.out.println(radius); //外部类的private成员
}
}
}Une classe interne membre existe attachée à la classe externe. Autrement dit, pour créer un objet d'une classe interne membre, un objet de la classe externe doit d'abord exister. La manière générale de créer un objet de classe interne membre est la suivante :
public class Test {
public static void main(String[] args) {
//第一种方式:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建
//第二种方式:
Outter.Inner inner1 = outter.getInnerInstance();
}
}
class Outter {
private Inner inner = null;
public Outter() {
}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
class Inner {
public Inner() {
}
}
}
Les classes internes peuvent avoir un accès public et package. Par exemple, dans l'exemple ci-dessus, la classe interne membre Inner est modifiée avec private, elle ne peut être accédée qu'à l'intérieur de la classe externe ; si elle est modifiée avec public, elle peut être accédée n'importe où ; si elle est modifiée avec protected, elle ne peut être accédée qu'au sein du même package ou par des classes qui héritent de la classe externe ; si elle a un accès par défaut, elle ne peut être accédée qu'au sein du même package. C'est un peu différent d'une classe externe, qui ne peut être modifiée qu'avec un accès package. Personnellement, je le comprends ainsi : puisqu'une classe interne membre ressemble à un membre de la classe externe, elle peut avoir des modificateurs d'accès tout comme les membres de classe.
2. Classe interne locale
Une classe interne locale est une classe définie à l'intérieur d'une méthode ou d'une portée. La différence entre elle et une classe interne membre est que l'accès à une classe interne locale est limité à cette méthode ou à cette portée.
class People{
public People() {
}
}
class Man{
public Man(){
}
public People getWoman(){
class Woman extends People{ //局部内部类
int age =0;
}
return new Woman();
}
} Remarque: Une classe interne locale est comme une variable locale à l'intérieur d'une méthode ; elle ne peut pas avoir de modificateurs public, protected, private ou static.
3. Classe interne anonyme
Les classes internes anonymes sont probablement les plus utilisées lorsque nous écrivons du code dans notre travail quotidien. Lors de l'écriture de code d'écouteur d'événements, utiliser des classes internes anonymes est non seulement pratique, mais rend aussi le code plus facile à maintenir. Le code suivant est un code d'écouteur d'événements Android :
scan_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
history_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});Ce code définit des écouteurs pour deux boutons, et des classes internes anonymes sont utilisées ici. Dans ce code :
new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}C'est l'utilisation d'une classe interne anonyme. Dans le code, un objet écouteur doit être défini pour le bouton. Utiliser une classe interne anonyme peut créer un objet correspondant tout en implémentant les méthodes de la classe parente ou de l'interface, mais la condition préalable est que la classe parente ou l'interface doit déjà exister. Bien sûr, la manière suivante est également acceptable et produit le même effet que l'utilisation de la classe interne anonyme ci-dessus.
private void setListener()
{
scan_bt.setOnClickListener(new Listener1());
history_bt.setOnClickListener(new Listener2());
}
class Listener1 implements View.OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
class Listener2 implements View.OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
Bien que cette manière puisse produire le même effet, elle est à la fois verbeuse et difficile à maintenir, donc généralement on utilise des classes internes anonymes pour écrire du code d'écouteur d'événements. De même, les classes internes anonymes ne peuvent pas avoir de modificateurs d'accès ni le modificateur static.
Une classe interne anonyme est le seul type de classe qui n'a pas de constructeur. Comme elle n'a pas de constructeur, le domaine d'utilisation des classes internes anonymes est très limité. La plupart des classes internes anonymes sont utilisées pour des rappels d'interface. Au moment de la compilation, le système nomme automatiquement une classe interne anonyme Outter$1.class. De manière générale, les classes internes anonymes sont utilisées pour hériter d'autres classes ou implémenter des interfaces, sans avoir besoin d'ajouter des méthodes supplémentaires ; elles implémentent ou redéfinissent uniquement les méthodes héritées.
4. Classe interne statique
Une classe interne statique est aussi une classe définie à l'intérieur d'une autre classe, sauf que le mot-clé static est ajouté avant la classe. Une classe interne statique n'a pas besoin de dépendre de la classe externe, ce qui est quelque peu similaire aux propriétés membres statiques d'une classe, et elle ne peut pas utiliser les variables membres ou méthodes non statiques de la classe externe. C'est facile à comprendre : parce qu'un objet de classe interne statique peut être créé sans objet de classe externe, si on lui permettait d'accéder aux membres non statiques de la classe externe, une contradiction apparaîtrait, puisque les membres non statiques de la classe externe doivent dépendre d'un objet spécifique.
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter {
public Outter() {
}
static class Inner {
public Inner() {
}
}
}

II. Compréhension approfondie des classes internes
1. Pourquoi une classe interne membre peut-elle accéder inconditionnellement aux membres de la classe externe ?
Avant cela, nous avons déjà discuté du fait qu'une classe interne membre peut accéder inconditionnellement aux membres de la classe externe. Alors, comment cela est-il exactement implémenté ? Décompilons le fichier bytecode pour le découvrir. En fait, lorsque le compilateur compile, il compile la classe interne membre dans un fichier bytecode séparé. Voici le code de Outter.java :
public class Outter {
private Inner inner = null;
public Outter() {
}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
protected class Inner {
public Inner() {
}
}
}Après compilation, deux fichiers bytecode apparaissent :

En décompilant le fichier Outter$Inner.class, on obtient les informations suivantes :
E:\Workspace\Test\bin\com\cxh\test2>javap -v Outter$Inner
Compiled from "Outter.java"
public class com.cxh.test2.Outter$Inner extends java.lang.Object
SourceFile: "Outter.java"
InnerClass:
#24= #1 of #22; //Inner=class com/cxh/test2/Outter$Inner of class com/cxh/tes
t2/Outter
minor version: 0
major version: 50
Constant pool:
const #1 = class #2; // com/cxh/test2/Outter$Inner
const #2 = Asciz com/cxh/test2/Outter$Inner;
const #3 = class #4; // java/lang/Object
const #4 = Asciz java/lang/Object;
const #5 = Asciz this$0;
const #6 = Asciz Lcom/cxh/test2/Outter;;
const #7 = Asciz <init>;
const #8 = Asciz (Lcom/cxh/test2/Outter;)V;
const #9 = Asciz Code;
const #10 = Field #1.#11; // com/cxh/test2/Outter$Inner.this$0:Lcom/cxh/t
est2/Outter;
const #11 = NameAndType #5:#6;// this$0:Lcom/cxh/test2/Outter;
const #12 = Method #3.#13; // java/lang/Object."<init>":()V
const #13 = NameAndType #7:#14;// "<init>":()V
const #14 = Asciz ()V;
const #15 = Asciz LineNumberTable;
const #16 = Asciz LocalVariableTable;
const #17 = Asciz this;
const #18 = Asciz Lcom/cxh/test2/Outter$Inner;;
const #19 = Asciz SourceFile;
const #20 = Asciz Outter.java;
const #21 = Asciz InnerClasses;
const #22 = class #23; // com/cxh/test2/Outter
const #23 = Asciz com/cxh/test2/Outter;
const #24 = Asciz Inner;
{
final com.cxh.test2.Outter this$0;
public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
Code:
Stack=2, Locals=2, Args_size=2
0: aload_0
1: aload_1
2: putfield #10; //Field this$0:Lcom/cxh/test2/Outter;
5: aload_0
6: invokespecial #12; //Method java/lang/Object."<init>":()V
9: return
LineNumberTable:
line 16: 0
line 18: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/cxh/test2/Outter$Inner;
}Les lignes 11 à 35 constituent le contenu du pool de constantes. Expliquons point par point le contenu de la ligne 38 :
final com.cxh.test2.Outter this$0;
Cette ligne est un pointeur vers l'objet de la classe externe. En voyant cela, tout le monde doit soudainement comprendre. C'est-à-dire que le compilateur ajoute par défaut une référence pointant vers l'objet de la classe externe pour la classe interne membre. Alors, comment cette référence est-elle initialisée ? Regardons ensuite le constructeur de la classe interne :
public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
De ceci, on peut voir que bien que le constructeur de la classe interne que nous avons définie soit un constructeur sans argument, le compilateur ajoute quand même un paramètre par défaut, dont le type est une référence à l'objet de la classe externe. Par conséquent, le pointeur Outter this&0 dans la classe interne membre pointe vers l'objet de la classe externe, ce qui permet d'accéder librement aux membres de la classe externe dans la classe interne membre. Cela montre aussi indirectement qu'une classe interne membre dépend de la classe externe. Si un objet de classe externe n'est pas créé, la référence Outter this&0 ne peut pas être initialisée et affectée, donc un objet de classe interne membre ne peut pas être créé.
2. Pourquoi les classes internes locales et les classes internes anonymes ne peuvent-elles accéder qu'aux variables locales final ?
Cette question a probablement troublé beaucoup de personnes. Avant d'en discuter, regardons le code suivant :
public class Test {
public static void main(String[] args) {
}
public void test(final int b) {
final int a = 10;
new Thread(){
public void run() {
System.out.println(a);
System.out.println(b);
};
}.start();
}
} Ce code sera compilé en deux fichiers de classe : Test.class et Test1.class.Par défaut, le compilateur nomme les classes internes anonymes et les classes internes locales commeOutterx.class(x est un entier positif)。

D'après la figure ci-dessus, la classe interne anonyme dans la méthode test est nommée Test$1.
Dans le code ci-dessus, si le final devant l'une des variables a ou b est supprimé, ce code ne sera pas compilé. D'abord, considérons un tel problème :
Lorsque la méthode test termine son exécution, le cycle de vie de la variable a se termine. À ce moment-là, le cycle de vie de l'objet Thread peut ne pas encore être terminé. Il devient donc impossible de continuer à accéder à la variable a dans la méthode run de Thread. Mais cet effet doit encore être obtenu. Que peut-on faire ? Java utilise une approche de copie pour résoudre ce problème. La décompilation du bytecode de ce code donne ce qui suit :

Nous pouvons voir que dans la méthode run il y a une instruction :
bipush 10
Cette instruction empile l'opérande 10 sur la pile, ce qui indique qu'une variable locale est utilisée. Ce processus est effectué par défaut par le compilateur lors de la compilation. Si la valeur de la variable peut être déterminée au moment de la compilation, le compilateur ajoute par défaut un littéral de contenu égal au pool de constantes de la classe interne anonyme (classe interne locale), ou intègre directement le bytecode correspondant dans le bytecode exécuté. De cette manière, la variable utilisée par la classe interne anonyme est une autre variable locale, seule sa valeur est égale à celle de la variable locale dans la méthode, elle est donc complètement indépendante de la variable locale dans la méthode.
Regardons maintenant un autre exemple :
public class Test {
public static void main(String[] args) {
}
public void test(final int a) {
new Thread(){
public void run() {
System.out.println(a);
};
}.start();
}
}La décompilation donne :

Nous pouvons voir que le constructeur de la classe interne anonyme Test$1 a deux paramètres : l'un est une référence à l'objet de la classe externe, et l'autre est une variable int. De toute évidence, le paramètre a dans la méthode test est passé comme argument pour initialiser la copie de la variable a dans la classe interne anonyme.
C'est-à-dire que, si la valeur de la variable locale peut être déterminée au moment de la compilation, une copie est créée directement dans la classe interne anonyme. Si la valeur de la variable locale ne peut pas être déterminée au moment de la compilation, la copie est initialisée en passant des paramètres via le constructeur.
D'après ce qui précède, on peut voir que la variable a accédée dans la méthode run n'est pas du tout la variable locale a dans la méthode test. Cela résout le problème des cycles de vie incohérents mentionné précédemment. Mais un nouveau problème se pose : puisque la variable a accédée dans la méthode run et la variable a dans la méthode test ne sont pas la même variable, que se passerait-il si la valeur de la variable a était modifiée dans la méthode run ?
Oui, cela provoquerait une incohérence des données, ce qui ne permettrait pas d'atteindre l'intention et les exigences d'origine. Pour résoudre ce problème, le compilateur Java restreint la variable a à être une variable final, et n'autorise pas la modification de la variable a. Pour les variables de type référence, il n'est pas permis de pointer vers un nouvel objet. Cela résout le problème d'incohérence des données.
À ce stade, tout le monde devrait comprendre pourquoi les variables locales et les paramètres d'une méthode doivent être restreints avec final.
3. Y a-t-il quelque chose de spécial à propos des classes internes statiques ?
D'après ce qui précède, nous pouvons savoir que les classes internes statiques ne dépendent pas de la classe externe, ce qui signifie que vous pouvez créer un objet de la classe interne sans créer d'objet de la classe externe. De plus, les classes internes statiques ne détiennent pas de référence vers l'objet de la classe externe. Les lecteurs peuvent essayer de décompiler eux-mêmes le fichier de classe pour voir qu'il n'y a pas deréférence Outter this&0.
III. Cas d'utilisation et avantages des classes internes
Pourquoi les classes internes sont-elles nécessaires en Java ? Pour résumer, il y a principalement les quatre points suivants :
- 1. Chaque classe interne peut hériter indépendamment d'une implémentation d'une interface, donc le fait que la classe externe ait déjà hérité d'une certaine implémentation (d'interface) ou non n'a aucun effet sur la classe interne. Les classes internes rendent la solution de l'héritage multiple complète.
- 2. Il est pratique de regrouper des classes ayant certaines relations logiques, et aussi de les masquer de l'extérieur.
- 3. Il est pratique pour écrire des programmes événementiels.
- 4. Il est pratique pour écrire du code de threads.
Personnellement, je pense que le premier point est l'une des raisons les plus importantes. L'existence des classes internes rend le mécanisme d'héritage multiple de Java plus complet.
IV. Questions courantes de tests écrits et d'entretiens liées aux classes internes
1. Complétez le code aux emplacements (1), (2), (3) selon les commentaires
public class Test{
public static void main(String[] args){
// 初始化Bean1
(1)
bean1.I++;
// 初始化Bean2
(2)
bean2.J++;
//初始化Bean3
(3)
bean3.k++;
}
class Bean1{
public int I = 0;
}
static class Bean2{
public int J = 0;
}
}
class Bean{
class Bean3{
public int k = 0;
}
}
D'après ce qui précède, pour les classes internes membres, vous devez d'abord créer une instance de la classe externe avant de pouvoir créer une instance de la classe interne. Cependant, pour les classes internes statiques, vous pouvez créer une instance de la classe interne sans créer d'instance de la classe externe.
La forme générale pour créer un objet de classe interne statique est :OuterClass.InnerClass xxx = new OuterClass.InnerClass()
La forme générale pour créer un objet de classe interne membre est :OuterClass.InnerClass xxx = outerObjectName.new InnerClass()
Par conséquent, le code aux emplacements (1), (2), (3) est respectivement :
Test test = new Test(); Test.Bean1 bean1 = test.new Bean1();
Test.Bean2 b2 = new Test.Bean2();
Bean bean = new Bean(); Bean.Bean3 bean3 = bean.new Bean3();
2. Quelle est la sortie du code suivant ?
public class Test {
public static void main(String[] args) {
Outter outter = new Outter();
outter.new Inner().print();
}
}
class Outter
{
private int a = 1;
class Inner {
private int a = 2;
public void print() {
int a = 3;
System.out.println("局部变量:" + a);
System.out.println("内部类变量:" + this.a);
System.out.println("外部类变量:" + Outter.this.a);
}
}
}3 2 1
Enfin, un dernier point de connaissance : concernant l'héritage des classes internes membres. D'une manière générale, les classes internes sont rarement utilisées pour l'héritage. Mais lorsqu'elles le sont, il y a deux points à noter :
- 1) La référence à une classe interne membre doit être de la forme Outter.Inner
- 2) Le constructeur doit avoir une référence à l'objet de la classe externe, et appeler super() via cette référence. Ce code est extrait de "Thinking in Java"
class WithInner {
class Inner{
}
}
class InheritInner extends WithInner.Inner {
// InheritInner() 是不能通过编译的,一定要加上形参
InheritInner(WithInner wi) {
wi.super(); //必须有这句调用
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner obj = new InheritInner(wi);
}
}
Source originale : https://www.cnblogs.com/dolphin0520/p/3811445.html