Nasljeđivanje u Javi, 1. dio: Ključna riječ extends

Java podržava ponovnu upotrebu klase nasljeđivanjem i sastavljanjem. Ovaj dvodijelni vodič podučava vas kako koristiti nasljeđivanje u svojim Java programima. U 1. dijelu naučit ćete kako koristiti extendsključnu riječ za izvođenje podređene klase iz roditeljske klase, pozivanje konstruktora i metoda nadređene klase i nadjačavanje metoda. U 2. dijelu krenut ćete u obilazak java.lang.Object, što je Javina superklasa iz koje nasljeđuje svaka druga klasa.

Da biste dovršili učenje o nasljeđivanju, svakako pogledajte moj Java savjet koji objašnjava kada koristiti sastav u odnosu na nasljeđivanje. Naučit ćete zašto je sastav važan dodatak nasljeđivanju i kako ga koristiti za zaštitu od problema s enkapsulacijom u vašim Java programima.

preuzimanje Preuzmite kod Preuzmite izvorni kod za primjere aplikacija u ovom vodiču. Stvorio Jeff Friesen za JavaWorld.

Nasljeđivanje Java: dva primjera

Nasljeđivanje je programska konstrukcija koju programeri softvera koriste za uspostavljanje odnosa između kategorija. Nasljeđivanje nam omogućuje izvođenje specifičnijih kategorija iz općenitijih. Specifičnija kategorija je vrsta više generičke kategorije. Na primjer, tekući račun je vrsta računa na kojem možete vršiti pologe i isplate. Slično tome, kamion je vrsta vozila koje se koristi za vuču velikih predmeta.

Nasljeđivanje se može spustiti kroz više razina, što dovodi do sve specifičnijih kategorija. Kao primjer, slika 1. prikazuje nasljeđivanje automobila i kamiona od vozila; karavan nasljeđuje iz automobila; i kamion za smeće koji nasljeđuje od kamiona. Strelice pokazuju od specifičnijih kategorija "podređenih" (niže dolje) prema manje specifičnim "roditeljskim" kategorijama (gore gore).

Jeff Friesen

Ovaj primjer ilustrira pojedinačno nasljeđivanje u kojem podređena kategorija nasljeđuje stanje i ponašanje od jedne neposredne roditeljske kategorije. Suprotno tome, višestruko nasljeđivanje omogućuje podređenoj kategoriji nasljeđivanje stanja i ponašanja iz dvije ili više neposrednih roditeljskih kategorija. Hijerarhija na slici 2 ilustrira višestruko nasljeđivanje.

Jeff Friesen

Kategorije su opisane po klasama. Java podržava pojedinačno nasljeđivanje putem ekstenzije klase , u kojoj jedna klasa izravno nasljeđuje dostupna polja i metode iz druge klase proširujući tu klasu. Java, međutim, ne podržava višestruko nasljeđivanje putem ekstenzije klase.

Kada pregledavate hijerarhiju nasljeđivanja, lako možete otkriti više nasljeđa prisutnošću dijamantnog uzorka. Slika 2 prikazuje ovaj obrazac u kontekstu vozila, kopnenog vozila, vodnog vozila i lebdjelice.

Ključna riječ extends

Java podržava proširenje klase putem extendsključne riječi. Kad je prisutan, extendsnavodi odnos roditelja i djeteta između dva razreda. Ispod koristim extendsza uspostavljanje veze između klasa Vehiclei Car, a zatim između Accounti SavingsAccount:

Popis 1. extendsKljučna riječ određuje odnos roditelja i djeteta

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 }

extendsKljučna riječ je određen po imenu klase i pred drugim imenom klase. Naziv klase prije extendsidentificira dijete i ime klase nakon extendsidentificira roditelja. Nemoguće je navesti više naziva klasa nakon extendsšto Java ne podržava višestruko nasljeđivanje na temelju klasa.

Ovi primjeri kodificirati je-a odnosi: Carje specijalizirana Vehiclei SavingsAccountje specijalizirana Account. Vehiclea Accountpoznati su kao osnovni razredi , roditeljski razredi ili superrazredi . Cara SavingsAccountpoznati su kao izvedeni razredi , podređeni ili podrazredi .

