Leitfaden zur Vererbung in Java

1. Übersicht

Eines der Grundprinzipien der objektorientierten Programmierung - die Vererbung - ermöglicht es uns, vorhandenen Code wiederzuverwenden oder einen vorhandenen Typ zu erweitern.

Einfach ausgedrückt, in Java kann eine Klasse eine andere Klasse und mehrere Schnittstellen erben, während eine Schnittstelle andere Schnittstellen erben kann.

In diesem Artikel beginnen wir mit der Notwendigkeit der Vererbung und gehen dazu über, wie die Vererbung mit Klassen und Schnittstellen funktioniert.

Anschließend wird erläutert, wie sich die Variablen- / Methodennamen und Zugriffsmodifikatoren auf die vererbten Mitglieder auswirken.

Und am Ende werden wir sehen, was es bedeutet, einen Typ zu erben.

2. Die Notwendigkeit der Vererbung

Stellen Sie sich vor, Sie bieten Ihren Kunden als Automobilhersteller mehrere Automodelle an. Auch wenn verschiedene Automodelle unterschiedliche Merkmale wie ein Schiebedach oder kugelsichere Fenster bieten könnten, würden sie alle gemeinsame Komponenten und Merkmale wie Motor und Räder enthalten.

Es ist sinnvoll , ein grundlegendes Design zu erstellen und es zu erweitern, um spezielle Versionen zu erstellen, anstatt jedes Automodell einzeln von Grund auf neu zu entwerfen.

In ähnlicher Weise können wir mit Vererbung eine Klasse mit grundlegenden Funktionen und Verhalten erstellen und ihre speziellen Versionen erstellen, indem wir Klassen erstellen, die diese Basisklasse erben. Auf die gleiche Weise können Schnittstellen vorhandene Schnittstellen erweitern.

Wir werden feststellen, dass mehrere Begriffe verwendet werden, um auf einen Typ zu verweisen, der von einem anderen Typ geerbt wird, insbesondere:

  • Ein Basistyp wird auch als Super- oder Elterntyp bezeichnet
  • Ein abgeleiteter Typ wird als erweiterter, untergeordneter oder untergeordneter Typ bezeichnet

3. Klassenvererbung

3.1. Eine Klasse erweitern

Eine Klasse kann eine andere Klasse erben und zusätzliche Mitglieder definieren.

Beginnen wir mit der Definition einer Basisklasse Auto :

