Utilisation du mot-clé super pour les attributs et méthodes
En Java, le mot-clé super permet d'accéder aux attributs et méthodes de la classe parente. Lorsqu'une classe enfant hérite d'une classe parente et redéfinit un attribut ou une méthode, il est nécessaire d'utiliser super pour faire référence à la version du parent. Sans ce préfixe, la référence se fait par défaut à la classe enfant, ce qui peut entraîner des ambiguïtés si des éléments portent le même nom.
package com.exemple.base;
// Classe parente
class ClasseParent {
public int identifiant = 2;
public void deplacer() {
System.out.println("Déplacement de la classe parente");
}
public void deplacerAutre() {
System.out.println("Autre déplacement de la classe parente");
}
}
// Classe enfant
class ClasseEnfant extends ClasseParent {
public int identifiant = 20; // Attribut avec le même nom que le parent
@Override
public void deplacer() {
super.deplacer(); // Appel de la méthode du parent
deplacerAutre(); // Appel de la méthode redéfinie dans l'enfant
System.out.println("Déplacement de la classe enfant");
}
@Override
public void deplacerAutre() {
System.out.println("Autre déplacement de la classe enfant");
}
public void tester() {
System.out.println(super.identifiant); // Référence à l'attribut du parent
System.out.println(identifiant); // Référence à l'attribut de l'enfant
}
}
public class TestSuper {
public static void main(String[] args) {
ClasseEnfant enfant = new ClasseEnfant();
enfant.tester();
}
}
Appel des constructeurs avec super
Le mot-clé super peut aussi être utilisé pour invoquer un constructeur de la classe parente. Cette invocation doit être placée en première ligne dans le constructeur de l'enfant. Si aucun appel explicite à super n'est effectué, le constructeur par défaut du parent est automatiquement appelé. Cela garantit l'initialisation correcte des éléments hérités.
package com.exemple.constructeurs;
// Classe parente
class SuperClasse {
int valeur;
public SuperClasse() {
System.out.println("Constructeur par défaut de SuperClasse");
}
public SuperClasse(int val) {
this.valeur = val;
System.out.println("Constructeur avec paramètre de SuperClasse");
}
}
// Classe enfant
class SousClasse extends SuperClasse {
public SousClasse() {
super(); // Appel du constructeur par défaut du parent
System.out.println("Constructeur de SousClasse");
}
public SousClasse(int val) {
this(); // Appel d'un autre constructeur de la classe actuelle
System.out.println("Constructeur avec paramètre de SousClasse");
}
}
public class TestConstructeurs {
public static void main(String[] args) {
new SousClasse(5);
}
}
Notes supplémentaires sur super et la hiérarchie des classes
Lors de la création d'un objet enfant, les constructeurs de toute la chaîne d'héritage sont appelés successivement vers le haut. Cependant, seul l'objet enfant est réellement instancié. Les attributs statiques sont liés à la classe et peuvent être accédés via le nom de la classe, mais il est recommandé d'utiliser cette approche plutôt que via les objets.
package com.exemple.hierarchie;
// Classe racine
class Racine {
static int idStatique = 10;
int id = 1;
public Racine() {
super();
}
}
// Classe intermédiaire
class Interm extends Racine {
static int idStatique = 20;
int id = 2;
public Interm() {
super();
}
}
// Classe feuille
class Feuille extends Interm {
static int idStatique = 30;
int id = 3;
public Feuille() {
super();
}
public Feuille(int a) {
this();
}
}
public class TestHierarchie {
public static void main(String[] args) {
Feuille obj = new Feuille();
System.out.println(Racine.idStatique);
System.out.println(Interm.idStatique);
System.out.println(Feuille.idStatique);
System.out.println(Racine.id);
System.out.println(Interm.id);
System.out.println(Feuille.id);
}
}
La classe Object et la méthode toString
En Java, toutes les classes héritent implicitement de la classe Object. La méthode toString() par défaut retourne le nom de la classe suivi d'un code de hachage, qui peut être utilisé pour identifier un objet mais n'est pas une adresse mémoire exacte. Il est courant de redéfinir cette méthode pour fournir une représentation plus significative des données de l'objet.
package com.exemple.object;
// Classe personnalisée
class Individu {
public int code;
public String nom;
public Individu(int code, String nom) {
this.code = code;
this.nom = nom;
}
@Override
public String toString() {
return "Code: " + code + ", Nom: " + nom;
}
}
public class TestObject {
public static void main(String[] args) {
Individu indiv = new Individu(1, "Alice");
System.out.println(indiv);
System.out.println(indiv.toString());
// Exemples avec des classes standard
String chaine = new String("Bonjour");
System.out.println(chaine);
Integer entier = new Integer(5);
System.out.println(entier);
}
}
Comparaison avec l'opérateur ==
L'opérateur == compare des valeurs primitives directement, mais pour les types de référence, il vérifie si deux références pointent vers le même objet en mémoire. Cela signifie que deux instances distinctes ne seront pas considérées comme égales même si elles contiennent des données identiques.
package com.exemple.comparaison;
// Classe simple pour les tests
class SimpleObjet {
}
public class TestComparaison {
public static void main(String[] args) {
SimpleObjet obj1 = new SimpleObjet();
SimpleObjet obj2 = new SimpleObjet();
SimpleObjet obj3 = obj2;
System.out.println(obj1 == obj2); // Faux : objets différents
System.out.println(obj2 == obj3); // Vrai : même référence
int a = 10;
int b = 10;
System.out.println(a == b); // Vrai : valeurs égales
System.out.println(10 == 10.0); // Vrai : promotion automatique de type
}
}
Le polymorphisme en Java
Le polymorphisme permet à une référence de classe parente de désigner un objet de classe enfant. Cela nécessite trois conditions : l'héritage, la redéfinition de méthodes, et une référence parente pointant vers un objet enfant. Lors de la compilation, seul le type de la référence (gauche) est pris en compte, tandis qu'à l'exécution, c'est le type réel de l'objet (droit) qui détermine le comportement. Les attributs ne bénéficient pas du polymorphisme car ils ne peuvent pas être redéfinis.
Les avantages incluent une meilleuer flexibilité et extensibilité du code, tandis que l'inconvénient principal est l'impossibilité d'accéder directement aux membres uniques de la classe enfant via la référence parente. Pour y remédier, on utilise le transtypage descednant (downcasting), qui convertit une référence parente en type enfant, après une vérification avec instanceof pour éviter les exceptions.
package com.exemple.polymorphisme;
// Classe parente
class Creature {
public int id = 1;
public void manger() {
System.out.println("La creature mange");
}
public void agir() {
System.out.println("La creature agit");
}
}
// Classe enfant 1
class Chien extends Creature {
public int id = 10;
public int idUnique = 100;
@Override
public void manger() {
System.out.println("Le chien mange des os");
}
public void aboyer() {
System.out.println("Le chien aboie");
}
}
// Classe enfant 2
class Chat extends Creature {
@Override
public void manger() {
System.out.println("Le chat mange du poisson");
}
public void grimper() {
System.out.println("Le chat grimpe aux arbres");
}
}
// Classe pour tester le polymorphisme
class Personne {
public void nourrir(Creature c) {
c.manger();
c.agir();
}
}
public class TestPolymorphisme {
public static void main(String[] args) {
Personne p = new Personne();
p.nourrir(new Chien());
p.nourrir(new Chat());
// Polymorphisme : référence parente vers objet enfant
Creature ref1 = new Chien();
Creature ref2 = new Chat();
ref1.manger(); // Appelle la méthode du Chien
ref2.manger(); // Appelle la méthode du Chat
ref1.agir(); // Appelle la méthode de Creature (non redéfinie)
// Accès aux attributs : pas de polymorphisme
Creature ref3 = new Chien();
System.out.println(ref3.id); // Affiche l'id de Creature (1)
// Transtypage descendant pour accéder aux membres uniques
Creature ref4 = new Chien();
if (ref4 instanceof Chien) {
Chien chien = (Chien) ref4;
chien.aboyer();
System.out.println(chien.idUnique);
}
// Transtypage invalide provoque une exception
try {
Chat chat = (Chat) ref4; // ref4 est un Chien, pas un Chat
} catch (ClassCastException e) {
System.out.println("Exception de transtypage");
}
// Transtypage ascendant (upcasting) implicite
Chien chienOriginal = new Chien();
Creature ref5 = chienOriginal;
}
}
Vérification de type avec instanceof
Pour éviter les ClassCastException lors du transtypage descendant, il est essentiel de vérifier le type réel de l'objet avec l'opérateur instanceof. Cet opérateur retourne un booléen indiquant si l'objet est une instance de la classe spécifiée ou d'une de ses sous-classes.
package com.exemple.instanceof;
// Classe de base
class Employe {
public void travailler() {
System.out.println("L'employe travaille");
}
}
// Classe dérivée pour les employés masculins
class EmployeHomme extends Employe {
@Override
public void travailler() {
System.out.println("L'employe homme travaille intensément");
}
public void fumer() {
System.out.println("L'employe homme fume en pause");
}
}
// Classe dérivée pour les employées féminines
class EmployeFemme extends Employe {
@Override
public void travailler() {
System.out.println("L'employée femme travaille efficacement");
}
public void acheter() {
System.out.println("L'employée femme fait des achats");
}
}
public class TestInstanceof {
public static void main(String[] args) {
testerAchat(new EmployeHomme());
testerAchat(new EmployeFemme());
}
public static void testerAchat(Employe emp) {
if (emp instanceof EmployeFemme) {
EmployeFemme femme = (EmployeFemme) emp;
femme.acheter();
} else {
System.out.println("Ce n'est pas une employée éligible");
}
}
}