Overerving in Java, deel 1: het sleutelwoord extends

Java ondersteunt hergebruik van klassen door middel van overerving en compositie. Deze tweedelige tutorial leert u hoe u overerving in uw Java-programma's kunt gebruiken. In deel 1 leert u hoe u het extendssleutelwoord gebruikt om een ​​onderliggende klasse af te leiden uit een bovenliggende klasse, constructors en methoden voor bovenliggende klassen aan te roepen en methoden te overschrijven. In deel 2 ga je op tournee java.lang.Object, Java's superklasse waarvan elke andere klasse erft.

Om je kennis over overerving te voltooien, moet je mijn Java-tip lezen waarin wordt uitgelegd wanneer compositie versus overerving moet worden gebruikt. U leert waarom compositie een belangrijke aanvulling is op overerving en hoe u deze kunt gebruiken om problemen met inkapseling in uw Java-programma's te voorkomen.

download Download de code Download de broncode voor voorbeeldtoepassingen in deze tutorial. Gemaakt door Jeff Friesen voor JavaWorld.

Overerving van Java: twee voorbeelden

Overerving is een programmeerconstructie die softwareontwikkelaars gebruiken om is-a-relaties tussen categorieën tot stand te brengen. Overerving stelt ons in staat om meer specifieke categorieën af te leiden uit meer algemene categorieën. De meer specifieke categorie is een soort van de meer generieke categorie. Een lopende rekening is bijvoorbeeld een soort rekening waarop u stortingen en opnames kunt doen. Evenzo is een vrachtwagen een soort voertuig dat wordt gebruikt voor het vervoeren van grote voorwerpen.

Overerving kan via meerdere niveaus dalen, wat leidt tot steeds specifiekere categorieën. Als voorbeeld toont figuur 1 auto en vrachtwagen die van een voertuig zijn geërfd; stationwagen erft van auto; en vuilniswagen erft van vrachtwagen. Pijlen wijzen van meer specifieke "kind" -categorieën (lager naar beneden) naar minder specifieke "ouder" -categorieën (hoger).

Jeff Friesen

Dit voorbeeld illustreert een enkele overerving waarbij een onderliggende categorie de staat en het gedrag van een direct bovenliggende categorie erft. Daarentegen stelt meervoudige overerving een onderliggende categorie in staat om staat en gedrag van twee of meer directe bovenliggende categorieën over te nemen. De hiërarchie in Figuur 2 illustreert meervoudige overerving.

Jeff Friesen

Categorieën worden beschreven door klassen. Java ondersteunt enkelvoudige overerving via klasse-extensie , waarbij de ene klasse toegankelijke velden en methoden direct overneemt van een andere klasse door die klasse uit te breiden. Java ondersteunt echter geen meervoudige overerving via klasse-extensie.

Wanneer u een overervingshiërarchie bekijkt, kunt u gemakkelijk meerdere overervingen detecteren door de aanwezigheid van een ruitpatroon. Figuur 2 toont dit patroon in de context van voertuig, landvoertuig, watervoertuig en hovercraft.

Het trefwoord verlengt

Java ondersteunt klasse-extensie via het extendstrefwoord. Specificeert, indien aanwezig, extendseen ouder-kindrelatie tussen twee klassen. Hieronder gebruik ik extendsom een ​​relatie te leggen tussen klassen Vehicleen Car, en vervolgens tussen Accounten SavingsAccount:

Listing 1. Het extendssleutelwoord specificeert een ouder-kindrelatie

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 }

Het extendssleutelwoord wordt gespecificeerd na de klassenaam en vóór een andere klassenaam. De klassenaam ervoor extendsidentificeert het kind en de klassenaam erna extendsidentificeert de ouder. Het is onmogelijk om meerdere klassenamen daarna op te geven, extendsomdat Java geen ondersteuning biedt voor op klassen gebaseerde meervoudige overerving.