public class Car { int wheels; String model; void start() { // Check essential parts } }

Die Klasse ArmoredCar kann die Mitglieder der Car- Klasse erben, indem das Schlüsselwort erweitert in ihrer Deklaration verwendet wird :

public class ArmoredCar extends Car { int bulletProofWindows; void remoteStartCar() { // this vehicle can be started by using a remote control } }

Wir können jetzt sagen, dass die ArmoredCar- Klasse eine Unterklasse von Car ist und die letztere eine Superklasse von ArmoredCar.

Klassen in Java unterstützen die Einzelvererbung . Die ArmoredCar- Klasse kann nicht mehrere Klassen erweitern.

Beachten Sie außerdem, dass eine Klasse ohne ein erweitertes Schlüsselwort implizit die Klasse java.lang.Object erbt .

Eine Unterklassenklasse erbt die nicht statisch geschützten und öffentlichen Mitglieder von der Oberklassenklasse. Darüber hinaus werden die Mitglieder mit Standard- und Paketzugriff vererbt, wenn sich die beiden Klassen im selben Paket befinden.

Andererseits werden die privaten und statischen Mitglieder einer Klasse nicht vererbt.

3.2. Zugriff auf übergeordnete Mitglieder aus einer untergeordneten Klasse

Um auf geerbte Eigenschaften oder Methoden zuzugreifen, können wir sie einfach direkt verwenden:

public class ArmoredCar extends Car { public String registerModel() { return model; } }

Beachten Sie, dass wir keinen Verweis auf die Oberklasse benötigen, um auf ihre Mitglieder zugreifen zu können.

4. Schnittstellenvererbung

4.1. Implementieren mehrerer Schnittstellen

Obwohl Klassen nur eine Klasse erben können, können sie mehrere Schnittstellen implementieren.

Stellen Sie sich vor, das ArmoredCar , das wir im vorhergehenden Abschnitt definiert haben, wird für einen Superspion benötigt. Daher dachte der Automobilhersteller daran, Flug- und Schwimmfunktionen hinzuzufügen:

public interface Floatable { void floatOnWater(); }
public interface Flyable { void fly(); }
public class ArmoredCar extends Car implements Floatable, Flyable{ public void floatOnWater() { System.out.println("I can float!"); } public void fly() { System.out.println("I can fly!"); } }

Im obigen Beispiel stellen wir fest, dass das Schlüsselwort implementiert wird , um von einer Schnittstelle zu erben.

4.2. Probleme mit Mehrfachvererbung

Java ermöglicht die Mehrfachvererbung über Schnittstellen.

Bis Java 7 war dies kein Problem. Schnittstellen konnten nur abstrakte Methoden definieren, dh Methoden ohne Implementierung. Wenn eine Klasse mehrere Schnittstellen mit derselben Methodensignatur implementierte, war dies kein Problem. Die implementierende Klasse hatte schließlich nur eine Methode zu implementieren.

Mal sehen, wie sich diese einfache Gleichung mit der Einführung von Standardmethoden in Schnittstellen mit Java 8 geändert hat .

Ab Java 8 können Schnittstellen Standardimplementierungen für ihre Methoden definieren (eine Schnittstelle kann weiterhin abstrakte Methoden definieren ). Dies bedeutet, dass die untergeordnete Klasse separate Implementierungen erben würde, wenn eine Klasse mehrere Schnittstellen implementiert, die Methoden mit derselben Signatur definieren. Das klingt komplex und ist nicht erlaubt.

Java verbietet die Vererbung mehrerer Implementierungen derselben Methoden, die in separaten Schnittstellen definiert sind.

Hier ist ein Beispiel:

public interface Floatable { default void repair() { System.out.println("Repairing Floatable object"); } }
public interface Flyable { default void repair() { System.out.println("Repairing Flyable object"); } }
public class ArmoredCar extends Car implements Floatable, Flyable { // this won't compile }

Wenn wir beide Schnittstellen implementieren möchten, müssen wir die Reparatur () -Methode überschreiben .

Wenn die Schnittstellen in den vorhergehenden Beispielen Variablen mit demselben Namen definieren, z. B. Dauer , können wir nicht auf sie zugreifen, ohne dem Variablennamen den Schnittstellennamen voranzustellen:

public interface Floatable { int duration = 10; }
public interface Flyable { int duration = 20; }
public class ArmoredCar extends Car implements Floatable, Flyable { public void aMethod() { System.out.println(duration); // won't compile System.out.println(Floatable.duration); // outputs 10 System.out.println(Flyable.duration); // outputs 20 } }

4.3. Interfaces Extending Other Interfaces

An interface can extend multiple interfaces. Here's an example:

public interface Floatable { void floatOnWater(); }
interface interface Flyable { void fly(); }
public interface SpaceTraveller extends Floatable, Flyable { void remoteControl(); }

An interface inherits other interfaces by using the keyword extends. Classes use the keyword implements to inherit an interface.

5. Inheriting Type

When a class inherits another class or interfaces, apart from inheriting their members, it also inherits their type. This also applies to an interface that inherits other interfaces.

This is a very powerful concept, which allows developers to program to an interface (base class or interface), rather than programming to their implementations.

For example, imagine a condition, where an organization maintains a list of the cars owned by its employees. Of course, all employees might own different car models. So how can we refer to different car instances? Here's the solution:

public class Employee { private String name; private Car car; // standard constructor }

Because all derived classes of Car inherit the type Car, the derived class instances can be referred by using a variable of class Car:

Employee e1 = new Employee("Shreya", new ArmoredCar()); Employee e2 = new Employee("Paul", new SpaceCar()); Employee e3 = new Employee("Pavni", new BMW());

6. Hidden Class Members

6.1. Hidden Instance Members

What happens if both the superclass and subclass define a variable or method with the same name? Don't worry; we can still access both of them. However, we must make our intent clear to Java, by prefixing the variable or method with the keywords this or super.

The this keyword refers to the instance in which it's used. The super keyword (as it seems obvious) refers to the parent class instance:

public class ArmoredCar extends Car { private String model; public String getAValue() { return super.model; // returns value of model defined in base class Car // return this.model; // will return value of model defined in ArmoredCar // return model; // will return value of model defined in ArmoredCar } }

A lot of developers use this and super keywords to explicitly state which variable or method they're referring to. However, using them with all members can make our code look cluttered.

6.2. Hidden Static Members

What happens when our base class and subclasses define static variables and methods with the same name? Can we access a static member from the base class, in the derived class, the way we do for the instance variables?

Let's find out using an example:

public class Car { public static String msg() { return "Car"; } }
public class ArmoredCar extends Car { public static String msg() { return super.msg(); // this won't compile. } }

No, we can't. The static members belong to a class and not to instances. So we can't use the non-static super keyword in msg().

Since static members belong to a class, we can modify the preceding call as follows:

return Car.msg();

Consider the following example, in which both the base class and derived class define a static method msg() with the same signature:

public class Car { public static String msg() { return "Car"; } }
public class ArmoredCar extends Car { public static String msg() { return "ArmoredCar"; } }

Here's how we can call them:

Car first = new ArmoredCar(); ArmoredCar second = new ArmoredCar();

For the preceding code, first.msg() will output “Car and second.msg() will output “ArmoredCar”. The static message that is called depends on the type of the variable used to refer to ArmoredCar instance.

7. Conclusion

In diesem Artikel haben wir einen Kernaspekt der Java-Sprache behandelt - die Vererbung.

Wir haben gesehen, wie Java die Einzelvererbung mit Klassen und die Mehrfachvererbung mit Schnittstellen unterstützt, und die Feinheiten der Funktionsweise des Mechanismus in der Sprache erörtert.

Wie immer ist der vollständige Quellcode für die Beispiele auf GitHub verfügbar.