Héritage en Java, partie 1: le mot clé extend

Java prend en charge la réutilisation des classes via l'héritage et la composition. Ce didacticiel en deux parties vous apprend à utiliser l'héritage dans vos programmes Java. Dans la partie 1, vous apprendrez à utiliser le extendsmot - clé pour dériver une classe enfant à partir d'une classe parent, appeler des constructeurs et des méthodes de classe parent et remplacer des méthodes. Dans la partie 2, vous visiterez java.lang.Object, qui est la superclasse de Java dont toutes les autres classes héritent.

Pour compléter votre apprentissage de l'héritage, n'oubliez pas de consulter mon astuce Java expliquant quand utiliser la composition par rapport à l'héritage. Vous apprendrez pourquoi la composition est un complément important à l'héritage et comment l'utiliser pour se prémunir contre les problèmes d'encapsulation dans vos programmes Java.

télécharger Obtenir le code Téléchargez le code source des exemples d'applications dans ce didacticiel. Créé par Jeff Friesen pour JavaWorld.

Héritage Java: deux exemples

L'héritage est une construction de programmation que les développeurs de logiciels utilisent pour établir des relations entre les catégories. L'héritage nous permet de dériver des catégories plus spécifiques de catégories plus génériques. La catégorie la plus spécifique est une sorte de catégorie la plus générique. Par exemple, un compte courant est une sorte de compte dans lequel vous pouvez effectuer des dépôts et des retraits. De même, un camion est une sorte de véhicule utilisé pour transporter de gros objets.

L'héritage peut descendre à travers plusieurs niveaux, conduisant à des catégories toujours plus spécifiques. A titre d'exemple, la figure 1 montre une voiture et un camion héritant d'un véhicule; break héritant de la voiture; et camion poubelle héritant du camion. Les flèches pointent des catégories "enfants" plus spécifiques (en bas vers le bas) vers des catégories "parents" moins spécifiques (en haut).

Jeff Friesen

Cet exemple illustre l' héritage unique dans lequel une catégorie enfant hérite de l'état et des comportements d'une catégorie parent immédiate. En revanche, l' héritage multiple permet à une catégorie enfant d'hériter de l'état et des comportements d'au moins deux catégories parents immédiates. La hiérarchie de la figure 2 illustre l'héritage multiple.

Jeff Friesen

Les catégories sont décrites par classes. Java prend en charge l'héritage unique via l' extension de classe , dans laquelle une classe hérite directement des champs et méthodes accessibles d'une autre classe en étendant cette classe. Cependant, Java ne prend pas en charge l'héritage multiple via l'extension de classe.

Lors de l'affichage d'une hiérarchie d'héritage, vous pouvez facilement détecter l'héritage multiple par la présence d'un motif en losange. La figure 2 montre ce modèle dans le contexte d'un véhicule, d'un véhicule terrestre, d'un véhicule nautique et d'un aéroglisseur.

Le mot-clé extend

Java prend en charge l'extension de classe via le extendsmot - clé. Lorsqu'il est présent, extendsspécifie une relation parent-enfant entre deux classes. Ci-dessous, j'utilise extendspour établir une relation entre les classes Vehicleet Car, puis entre Accountet SavingsAccount:

Listing 1. Le extendsmot clé spécifie une relation parent-enfant

