Abstrakte Klassen in Java

1. Übersicht

Es gibt viele Fälle bei der Implementierung eines Vertrags, in denen wir einige Teile der Implementierung verschieben möchten, um sie später abzuschließen. Wir können dies in Java leicht durch abstrakte Klassen erreichen.

In diesem Tutorial lernen wir die Grundlagen abstrakter Klassen in Java kennen und in welchen Fällen sie hilfreich sein können .

2. Schlüsselkonzepte für abstrakte Klassen

Bevor wir uns mit der Verwendung einer abstrakten Klasse befassen, schauen wir uns ihre wichtigsten Merkmale an :

  • Wir definieren eine abstrakte Klasse mit dem Modifikator abstract vor dem Schlüsselwort class
  • Eine abstrakte Klasse kann in Unterklassen unterteilt, aber nicht instanziiert werden
  • Wenn eine Klasse eine oder mehrere abstrakte Methoden definiert, muss die Klasse selbst als abstrakt deklariert werden
  • Eine abstrakte Klasse kann sowohl abstrakte als auch konkrete Methoden deklarieren
  • Eine von einer abstrakten Klasse abgeleitete Unterklasse muss entweder alle abstrakten Methoden der Basisklasse implementieren oder selbst abstrakt sein

Um diese Konzepte besser zu verstehen, erstellen wir ein einfaches Beispiel.

Lassen Sie unsere abstrakte Basisklasse die abstrakte API eines Brettspiels definieren:

public abstract class BoardGame { //... field declarations, constructors public abstract void play(); //... concrete methods }

Dann können wir eine Unterklasse erstellen, die die Wiedergabemethode implementiert :

public class Checkers extends BoardGame { public void play() { //... implementation } }

3. Wann werden abstrakte Klassen verwendet?

Nun wollen wir einige typische Szenarien zu analysieren , wo wir abstrakte Klassen über Schnittstellen bevorzugen sollten und konkrete Klassen:

  • Wir möchten einige allgemeine Funktionen an einem Ort (Wiederverwendung von Code) zusammenfassen, die mehrere verwandte Unterklassen gemeinsam nutzen
  • Wir müssen teilweise eine API definieren, die unsere Unterklassen leicht erweitern und verfeinern können
  • Die Unterklassen müssen eine oder mehrere gängige Methoden oder Felder mit geschützten Zugriffsmodifikatoren erben

Denken Sie daran, dass all diese Szenarien gute Beispiele für die vollständige, vererbungsbasierte Einhaltung des Open / Closed-Prinzips sind.

Da die Verwendung abstrakter Klassen implizit Basistypen und Subtypen behandelt, nutzen wir außerdem den Polymorphismus.

Beachten Sie, dass die Wiederverwendung von Code ein sehr zwingender Grund für die Verwendung abstrakter Klassen ist, solange die Beziehung „is-a“ innerhalb der Klassenhierarchie erhalten bleibt.

Und Java 8 fügt eine weitere Falte mit Standardmethoden hinzu, die manchmal die Notwendigkeit ersetzen kann, eine abstrakte Klasse insgesamt zu erstellen.

4. Eine Beispielhierarchie von Dateireadern

Schauen wir uns ein anderes Beispiel an, um die Funktionalität abstrakter Klassen besser zu verstehen.

4.1. Definieren einer abstrakten Basisklasse

Wenn wir also mehrere Arten von Dateireadern haben möchten, können wir eine abstrakte Klasse erstellen, die zusammenfasst, was beim Lesen von Dateien üblich ist:

public abstract class BaseFileReader { protected Path filePath; protected BaseFileReader(Path filePath) { this.filePath = filePath; } public Path getFilePath() { return filePath; } public List readFile() throws IOException { return Files.lines(filePath) .map(this::mapFileLine).collect(Collectors.toList()); } protected abstract String mapFileLine(String line); }

Beachten Sie, dass wir filePath geschützt haben, damit die Unterklassen bei Bedarf darauf zugreifen können. Noch wichtiger ist, dass wir etwas ungeschehen gemacht haben: Wie man tatsächlich eine Textzeile aus dem Inhalt der Datei analysiert .

Unser Plan ist einfach: Während unsere konkreten Klassen nicht jeweils eine spezielle Methode zum Speichern des Dateipfads oder zum Durchlaufen der Datei haben, haben sie jeweils eine spezielle Methode zum Transformieren jeder Zeile.

Auf den ersten Blick scheint BaseFileReader unnötig zu sein. Es ist jedoch die Grundlage für ein sauberes, leicht erweiterbares Design. Daraus können wir auf einfache Weise verschiedene Versionen eines Dateireaders implementieren, die sich auf ihre einzigartige Geschäftslogik konzentrieren können .

4.2. Unterklassen definieren

Eine natürliche Implementierung ist wahrscheinlich eine, die den Inhalt einer Datei in Kleinbuchstaben konvertiert:

public class LowercaseFileReader extends BaseFileReader { public LowercaseFileReader(Path filePath) { super(filePath); } @Override public String mapFileLine(String line) { return line.toLowerCase(); } }

Oder eine andere, die den Inhalt einer Datei in Großbuchstaben konvertiert:

public class UppercaseFileReader extends BaseFileReader { public UppercaseFileReader(Path filePath) { super(filePath); } @Override public String mapFileLine(String line) { return line.toUpperCase(); } }

Wie wir aus diesem einfachen Beispiel sehen können, kann sich jede Unterklasse auf ihr einzigartiges Verhalten konzentrieren, ohne dass andere Aspekte des Dateilesens angegeben werden müssen.

4.3. Verwenden einer Unterklasse

Schließlich unterscheidet sich die Verwendung einer Klasse, die von einer abstrakten Klasse erbt, nicht von jeder anderen konkreten Klasse:

@Test public void givenLowercaseFileReaderInstance_whenCalledreadFile_thenCorrect() throws Exception { URL location = getClass().getClassLoader().getResource("files/test.txt") Path path = Paths.get(location.toURI()); BaseFileReader lowercaseFileReader = new LowercaseFileReader(path); assertThat(lowercaseFileReader.readFile()).isInstanceOf(List.class); }

Der Einfachheit halber befindet sich die Zieldatei im Ordner src / main / resources / files . Daher haben wir einen Anwendungsklassenlader verwendet, um den Pfad der Beispieldatei abzurufen. Schauen Sie sich unser Tutorial zu Klassenladern in Java an.

5. Schlussfolgerung

In diesem kurzen Artikel haben wir die Grundlagen abstrakter Klassen in Java kennengelernt und gelernt, wann sie zum Erreichen der Abstraktion und zum Einkapseln der gemeinsamen Implementierung an einem einzigen Ort verwendet werden müssen .

Wie üblich sind alle in diesem Tutorial gezeigten Codebeispiele auf GitHub verfügbar.