Arv i Java, del 1: Nyckelordet utvidgar

Java stöder klassåteranvändning genom arv och komposition. Denna tvådelade handledning lär dig hur du använder arv i dina Java-program. I del 1 lär du dig hur du använder extendsnyckelordet för att härleda en underordnad klass från en överordnad klass, åberopa överordnade klasskonstruktörer och metoder och åsidosätta metoder. I del 2 kommer du att turnera java.lang.Object, vilket är Java: s superklass som alla andra klasser ärver från.

För att slutföra ditt lärande om arv, se till att kolla in mitt Java-tips som förklarar när du ska använda komposition vs arv. Du lär dig varför komposition är ett viktigt komplement till arv och hur du använder den för att skydda dig mot problem med inkapsling i dina Java-program.

ladda ner Skaffa koden Ladda ner källkoden till exempel applikationer i denna handledning. Skapad av Jeff Friesen för JavaWorld.

Java-arv: Två exempel

Arv är en programmeringskonstruktion som mjukvaruutvecklare använder för att skapa is-a-relationer mellan kategorier. Arv gör det möjligt för oss att härleda mer specifika kategorier från mer generiska. Den mer specifika kategorin är en typ av den mer generiska kategorin. Till exempel är ett checkkonto ett slags konto där du kan göra insättningar och uttag. På samma sätt är en lastbil ett slags fordon som används för att transportera stora föremål.

Arv kan gå ner på flera nivåer, vilket leder till allt mer specifika kategorier. Som ett exempel visar figur 1 bil och lastbil som ärver från fordon; kombi som ärver från bilen; och sopbil som ärver från lastbil. Pilarna pekar från mer specifika "underordnade" kategorier (lägre ned) till mindre specifika "överordnade" kategorier (högre upp).

Jeff Friesen

Detta exempel illustrerar enstaka arv där en underordnad kategori ärver tillstånd och beteenden från en omedelbar föräldrakategori. Däremot möjliggör flera arv en barnkategori att ärva tillstånd och beteenden från två eller flera omedelbara föräldrakategorier. Hierarkin i figur 2 illustrerar flera arv.

Jeff Friesen

Kategorier beskrivs av klasser. Java stöder enstaka arv genom klasstillägg , där en klass ärver direkt tillgängliga fält och metoder från en annan klass genom att utöka den klassen. Java stöder dock inte flera arv genom klassförlängning.

När du visar en arvshierarki kan du enkelt upptäcka flera arv genom närvaron av ett diamantmönster. Figur 2 visar detta mönster i samband med fordon, landfordon, vattenfordon och svävare.

Nyckelordet utvidgar

Java stöder klasstillägg via extendsnyckelordet. När det är närvarande, extendsspecificeras ett förhållande mellan förälder och barn mellan två klasser. Nedan använder jag för extendsatt skapa en relation mellan klasser Vehicleoch Car, och sedan mellan Accountoch SavingsAccount:

Listning 1. extendsNyckelordet anger ett förhållande mellan förälder och barn

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 }

Den extendsnyckelordet anges efter klassnamnet, och innan ett klassnamnet. Klassnamnet före extendsidentifierar barnet och klassnamnet efter extendsidentifierar föräldern. Det är omöjligt att ange flera klassnamn efter extendseftersom Java inte stöder klassbaserad multipel arv.

Dessa exempel kodifierar is-a-relationer: Carär specialiserat Vehicleoch SavingsAccountär specialiserat Account. Vehicleoch Accountär kända som basklasser , föräldraklasser eller superklasser . Caroch SavingsAccountär kända som härledda klasser , barnklasser eller underklasser .

Avslutande klasser

Du kan förklara en klass som inte bör förlängas. till exempel av säkerhetsskäl. I Java använder vi finalnyckelordet för att förhindra att vissa klasser utvidgas. Lägg bara till en klassrubrik med final, som i final class Password. Med tanke på denna deklaration kommer kompilatorn att rapportera ett fel om någon försöker förlänga Password.

Barnklasser ärver tillgängliga fält och metoder från sina föräldraklasser och andra förfäder. De ärver dock aldrig konstruktörer. Istället förklarar barnklasser sina egna konstruktörer. Dessutom kan de förklara sina egna fält och metoder för att skilja dem från sina föräldrar. Överväg att lista 2.

Lista 2. En Accountföräldraklass

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

Listning 2 beskriver en generisk bankkontoklass som har ett namn och ett initialt belopp, som båda anges i konstruktören. Det låter också användare göra insättningar. (Du kan göra uttag genom att sätta in negativa pengar men vi ignorerar denna möjlighet.) Observera att kontonamnet måste ställas in när ett konto skapas.

Representerar valutavärden

antal pennies. Du kanske föredrar att använda a doubleeller a för floatatt lagra monetära värden, men att göra det kan leda till felaktigheter. För en bättre lösning, överväga BigDecimal, vilket är en del av Java: s standardklassbibliotek.

Listning 3 presenterar en SavingsAccountbarnklass som utökar sin Accountföräldraklass.

Listning 3. En SavingsAccountbarnklass utökar sin Accountföräldraklass

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

Den SavingsAccountklassen är trivialt, eftersom det inte behöver deklarera ytterligare fält eller metoder. Det förklarar dock en konstruktör som initialiserar fälten i sin Accountsuperklass. Initialisering händer när Accountkonstruktorn anropas via Java: s supernyckelord, följt av en argumentlista inom parentes.

När och var man ska ringa super ()

Precis som this()det första elementet i en konstruktör som kallar en annan konstruktör i samma klass super()måste vara det första elementet i en konstruktör som kallar en konstruktör i sin superklass. Om du bryter mot denna regel kommer kompilatorn att rapportera ett fel. Kompilatorn rapporterar också ett fel om den upptäcker ett super()samtal i en metod. ring bara super()in en konstruktör.

Listning 4 sträcker sig ytterligare Accountmed en CheckingAccountklass.

Listning 4. En CheckingAccountbarnklass utökar sin Accountföräldraklass

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

CheckingAccountär lite mer omfattande än SavingsAccountför att den förklarar en withdraw()metod. Lägg märke till den här metodens samtal till setAmount()och getAmount()som CheckingAccountärver från Account. Du kan inte komma åt amountfältet direkt Accounteftersom detta fält deklareras private(se Listing 2).

super () och konstruktören utan argument

Om super()inte anges i en underklasskonstruktör, och om superklassen inte förklarar en no-argumentkonstruktör, rapporterar kompilatorn ett fel. Detta beror på att underklasskonstruktören måste ringa en no-argumentsuperklasskonstruktör när den super()inte är närvarande.

Exempel på klasshierarki

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.

Du kan upptäcka ett försök att överbelasta istället för att åsidosätta en metod vid sammanställningstid genom att prefixa en underklasss metodhuvud med @Overridekommentaren:

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

Att specificera @Overrideberättar för kompilatorn att den givna metoden åsidosätter en annan metod. Om någon försökte överbelasta metoden istället skulle kompilatorn rapportera ett fel. Utan denna anteckning skulle kompilatorn inte rapportera ett fel eftersom metodöverbelastning är lagligt.

När ska du använda @Override

Utveckla vanan att prefixera övergripande metoder med @Override. Denna vana hjälper dig att upptäcka överbelastningsfel mycket tidigare.