class Vehicle { // member declarations } class Car extends Vehicle { // inherit accessible members from Vehicle // provide own member declarations } class Account { // member declarations } class SavingsAccount extends Account { // inherit accessible members from Account // provide own member declarations }

Le extendsmot-clé est spécifié après le nom de la classe et avant un autre nom de classe. Le nom de classe avant extendsidentifie l'enfant et le nom de classe après extendsidentifie le parent. Il est impossible de spécifier plusieurs noms de classe après extendscar Java ne prend pas en charge l'héritage multiple basé sur les classes.

Ces exemples codifient est-une relations: Carest un spécialiste Vehicleet SavingsAccountest un spécialisé Account. Vehicleet Accountsont appelés classes de base , les classes parents ou superclasse . Caret SavingsAccountsont connus comme les classes dérivées , des classes d'enfants , ou les sous - classes .

Cours finaux

Vous pouvez déclarer une classe qui ne doit pas être étendue; par exemple pour des raisons de sécurité. En Java, nous utilisons le finalmot - clé pour empêcher l'extension de certaines classes. Préfixez simplement un en-tête de classe avec final, comme dans final class Password. Compte tenu de cette déclaration, le compilateur signalera une erreur si quelqu'un tente d'étendre Password.

Les classes enfants héritent des champs et méthodes accessibles de leurs classes parentes et d'autres ancêtres. Cependant, ils n'héritent jamais des constructeurs. Au lieu de cela, les classes enfants déclarent leurs propres constructeurs. De plus, ils peuvent déclarer leurs propres champs et méthodes pour les différencier de leurs parents. Considérez la liste 2.

Listing 2. Une Accountclasse parente

class Account { private String name; private long amount; Account(String name, long amount) { this.name = name; setAmount(amount); } void deposit(long amount) { this.amount += amount; } String getName() { return name; } long getAmount() { return amount; } void setAmount(long amount) { this.amount = amount; } }

Le listing 2 décrit une classe de compte bancaire générique qui a un nom et un montant initial, tous deux définis dans le constructeur. En outre, il permet aux utilisateurs d'effectuer des dépôts. (Vous pouvez effectuer des retraits en déposant des sommes d'argent négatives, mais nous ignorerons cette possibilité.) Notez que le nom du compte doit être défini lors de la création d'un compte.

Représentation des valeurs monétaires

compte de quelques centimes. Vous préférerez peut-être utiliser a doubleou a floatpour stocker des valeurs monétaires, mais cela peut entraîner des inexactitudes. Pour une meilleure solution, considérez BigDecimal, qui fait partie de la bibliothèque de classes standard de Java.

Le listing 3 présente une SavingsAccountclasse enfant qui étend sa Accountclasse parent.

Listing 3. Une SavingsAccountclasse enfant étend sa Accountclasse parent

class SavingsAccount extends Account { SavingsAccount(long amount) { super("savings", amount); } }

La SavingsAccountclasse est triviale car elle n'a pas besoin de déclarer des champs ou des méthodes supplémentaires. Il déclare cependant un constructeur qui initialise les champs de sa Accountsuperclasse. L'initialisation se produit lorsque Accountle constructeur de est appelé via le supermot - clé Java , suivi d'une liste d'arguments entre parenthèses.

Quand et où appeler super ()

Tout comme this()doit être le premier élément d'un constructeur qui appelle un autre constructeur de la même classe, super()doit être le premier élément d'un constructeur qui appelle un constructeur de sa superclasse. Si vous enfreignez cette règle, le compilateur signalera une erreur. Le compilateur signalera également une erreur s'il détecte un super()appel dans une méthode; n'appelle jamais super()qu'un constructeur.

Le listing 4 s'étend plus loin Accountavec une CheckingAccountclasse.

Listing 4. Une CheckingAccountclasse enfant étend sa Accountclasse parent

class CheckingAccount extends Account { CheckingAccount(long amount) { super("checking", amount); } void withdraw(long amount) { setAmount(getAmount() - amount); } }

CheckingAccount is a little more substantial than SavingsAccount because it declares a withdraw() method. Notice this method's calls to setAmount() and getAmount(), which CheckingAccount inherits from Account. You cannot directly access the amount field in Account because this field is declared private (see Listing 2).

super() and the no-argument constructor

If super() is not specified in a subclass constructor, and if the superclass doesn't declare a no-argument constructor, then the compiler will report an error. This is because the subclass constructor must call a no-argument superclass constructor when super() isn't present.

Class hierarchy example

I've created an AccountDemo application class that lets you try out the Account class hierarchy. First take a look at AccountDemo's source code.

Listing 5. AccountDemo demonstrates the account class hierarchy

class AccountDemo { public static void main(String[] args) { SavingsAccount sa = new SavingsAccount(10000); System.out.println("account name: " + sa.getName()); System.out.println("initial amount: " + sa.getAmount()); sa.deposit(5000); System.out.println("new amount after deposit: " + sa.getAmount()); CheckingAccount ca = new CheckingAccount(20000); System.out.println("account name: " + ca.getName()); System.out.println("initial amount: " + ca.getAmount()); ca.deposit(6000); System.out.println("new amount after deposit: " + ca.getAmount()); ca.withdraw(3000); System.out.println("new amount after withdrawal: " + ca.getAmount()); } }

