Ereditarietà in Java, parte 1: la parola chiave extends
Java supporta il riutilizzo delle classi attraverso l'ereditarietà e la composizione. Questo tutorial in due parti ti insegna come usare l'ereditarietà nei tuoi programmi Java. Nella parte 1 imparerai come usare la extends
parola chiave per derivare una classe figlia da una classe genitore, invocare costruttori e metodi della classe genitore e sovrascrivere metodi. Nella parte 2 visiterai java.lang.Object
, che è la superclasse di Java da cui eredita ogni altra classe.
Per completare il tuo apprendimento sull'ereditarietà, assicurati di controllare il mio suggerimento su Java che spiega quando usare la composizione rispetto all'ereditarietà. Imparerai perché la composizione è un importante complemento all'ereditarietà e come usarla per proteggerti dai problemi di incapsulamento nei tuoi programmi Java.
download Ottieni il codice Scarica il codice sorgente per applicazioni di esempio in questo tutorial. Creato da Jeff Friesen per JavaWorld.Ereditarietà Java: due esempi
L'ereditarietà è un costrutto di programmazione che gli sviluppatori di software utilizzano per stabilire relazioni is-a tra categorie. L'ereditarietà ci consente di derivare categorie più specifiche da categorie più generiche. La categoria più specifica è una sorta di categoria più generica. Ad esempio, un conto corrente è un tipo di conto in cui è possibile effettuare depositi e prelievi. Allo stesso modo, un camion è un tipo di veicolo utilizzato per il trasporto di oggetti di grandi dimensioni.
L'ereditarietà può scendere attraverso più livelli, portando a categorie sempre più specifiche. A titolo di esempio, la figura 1 mostra auto e camion che ereditano dal veicolo; station wagon ereditata dall'auto; e camion della spazzatura che eredita dal camion. Le frecce puntano da categorie "figlio" più specifiche (più in basso) a categorie "padre" meno specifiche (più in alto).

Questo esempio illustra l' ereditarietà singola in cui una categoria figlio eredita stato e comportamenti da una categoria padre immediata. Al contrario, l'ereditarietà multipla consente a una categoria figlio di ereditare stato e comportamenti da due o più categorie padre immediate. La gerarchia nella Figura 2 illustra l'ereditarietà multipla.

Le categorie sono descritte per classi. Java supporta l'ereditarietà singola tramite l' estensione di classe , in cui una classe eredita direttamente campi e metodi accessibili da un'altra classe estendendo quella classe. Tuttavia, Java non supporta l'ereditarietà multipla tramite l'estensione di classe.
Quando si visualizza una gerarchia di ereditarietà, è possibile rilevare facilmente l'ereditarietà multipla dalla presenza di un motivo a rombi. La Figura 2 mostra questo modello nel contesto di veicoli, veicoli terrestri, veicoli acquatici e hovercraft.
La parola chiave extends
Java supporta l'estensione della classe tramite la extends
parola chiave. Quando presente, extends
specifica una relazione genitore-figlio tra due classi. Di seguito utilizzo extends
per stabilire una relazione tra le classi Vehicle
e Car
, quindi tra Account
e SavingsAccount
:
Listato 1. La extends
parola chiave specifica una relazione genitore-figlio
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 }
La extends
parola chiave viene specificata dopo il nome della classe e prima del nome di un altro classe. Il nome della classe prima extends
identifica il bambino e il nome della classe dopo extends
identifica il genitore. È impossibile specificare più nomi di classe dopo extends
perché Java non supporta l'ereditarietà multipla basata su classi.
Questi esempi codificano le relazioni is-a: Car
è un specializzato Vehicle
ed SavingsAccount
è uno specializzato Account
. Vehicle
e Account
sono note come classi base , classi genitore o superclassi . Car
e SavingsAccount
sono note come classi derivate , classi figlie o sottoclassi .
Classi finali
Potreste dichiarare una classe che non dovrebbe essere estesa; ad esempio per motivi di sicurezza. In Java, utilizziamo la final
parola chiave per impedire che alcune classi vengano estese. Basta anteporre a un'intestazione di classe final
, come in final class Password
. Data questa dichiarazione, il compilatore segnalerà un errore se qualcuno tenta di estendere Password
.
Le classi figlie ereditano campi e metodi accessibili dalle loro classi padre e altri antenati. Tuttavia, non ereditano mai i costruttori. Invece, le classi figlie dichiarano i propri costruttori. Inoltre, possono dichiarare i propri campi e metodi per differenziarli dai loro genitori. Considera il listato 2.
Listato 2. Una Account
classe genitore
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; } }
Il Listato 2 descrive una classe di conto bancario generico che ha un nome e un importo iniziale, entrambi impostati nel costruttore. Inoltre, consente agli utenti di effettuare depositi. (Puoi effettuare prelievi depositando importi negativi di denaro, ma ignoreremo questa possibilità.) Nota che il nome dell'account deve essere impostato quando viene creato un account.
Rappresentare i valori di valuta
conteggio dei penny. Potresti preferire utilizzare a double
o a float
per memorizzare valori monetari, ma ciò può portare a imprecisioni. Per una soluzione migliore, considera BigDecimal
, che fa parte della libreria di classi standard di Java.
Il Listato 3 presenta una SavingsAccount
classe figlia che estende la sua Account
classe genitore.
Listato 3. Una SavingsAccount
classe figlia estende la sua Account
classe genitore
class SavingsAccount extends Account { SavingsAccount(long amount) { super("savings", amount); } }
La SavingsAccount
classe è banale perché non ha bisogno di dichiarare campi o metodi aggiuntivi. Tuttavia, dichiara un costruttore che inizializza i campi nella sua Account
superclasse. L'inizializzazione avviene quando Account
il costruttore di viene chiamato tramite la super
parola chiave Java , seguita da un elenco di argomenti tra parentesi.
Quando e dove chiamare super ()
Just as this()
must be the first element in a constructor that calls another constructor in the same class, super()
must be the first element in a constructor that calls a constructor in its superclass. If you break this rule the compiler will report an error. The compiler will also report an error if it detects a super()
call in a method; only ever call super()
in a constructor.
Listing 4 further extends Account
with a CheckingAccount
class.
Listing 4. A CheckingAccount
child class extends its Account
parent class
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.
È possibile rilevare un tentativo di sovraccarico invece di sovrascrivere un metodo in fase di compilazione prefissando l'intestazione del metodo di una sottoclasse con l' @Override
annotazione:
@Override void print(String owner) { System.out.print("Owner: " + owner); super.print(); }
La specifica @Override
indica al compilatore che il metodo dato sovrascrive un altro metodo. Se invece qualcuno tentasse di sovraccaricare il metodo, il compilatore segnalava un errore. Senza questa annotazione, il compilatore non segnalerebbe un errore perché il sovraccarico del metodo è legale.
Quando usare @Override
Sviluppa l'abitudine di anteporre ai metodi di override @Override
. Questa abitudine ti aiuterà a rilevare gli errori di sovraccarico molto prima.