Deze voorbeelden codificeren is-a-relaties: Caris gespecialiseerd Vehicleen SavingsAccountis gespecialiseerd Account. Vehicleen Accountstaan ​​bekend als basisklassen , bovenliggende klassen of superklassen . Caren SavingsAccountstaan ​​bekend als afgeleide klassen , onderliggende klassen of subklassen .

Laatste lessen

U zou een klasse kunnen aangeven die niet mag worden verlengd; bijvoorbeeld om veiligheidsredenen. In Java gebruiken we het finalsleutelwoord om te voorkomen dat sommige klassen worden uitgebreid. Voeg eenvoudig een voorvoegsel voor een klassenheader toe final, zoals in final class Password. Gegeven deze verklaring zal de compiler een fout rapporteren als iemand probeert uit te breiden Password.

Onderliggende klassen erven toegankelijke velden en methoden van hun bovenliggende klassen en andere voorouders. Ze erven echter nooit constructeurs. In plaats daarvan declareren kindklassen hun eigen constructeurs. Bovendien kunnen ze hun eigen velden en methoden aangeven om ze te onderscheiden van hun ouders. Overweeg lijst 2.

Listing 2. Een Accountbovenliggende klasse

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; } }

Lijst 2 beschrijft een generieke bankrekeningklasse met een naam en een initieel bedrag, die beide zijn ingesteld in de constructor. Het laat gebruikers ook stortingen doen. (U kunt opnames doen door negatieve bedragen te storten, maar we zullen deze mogelijkheid negeren.) Merk op dat de accountnaam moet worden ingesteld wanneer een account wordt aangemaakt.

Vertegenwoordigen valutawaarden

telling van centen. Misschien geeft u er de voorkeur aan om a doubleof a floatte gebruiken om geldwaarden op te slaan, maar dat kan tot onnauwkeurigheden leiden. Overweeg voor een betere oplossing, BigDecimaldie deel uitmaakt van Java's standaard klassenbibliotheek.

Lijst 3 presenteert een SavingsAccountonderliggende klasse die de Accountbovenliggende klasse uitbreidt .

Lijst 3. Een SavingsAccountonderliggende klasse breidt de Accountbovenliggende klasse uit

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

De SavingsAccountklasse is triviaal omdat het geen extra velden of methoden hoeft te declareren. Het declareert echter wel een constructor die de velden in zijn Accountsuperklasse initialiseert . Initialisatie vindt plaats wanneer Accountde constructor van wordt aangeroepen via Java's supersleutelwoord, gevolgd door een argumentenlijst tussen haakjes.

Wanneer en waar super () bellen

Net zoals this()het eerste element in een constructor moet zijn dat een andere constructor in dezelfde klasse aanroept, super()moet het eerste element in een constructor zijn dat een constructor in zijn superklasse aanroept. Als je deze regel overtreedt, zal de compiler een fout rapporteren. De compiler zal ook een fout rapporteren als hij een super()aanroep in een methode detecteert ; Roep alleen super()een constructor aan.

Listing 4 breidt zich verder uit Accountmet een CheckingAccountklasse.

Lijst 4. Een CheckingAccountonderliggende klasse breidt de Accountbovenliggende klasse uit

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.

U kunt een poging tot overbelasting detecteren in plaats van een methode tijdens het compileren te overschrijven door de methodeheader van een subklasse vooraf te laten gaan met de @Overrideannotatie:

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

Specificeren @Overridevertelt de compiler dat de gegeven methode een andere methode overschrijft. Als iemand in plaats daarvan zou proberen de methode te overbelasten, zou de compiler een fout rapporteren. Zonder deze annotatie zou de compiler geen fout rapporteren omdat overbelasting van de methode legaal is.

Wanneer @Override gebruiken

Ontwikkel de gewoonte om overschrijvende methoden vooraf te zetten met @Override. Deze gewoonte zal u helpen om fouten bij overbelasting veel eerder op te sporen.