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 extends
nyckelordet 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).

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.

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 extends
nyckelordet. När det är närvarande, extends
specificeras ett förhållande mellan förälder och barn mellan två klasser. Nedan använder jag för extends
att skapa en relation mellan klasser Vehicle
och Car
, och sedan mellan Account
och SavingsAccount
:
Listning 1. extends
Nyckelordet 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 extends
nyckelordet anges efter klassnamnet, och innan ett klassnamnet. Klassnamnet före extends
identifierar barnet och klassnamnet efter extends
identifierar föräldern. Det är omöjligt att ange flera klassnamn efter extends
eftersom Java inte stöder klassbaserad multipel arv.
Dessa exempel kodifierar is-a-relationer: Car
är specialiserat Vehicle
och SavingsAccount
är specialiserat Account
. Vehicle
och Account
är kända som basklasser , föräldraklasser eller superklasser . Car
och 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 final
nyckelordet 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 Account
fö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 double
eller a för float
att 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 SavingsAccount
barnklass som utökar sin Account
föräldraklass.
Listning 3. En SavingsAccount
barnklass utökar sin Account
föräldraklass
class SavingsAccount extends Account { SavingsAccount(long amount) { super("savings", amount); } }
Den SavingsAccount
klassen ä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 Account
superklass. Initialisering händer när Account
konstruktorn anropas via Java: s super
nyckelord, 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 Account
med en CheckingAccount
klass.
Listning 4. En CheckingAccount
barnklass utökar sin Account
fö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 SavingsAccount
fö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 amount
fältet direkt Account
eftersom 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-argument
konstruktör, rapporterar kompilatorn ett fel. Detta beror på att underklasskonstruktören måste ringa en no-argument
superklasskonstruktö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 @Override
kommentaren:
@Override void print(String owner) { System.out.print("Owner: " + owner); super.print(); }
Att specificera @Override
berä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.