Introduction au Mot-Clé 'this' en Java
Le mot-clé this en Java est une référence à l'instnace courante de la classe. Il est principalement utilisé pour lever l'ambiguïté entre les variables d'instance et les paramètres locaux, appeler d'autres constructeurs de la même classe, ou encore passer l'objet courant comme argument à une méthode. Comprendre ses différentes utilisations est fondamental pour écrire du code Java robuste et clair.
Utilisation de 'this' dans les Constructeurs
Un constructeur est une méthode spéciale invoquée automatiquement lors de la création d'un objet via l'opérateur new. Si une classe possède plusieurs constructeurs (surcharge de constructeurs), il est possible d'appeler un constructeur depuis un autre à l'aide de this(...). Cette fonctionnalité permet de mutualiser le code d'initialisation et d'éviter la répétition.
Cependant, l'appel à this(...) est soumis à des règles strictes :
- Il ne peut être utilisé que dans un constructeur pour appeler un autre constructeur de la même classe. Les méthodes ordinaires ne peuvent pas l'utiliser pour cet objectif.
- L'appel à
this(...)doit être la première instruction du constructeur. Par conséquent, il est impossible d'utiliserthis(...)etsuper(...)(pour appeler un constructeur de la classe parente) dans le même constructeur, car les deux requièrent d'être la première instruction. - Les appels à
this(...)ne doivent pas former de boucle récursive. Par exemple, un constructeur A appelant B, et B appelant A directement ou indirectement, est interdit.
Voici un exemple de code illustrant un appel récursif interdit :
class CycleConstructeur {
CycleConstructeur() {
// Appelle le constructeur avec un int
this(10);
}
CycleConstructeur(int val) {
// Appelle le constructeur sans argument, créant une récursion infinie
this();
}
// Un autre exemple de récursion directe
CycleConstructeur(int val1, int val2) {
this(val1, val2); // Appel à lui-même
}
}
Dans les constructeurs, comme ailleurs dans la classe, this peut également être utilisé pour accéder aux variables d'instance ou invoquer des méthodes de l'objet courant.
Utilisation de 'this' en dehors des Constructeurs
En dehors du contexte d'appel de constructeurs, this possède plusieurs usages courants :
- Accès aux membres de l'instance :
this.nomDeVariablepermet de désambiguïser l'accès à une variable d'instance lorsque son nom est masqué par un paramètre de méthode ou une variable locale. Par exemple, dansvoid setValeur(int valeur) { this.valeur = valeur; }. - Invocation de méthodes de l'instance :
this.nomDeMethode(arguments...)appelle une méthode de l'objet courant. Bien que souvent implicite (on peut juste écrirenomDeMethode()), l'utilisation explicite dethispeut améliorer la lisibilité. - Référence à l'objet courant : Le mot-clé
thislui-même représente une référence à l'objet sur lequel la méthode est appelée. Cela est utile pour passer l'objet courant comme argument à une autre méthode, par exempleautreObjet.enregistrer(this);.
this permet d'accéder à tous les membres de l'instance, y compris les membres private, car ils appartiennent à la même classe. Cependant, this ne peut pas être utilisé dans un contexte statique (méthodes statiques ou blocs statiques), car il n'y a pas d'instance d'objet courant associée à un membre statique.
'this' et l'Héritage
Dans une hiérarchie d'héritage, le comportement de this est crucial pour comprendre comment les champs et les méthodes sont résolus :
this(...): Comme mentionné, il appelle touojurs un constructeur de la classe courante. Il n'a aucun lien direct avec les constructeurs de la super-classe pour son appel direct.this.champ: Accède au champ de l'instance courante. Si un champ est "masqué" (shadowed) par un champ de même nom dans une sous-classe,this.champfera référence au champ de la classe dans laquelle il est utilisé. Autrement dit, si uneClasseFillea un champxet étend uneClasseMerequi a aussi un champx, alorsthis.xdansClasseFillefera référence àClasseFille.x.this.methode(): Invoque la méthode sur l'instance courante. Si la méthode est surchargée (overridden) dans une sous-classe, le mécanisme de polymorphisme de Java garantira que la version de la sous-classe est appelée, même si l'appel est initié depuis la super-classe viathis, tant que l'objet est une instance de la sous-classe.this(référence d'objet) : Représente toujours l'instance de la classe concrète la plus dérivée. Si un objet deClasseFilleest créé, alorsthis, même à l'intérieur des méthodes héritées deClasseMere, fera référence à l'instance deClasseFille.
Considérons l'exemple suivant pour illustrer ces points :
public class TestHeritage {
public static void main(String[] args) {
ClasseFille objetFille = new ClasseFille();
System.out.println("--- Appel direct depuis l'objet ---");
objetFille.afficherDetails();
}
}
class ClasseMere {
public String message;
ClasseMere() {
this(1); // Appelle ClasseMere(int)
}
ClasseMere(int init) {
this.message = "Message de la classe Mere";
System.out.println("Constructeur ClasseMere(int) - this.message = " + this.message);
this.afficherDetails(); // Appelle la version surchargée si l'objet est une ClasseFille
}
public void afficherDetails() {
System.out.println("Methode afficherDetails de la Mere: " + this.message);
}
}
class ClasseFille extends ClasseMere {
public String message; // Masque le champ 'message' de la ClasseMere
ClasseFille() {
// super() est implicitement appelé ici, appelant ClasseMere()
// puis ClasseMere(int)
System.out.println("Constructeur ClasseFille()");
this.message = "Message de la classe Fille"; // Affecte le champ de ClasseFille
}
@Override
public void afficherDetails() {
System.out.println("Methode afficherDetails de la Fille: " + this.message);
super.afficherDetails(); // Appelle explicitement la méthode de la super-classe
}
}
Lors de l'exécution de new ClasseFille() :
- Le constructeur
ClasseFille()est appelé. Il n'a pas d'appelthis()ousuper()explicite, doncsuper()(le constructeur sans argument deClasseMere) est implicitement appelé. - Le constructeur
ClasseMere()est exécuté, qui à son tour appellethis(1). Ici,this(1)appelleClasseMere(int init)(constructeur de la même classe). - Dans
ClasseMere(int init),this.message = "Message de la classe Mere";initialise le champmessagede laClasseMere. Notez que le champmessagedeClasseFillen'est pas affecté car il s'agit d'un champ masqué distinct. - Ensuite,
this.afficherDetails();est appelé depuisClasseMere(int init). Cependant, comme l'objet en cours de construction est une instance deClasseFilleet queClasseFillea surchargéafficherDetails(), c'est la version deClasseFillequi est exécutée (polymorphisme). - La méthode
ClasseFille.afficherDetails()est appelée. Elle imprime d'abordthis.message, qui est lemessagede laClasseFille. À ce stade, ce champ n'a pas encore été initialisé par le constructeur deClasseFille, donc il affichenull. Ensuite,super.afficherDetails();est appelé, qui exécute la version deClasseMere. - La méthode
ClasseMere.afficherDetails()est appelée. Elle imprimethis.message, qui fait référence au champmessagede laClasseMere, affichant "Message de la classe Mere". - L'exécution retourne au constructeur
ClasseFille(). La lignethis.message = "Message de la classe Fille";initialise le champmessagede laClasseFille.
Après l'instanciation, l'appel objetFille.afficherDetails() exécute de nouveau ClasseFille.afficherDetails(), qui affichera le message de la ClasseFille (maintenant "Message de la classe Fille"), suivi du message de la ClasseMere via super.afficherDetails().
Le résultat de l'exécution sera :
Constructeur ClasseMere(int) - this.message = Message de la classe Mere
Methode afficherDetails de la Fille: null
Methode afficherDetails de la Mere: Message de la classe Mere
Constructeur ClasseFille()
--- Appel direct depuis l'objet ---
Methode afficherDetails de la Fille: Message de la classe Fille
Methode afficherDetails de la Mere: Message de la classe Mere
Comparaison de 'this' et 'super'
Bien que this et super soient tous deux des mots-clés spéciaux pour la référence à des membres, ils ont des rôles distincts :
this: Représente toujours l'instance de l'objet courant. Vous pouvez assignerthisà une variable du type de la classe ou de ses super-types (ex:Object obj = this;).super: N'est pas une référence d'objet. C'est un mot-clé qui permet d'accéder aux membres de la super-classe (champs masqués ou méthodes surchargées) depuis une sous-classe. Vous ne pouvez pas faireObject obj = super;.
L'utilité principale de super est de fournir un mécanisme pour "remonter" dans la hiérarchie pour accéder à des membres qui auraient été masqués ou surchargés dans la classe courante. Si un membre (variable ou méthode) n'est ni masqué ni surchargé, l'accès via this ou directement par son nom (pour les méthodes) aura le même effet que si vous tentiez d'y accéder via super, car le membre est simplement hérité.
'this.getClass()' vs 'super.getClass()'
La méthode getClass() est une méthode finale définie dans la classe Object, et elle est héritée par toutes les classes Java. Elle renvoie la classe d'exécution réelle de l'objet. Comme this et super font tous deux référence à la même instance d'objet sous-jacente (l'objet de la classe la plus concrète), l'appel de getClass() via this ou super produira toujours le même résultat : la classe concrète de l'objet instancié.
public class TestClassesRuntime {
public static void main(String[] args) {
Enfant objetEnfant = new Enfant();
objetEnfant.afficherClasses();
}
}
class Parent {
Parent() {
// Aucune opération particulière pour cet exemple
}
public void afficherClasses() {
System.out.println("Depuis Parent.afficherClasses():");
System.out.println(" this.getClass().getName(): " + this.getClass().getName());
System.out.println(" super.getClass().getName(): " + super.getClass().getName());
}
}
class Enfant extends Parent {
public void afficherClasses() {
System.out.println("Depuis Enfant.afficherClasses():");
System.out.println(" this.getClass().getName(): " + this.getClass().getName());
System.out.println(" super.getClass().getName(): " + super.getClass().getName());
super.afficherClasses(); // Appel la méthode de la classe parente
}
}
L'exécution de ce code affichera la même classe d'exécution pour this.getClass() et super.getClass(), à savoir Enfant, car l'objet réel instancié est un Enfant.
Depuis Enfant.afficherClasses():
this.getClass().getName(): Enfant
super.getClass().getName(): Enfant
Depuis Parent.afficherClasses():
this.getClass().getName(): Enfant
super.getClass().getName(): Enfant