B
qui hérite d’une classe
A
est une sous-classe de
A
, et
A
est la super-classe de
B
.
java.lang.Object
est la super-classe de toutes les classes Java, directement ou indirectement.
B
qui hérite d’une classe
A
, il faut utiliser la syntaxe suivante.
Exemple 81. Une classe qui hérite d'une autre classe
public class A { // déclarée dans le fichier A.java ... } public class B extends A { // déclarée dans le fichier B.java ... }
B
étend
A
, ou dérive de
A
, ou est une classe dérivée de
A
. On peut également exprimer cette relation en termes de parent / enfant, les parents étant les super classes, et les enfants les sous-classes.
On dit aussi que les objets de
B
sont des objets de
A
. Cette dernière façon d'exprimer la relation d'héritage est très pratique, et permet de bien comprendre cette notion d'héritage.
Une des particularités de Java par rapport à d’autres langages objet tels que le C++, est que l’héritage multiple n’est pas autorisé. Une classe ne peut hériter que d’une seule classe à la fois, ce qui simplifie grandement les choses, notamment les problèmes de collision de noms.
public
et
protected
d'une classe parent sont accessibles à ses classes enfant. En revanche, une classe parent ne peut accéder à ses classes enfant. Lorsque l'on définit une hiérarchie de classes, on cherche donc à répartir les méthodes les plus générales sur les classes parent, et les méthodes les plus spécialisées sur les classes enfant. Ce que l'on appelle "sous-classe" correspond en fait aux classes possédant le plus de méthodes.
Si un champ d'une classe enfant porte le même nom qu'un champ d'une classe parent, alors le champ enfant masque le champ parent. Par défaut, le code de la classe enfant accède au champ enfant. En revanche, le code de la classe parent accède au champ de sa classe. Il est possible pour un code de la classe enfant de lire le champ de la classe parent en utilisant le mot-clé
super
.
Exemple 82. Accès aux champs d'une super-classe - 1
public class A { // déclarée dans le fichier A.java protected String nom = "Je suis dans A" ; public void uneMethode() { System.out.println(nom) ; // imprime "Je suis dans A" } } public class B extends A { // déclarée dans le fichier B.java protected String nom = "Je suis dans B" ; public void uneAutreMethode() { System.out.println(nom) ; // imprime "Je suis dans B" System.out.println(super.nom) ; // imprime "Je suis dans A" } }
Exemple 83. Accès aux champs d'une super-classe - 2
public class Main { // déclarée dans le fichier Main.java public static void main(String... args) { A a = new A() ; // la classe A est celle que nous venons de définir B b = new B() ; // idem A ba = b ; // cette déclaration est légale, car b est un élément de A, // du fait que B étend A ; // b et ba désignent le même objet System.out.println("a.nom = " + a.nom) ; System.out.println("b.nom = " + b.nom) ; System.out.println("ba.nom = " + ba.nom) ; } }
> a.nom = Je suis dans A > b.nom = Je suis dans B > ba.nom = Je suis dans AS'amuser à écrire des collisions de nom de la sorte est un jeu qui ne doit pas dépasser le cadre de l'écriture d'un poly. On évitera de noter les bonnes pratiques qui ont été sournoisement piétinées dans cet exemple dans le seul but de le rendre incompréhensible : utilisation de champs non privés, collisions de nom pour un champ dans une classe et une extension, etc...
Exemple 84. Overloading et overriding
public class A { // déclarée dans le fichier A.java public void ouSuisJe() { System.out.println("Je suis dans A !") ; } } public class B extends A { // déclarée dans le fichier B.java public void ouSuisJe() { System.out.println("Je suis dans B...") ; } } public class Main { // déclarée dans le fichier Main.java public static void main(String ... args) { A a = new A() ; // mêmes déclarations que dans l'exemple précédent B b = new B() ; A ba = b ; System.out.println("a.ouSuisJe ? " + a.ouSuisJe()) ; System.out.println("b.ouSuisJe ?" + b.ouSuisJe()) ; System.out.println("ba.ouSuisJe ? " + ba.ouSuisJe()) ; } }
> a.ouSuisJe ? Je suis dans A ! > b.ouSuisJe ? Je suis dans B... > ba.ouSuisJe ? Je suis dans B...Dans ce cas, la méthode qui doit être appelée est déterminée à l'exécution, et même si un objet de
B
est déclaré en tant qu'objet de
A
, ce sont les méthodes de
B
qui sont appelées.
Enfin notons que les constructeurs ne sont pas des méthodes d'une classe au sens strict, la notion de surcharge n'a donc pas de sens pour eux.
final
, pris en compte à la compilation.
Exemple 85. Utilisation du mot-clé
final
public final class A { // déclarée dans le fichier A.java, ne peut être étendue public void ouSuisJe() { System.out.println("Je suis dans A !") ; } } public B { // déclarée dans B.java public final void ouSuisJe() { // ne peut être surchargée, bien que // B puisse être étendue System.out.println("J'y suis j'y reste !") ; } }
String
et de toutes les classes enveloppes des types de base. De même, les méthodes
wait()
de la classe
Object
sont finales, on ne peut donc pas les étendre.
Empêcher d'étendre une classe ou une méthode est une mesure de sécurité. Cela garantit effectivement que le comportement des objets que l'on manipule est bien celui que l'on attend. Par exemple, la classe
String
est immutable, ce qui signifie qu'une fois instanciée, on ne peut changer la valeur de la chaîne de caractères portée par l'objet. Si elle n'était pas finale, il serait possible de l'étendre en changeant ce comportement. Ces nouveaux objets pourraient être injectés dans n'importe quel code utilisant des
String
, et faire échouer des processus entiers.
String
StringBuffer
et
StringBuilder