Završna nastava

Možete proglasiti klasu koja se ne bi trebala produžavati; na primjer iz sigurnosnih razloga. U Javi koristimo finalključnu riječ kako bismo spriječili proširenje nekih klasa. Jednostavno dodajte zaglavlju klase prefiks final, kao u final class Password. S obzirom na ovu izjavu, sastavljač će prijaviti pogrešku ako netko pokuša produžiti Password.

Dijete klase nasljeđuju dostupna polja i metode od svojih roditeljskih razreda i drugih predaka. Međutim, oni nikad ne nasljeđuju konstruktore. Umjesto toga, podređene klase deklariraju vlastite konstruktore. Nadalje, mogu se izjasniti o vlastitim poljima i metodama kako bi ih razlikovali od roditelja. Razmislite o popisu 2.

Popis 2. AccountRoditeljska klasa

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

Popis 2 opisuje generičku klasu bankovnog računa koja ima ime i početni iznos, a koji su postavljeni u konstruktoru. Također, korisnicima omogućuje depozite. (Isplate možete izvršiti polaganjem negativnih iznosa novca, ali mi ćemo zanemariti tu mogućnost.) Imajte na umu da se naziv računa mora postaviti kada se račun kreira.

Prikazivanje vrijednosti valute

broja groša. Možda biste radije koristili a doubleili a floatza pohranu novčanih vrijednosti, ali to može dovesti do netočnosti. Za bolje rješenje razmotrite BigDecimalkoji je dio Javine standardne knjižnice klasa.

Popis 3 predstavlja SavingsAccountpodređenu klasu koja proširuje Accountroditeljsku klasu.

Popis 3. SavingsAccountPodređena klasa proširuje Accountroditeljsku klasu

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

SavingsAccountKlasa je trivijalan, jer ne treba proglasiti dodatna polja ili metode. Međutim, deklarira konstruktor koji inicijalizira polja u svojoj Accountsuperklasi. Inicijalizacija se događa kada se Accountkonstruktor poziva putem Javine superključne riječi, nakon čega slijedi popis argumenata u zagradama.

Kada i gdje nazvati super ()

Kao što this()mora biti prvi element u konstruktoru koji poziva drugi konstruktor u istoj klasi, tako super()mora biti i prvi element u konstruktoru koji poziva konstruktor u svojoj superklasi. Ako prekršite ovo pravilo, sastavljač će prijaviti pogrešku. Prevoditelj će također prijaviti pogrešku ako otkrije super()poziv u metodi; samo ikad pozvati super()konstruktor.

Popis 4 dalje se proširuje Accounts CheckingAccountklasom.

Popis 4. CheckingAccountPodređena klasa proširuje Accountroditeljsku klasu

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

CheckingAccountje malo značajniji nego SavingsAccountzato što deklarira withdraw()metodu. Primijetite pozive ove metode na setAmount()i getAmount(), koji CheckingAccountnasljeđuje od Account. Ne možete izravno pristupiti amountpolju Accountjer je to polje deklarirano private(vidi Popis 2).

super () i konstruktor bez argumenata

Ako super()nije navedeno u konstruktoru podklase, a ako superklasa ne deklarira no-argumentkonstruktor, tada će prevoditelj prijaviti pogrešku. To je zato što konstruktor podklase mora pozvati no-argumentkonstruktor superklase kada super()nije prisutan.

Primjer hijerarhije razreda

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.

Možete otkriti pokušaj preopterećenja umjesto da nadjačate metodu u vrijeme sastavljanja dodavanjem prefiksa zaglavlju metode podklase s @Overridenapomenom:

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

Navođenje @Overridenavodi prevoditelju da zadata metoda nadjačava drugu metodu. Ako bi netko umjesto toga pokušao preopteretiti metodu, sastavljač bi prijavio pogrešku. Bez ove napomene, kompajler ne bi prijavio pogrešku jer je preopterećenje metode legalno.

Kada koristiti @Override

Razvijte naviku dodavanja prefiksnih metoda pomoću @Override. Ova navika pomoći će vam da prije otkrijete pogreške preopterećenja.