Vererbung in Java, Teil 1: Das Schlüsselwort extens

Java unterstützt die Wiederverwendung von Klassen durch Vererbung und Komposition. In diesem zweiteiligen Tutorial erfahren Sie, wie Sie die Vererbung in Ihren Java-Programmen verwenden. In Teil 1 erfahren Sie, wie Sie mit dem extendsSchlüsselwort eine untergeordnete Klasse von einer übergeordneten Klasse ableiten, Konstruktoren und Methoden für übergeordnete Klassen aufrufen und Methoden überschreiben. In Teil 2 werden Sie eine Tour machen java.lang.Object, die Javas Superklasse ist, von der jede andere Klasse erbt.

Lesen Sie in meinem Java-Tipp, wann Komposition und Vererbung verwendet werden sollen, um das Erlernen der Vererbung abzuschließen. Sie erfahren, warum Komposition eine wichtige Ergänzung zur Vererbung ist und wie Sie sie zum Schutz vor Problemen mit der Kapselung in Ihren Java-Programmen verwenden können.

download Code abrufen Laden Sie den Quellcode herunter, zum Beispiel Anwendungen in diesem Tutorial. Erstellt von Jeff Friesen für JavaWorld.

Java-Vererbung: Zwei Beispiele

Vererbung ist ein Programmierkonstrukt, mit dem Softwareentwickler Beziehungen zwischen Kategorien herstellen. Die Vererbung ermöglicht es uns, spezifischere Kategorien von allgemeineren abzuleiten. Die spezifischere Kategorie ist eine Art allgemeinere Kategorie. Ein Girokonto ist beispielsweise eine Art Konto, auf dem Sie Ein- und Auszahlungen vornehmen können. Ebenso ist ein LKW eine Art Fahrzeug, das zum Ziehen großer Gegenstände verwendet wird.

Die Vererbung kann über mehrere Ebenen erfolgen, was zu immer spezifischeren Kategorien führt. Als Beispiel zeigt 1 Auto und LKW, die vom Fahrzeug erben; Kombi vom Auto erben; und Müllwagen, der vom LKW erbt. Pfeile zeigen von spezifischeren "untergeordneten" Kategorien (weiter unten) zu weniger spezifischen "übergeordneten" Kategorien (weiter oben).

Jeff Friesen

Dieses Beispiel zeigt eine einzelne Vererbung, bei der eine untergeordnete Kategorie den Status und das Verhalten einer unmittelbaren übergeordneten Kategorie erbt. Im Gegensatz dazu ermöglicht eine Mehrfachvererbung einer untergeordneten Kategorie, Status und Verhalten von zwei oder mehr unmittelbaren übergeordneten Kategorien zu erben. Die Hierarchie in Abbildung 2 zeigt die Mehrfachvererbung.

Jeff Friesen

Kategorien werden durch Klassen beschrieben. Java unterstützt die Einzelvererbung durch Klassenerweiterung , bei der eine Klasse direkt zugängliche Felder und Methoden von einer anderen Klasse erbt, indem sie diese Klasse erweitert. Java unterstützt jedoch keine Mehrfachvererbung durch Klassenerweiterung.

Wenn Sie eine Vererbungshierarchie anzeigen, können Sie die Mehrfachvererbung leicht anhand eines Rautenmusters erkennen. Abbildung 2 zeigt dieses Muster im Zusammenhang mit Fahrzeug, Landfahrzeug, Wasserfahrzeug und Luftkissenfahrzeug.

Das Extended-Schlüsselwort

Java unterstützt die Klassenerweiterung über das extendsSchlüsselwort. Wenn vorhanden, wird extendseine Eltern-Kind-Beziehung zwischen zwei Klassen angegeben. Im Folgenden stelle ich extendseine Beziehung zwischen Klassen Vehicleund Carund dann zwischen Accountund her SavingsAccount:

Listing 1. Das extendsSchlüsselwort gibt eine Eltern-Kind-Beziehung an

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 }

Das extendsSchlüsselwort wird nach dem Klassennamen und vor einem anderen Klassennamen angegeben. Der Klassenname vor extendsidentifiziert das Kind und der Klassenname nach extendsidentifiziert das Elternteil. Es ist unmöglich, mehrere Klassennamen danach anzugeben, extendsda Java keine klassenbasierte Mehrfachvererbung unterstützt.