The main() method in Listing 5 first demonstrates SavingsAccount, then CheckingAccount. Assuming Account.java, SavingsAccount.java, CheckingAccount.java, and AccountDemo.java source files are in the same directory, execute either of the following commands to compile all of these source files:

javac AccountDemo.java javac *.java

Execute the following command to run the application:

java AccountDemo

You should observe the following output:

account name: savings initial amount: 10000 new amount after deposit: 15000 account name: checking initial amount: 20000 new amount after deposit: 26000 new amount after withdrawal: 23000

Method overriding (and method overloading)

A subclass can override (replace) an inherited method so that the subclass's version of the method is called instead. An overriding method must specify the same name, parameter list, and return type as the method being overridden. To demonstrate, I've declared a print() method in the Vehicle class below.

Listing 6. Declaring a print() method to be overridden

class Vehicle { private String make; private String model; private int year; Vehicle(String make, String model, int year) { this.make = make; this.model = model; this.year = year; } String getMake() { return make; } String getModel() { return model; } int getYear() { return year; } void print() { System.out.println("Make: " + make + ", Model: " + model + ", Year: " + year); } }

Next, I override print() in the Truck class.

Listing 7. Overriding print() in a Truck subclass

class Truck extends Vehicle { private double tonnage; Truck(String make, String model, int year, double tonnage) { super(make, model, year); this.tonnage = tonnage; } double getTonnage() { return tonnage; } void print() { super.print(); System.out.println("Tonnage: " + tonnage); } }

Truck's print() method has the same name, return type, and parameter list as Vehicle's print() method. Note, too, that Truck's print() method first calls Vehicle's print() method by prefixing super. to the method name. It's often a good idea to execute the superclass logic first and then execute the subclass logic.

Calling superclass methods from subclass methods

In order to call a superclass method from the overriding subclass method, prefix the method's name with the reserved word super and the member access operator. Otherwise you will end up recursively calling the subclass's overriding method. In some cases a subclass will mask non-private superclass fields by declaring same-named fields. You can use super and the member access operator to access the non-private superclass fields.

To complete this example, I've excerpted a VehicleDemo class's main() method:

Truck truck = new Truck("Ford", "F150", 2008, 0.5); System.out.println("Make = " + truck.getMake()); System.out.println("Model = " + truck.getModel()); System.out.println("Year = " + truck.getYear()); System.out.println("Tonnage = " + truck.getTonnage()); truck.print();

The final line, truck.print();, calls truck's print() method. This method first calls Vehicle's print() to output the truck's make, model, and year; then it outputs the truck's tonnage. This portion of the output is shown below:

Make: Ford, Model: F150, Year: 2008 Tonnage: 0.5

Use final to block method overriding

Occasionally you might need to declare a method that should not be overridden, for security or another reason. You can use the final keyword for this purpose. To prevent overriding, simply prefix a method header with final, as in final String getMake(). The compiler will then report an error if anyone attempts to override this method in a subclass.

Method overloading vs overriding

Suppose you replaced the print() method in Listing 7 with the one below:

void print(String owner) { System.out.print("Owner: " + owner); super.print(); }

The modified Truck class now has two print() methods: the preceding explicitly-declared method and the method inherited from Vehicle. The void print(String owner) method doesn't override Vehicle's print() method. Instead, it overloads it.

Vous pouvez détecter une tentative de surcharge au lieu de remplacer une méthode au moment de la compilation en préfixant l'en-tête de méthode d'une sous-classe avec l' @Overrideannotation:

@Override void print(String owner) { System.out.print("Owner: " + owner); super.print(); }

La spécification @Overrideindique au compilateur que la méthode donnée remplace une autre méthode. Si quelqu'un tentait de surcharger la méthode à la place, le compilateur signalerait une erreur. Sans cette annotation, le compilateur ne rapporterait pas d'erreur car la surcharge de méthode est légale.

Quand utiliser @Override

Développez l'habitude de préfixer les méthodes de substitution avec @Override. Cette habitude vous aidera à détecter les erreurs de surcharge beaucoup plus tôt.