Diese Beispiele kodifiziert ist-ein - Beziehungen: Careine ist spezialisiert Vehicleund SavingsAccountist ein spezialisiert Account. Vehicleund Accountwerden als Basisklassen , übergeordnete Klassen oder Oberklassen bezeichnet . Carund SavingsAccountwerden als abgeleitete Klassen , untergeordnete Klassen oder Unterklassen bezeichnet .

Abschlussklassen

Sie können eine Klasse deklarieren, die nicht erweitert werden soll. zum Beispiel aus Sicherheitsgründen. In Java verwenden wir das finalSchlüsselwort, um zu verhindern, dass einige Klassen erweitert werden. Stellen Sie einem Klassenkopf einfach finalwie in vor final class Password. In Anbetracht dieser Deklaration meldet der Compiler einen Fehler, wenn jemand versucht, ihn zu erweitern Password.

Untergeordnete Klassen erben zugängliche Felder und Methoden von ihren übergeordneten Klassen und anderen Vorfahren. Sie erben jedoch niemals Konstruktoren. Stattdessen deklarieren untergeordnete Klassen ihre eigenen Konstruktoren. Darüber hinaus können sie ihre eigenen Felder und Methoden angeben, um sie von ihren Eltern zu unterscheiden. Betrachten Sie Listing 2.

Listing 2. Eine Accountübergeordnete 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; } }

Listing 2 beschreibt eine generische Bankkontoklasse mit einem Namen und einem Anfangsbetrag, die beide im Konstruktor festgelegt sind. Außerdem können Benutzer Einzahlungen vornehmen. (Sie können Auszahlungen vornehmen, indem Sie negative Geldbeträge einzahlen. Diese Möglichkeit wird jedoch ignoriert.) Beachten Sie, dass der Kontoname bei der Erstellung eines Kontos festgelegt werden muss.

Darstellung von Währungswerten

Anzahl der Pennys. Möglicherweise bevorzugen Sie die Verwendung von a doubleoder a floatzum Speichern von Geldwerten. Dies kann jedoch zu Ungenauigkeiten führen. Betrachten Sie für eine bessere Lösung, BigDecimalwelche Teil der Standardklassenbibliothek von Java ist.

Listing 3 zeigt eine SavingsAccountuntergeordnete Klasse, die die Accountübergeordnete Klasse erweitert.

Listing 3. Eine SavingsAccountuntergeordnete Klasse erweitert ihre Accountübergeordnete Klasse

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

Die SavingsAccountKlasse ist trivial, da keine zusätzlichen Felder oder Methoden deklariert werden müssen. Es deklariert jedoch einen Konstruktor, der die Felder in seiner AccountOberklasse initialisiert . Die Initialisierung erfolgt, wenn Accountder Konstruktor über das Java- superSchlüsselwort aufgerufen wird , gefolgt von einer Argumentliste in Klammern.

Wann und wo man super anruft ()

So wie this()es das erste Element in einem Konstruktor sein muss, das einen anderen Konstruktor in derselben Klasse aufruft, super()muss es das erste Element in einem Konstruktor sein, das einen Konstruktor in seiner Oberklasse aufruft. Wenn Sie gegen diese Regel verstoßen, meldet der Compiler einen Fehler. Der Compiler meldet auch einen Fehler, wenn er einen super()Aufruf in einer Methode erkennt . Rufen Sie immer nur super()einen Konstruktor auf.

Listing 4 wird Accountum eine CheckingAccountKlasse erweitert.

Listing 4. Eine CheckingAccountuntergeordnete Klasse erweitert ihre Accountübergeordnete Klasse

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.

Sie können einen Versuch zum Überladen erkennen, anstatt eine Methode zur Kompilierungszeit zu überschreiben, indem Sie dem Methodenheader einer Unterklasse die @OverrideAnmerkung voranstellen:

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

Durch Angabe @Overridewird dem Compiler mitgeteilt , dass die angegebene Methode eine andere Methode überschreibt. Wenn jemand versucht, die Methode zu überladen, meldet der Compiler einen Fehler. Ohne diese Anmerkung würde der Compiler keinen Fehler melden, da das Überladen von Methoden zulässig ist.

Wann soll @Override verwendet werden?

Entwickeln Sie die Gewohnheit, überschreibenden Methoden mit Präfix zu setzen @Override. Diese Angewohnheit hilft Ihnen, Überlastungsfehler viel früher zu erkennen.