W3docs

Java Abstraktion

Implementierungsdetails hinter abstrakten Typen in Java verbergen – mit abstrakten Klassen und Interfaces.

Abstraktion ist das vierte Grundprinzip der OOP: Beschreiben Sie was etwas tut, ohne sich auf das wie festzulegen. Sie deklarieren die Operationen, die ein Typ unterstützt, überlassen die Implementierung den konkreten Klassen und schreiben den Rest Ihres Programms gegen den abstrakten Typ. Dieses Kapitel bietet einen konzeptionellen Überblick — Javas zwei Mechanismen dafür, abstract class und interface, erhalten jeweils ein eigenes Kapitel.

Die zwei Fragen

Jede Typdeklaration in Java beantwortet zwei Fragen:

  1. Was können Aufrufer mit Werten dieses Typs tun? (seine API)
  2. Wie wird jede dieser Operationen implementiert? (ihr Rumpf)

Eine konkrete Klasse beantwortet beide. Ein abstrakter Typ beantwortet nur die erste und überlässt die zweite den Untertypen:

public interface Shape {
  double area();         // what — every Shape has an area
}

public class Circle implements Shape {
  double r;
  public Circle(double r) { this.r = r; }
  public double area() { return Math.PI * r * r; }   // how
}
public class Square implements Shape {
  double side;
  public Square(double side) { this.side = side; }
  public double area() { return side * side; }
}

Shape sagt: „Jede Form hat eine Fläche." Circle und Square definieren, wie diese berechnet wird. Code, der ein Shape entgegennimmt, interessiert das nicht:

double sumAreas(List<Shape> shapes) {
  double sum = 0;
  for (Shape s : shapes) sum += s.area();
  return sum;
}

Diese Funktion ist über die Abstraktion geschlossen. Sie funktioniert heute für Circle und Square, morgen für Triangle und in sechs Monaten für Polygon. Keiner der neuen Typen erfordert eine Änderung an sumAreas.

Javas zwei Mechanismen

MechanismusWas er bietetWann man ihn verwendet
abstrakte KlasseEine partielle Klasse — einige Methoden abstrakt, andere mit Rumpf, dazu Felder und KonstruktorenWenn Untertypen Zustand und Infrastruktur-Code teilen
InterfaceEin reiner (oder nahezu reiner) Vertrag — Methoden, die implementierende Klassen bereitstellen müssen; kein Instanz-ZustandWenn Untertypen nur eine Menge von Operationen teilen müssen und sonst nichts gemeinsam haben

Eine Klasse erweitert eine abstrakte Klasse. Eine Klasse kann viele Interfaces implementieren. Diese Asymmetrie lenkt viele Entwurfsentscheidungen: Wenn Sie so etwas wie „Mehrfachvererbung" benötigen, sind Interfaces meist die Antwort.

Abstrakte Klassen — partielle Implementierung

abstract an einer Klasse bedeutet: „Diese Klasse kann nicht direkt instanziiert werden — nur Unterklassen können es." abstract an einer Methode bedeutet: „Kein Rumpf hier; jede konkrete Unterklasse muss einen bereitstellen":

public abstract class Shape {
  public abstract double area();      // every Shape must define this

  // a concrete method, shared across all shapes
  public final String describe() {
    return getClass().getSimpleName() + " area=" + area();
  }
}

new Shape() ist ein Compilerfehler. new Circle() funktioniert. Innerhalb von describe leitet der Aufruf area() an die Implementierung der tatsächlichen Unterklasse weiter — derselbe Polymorphismus-Mechanismus wie bei jeder überschriebenen Methode.

Verwenden Sie eine abstrakte Klasse, wenn Untertypen tatsächlich Code teilen. Wenn Sie denselben Helfer in drei Unterklassen schreiben, ist das ein Signal, ihn in die Elternklasse zu verlagern.

Interfaces — der Vertrag

Ein Interface deklariert Operationen und überlässt die Implementierung vollständig demjenigen, der es implementiert:

public interface Comparable<T> {
  int compareTo(T other);
}

public class Money implements Comparable<Money> {
  private final long cents;
  public int compareTo(Money other) {
    return Long.compare(this.cents, other.cents);
  }
}

Jetzt funktioniert Money überall dort, wo ein Comparable erwartet wird — Collections.sort(...), TreeMap, Arrays.sort(...), Ihre eigenen generischen Algorithmen. Die Standardbibliothek und Ihr Code einigen sich auf Comparable als gemeinsame Abstraktion; keine Seite weiß von der anderen.

Die große Mehrheit von Javas Standard-Interfaces (List, Map, Iterable, Runnable, Function, Comparator, AutoCloseable) funktioniert so: ein kleiner, fokussierter Vertrag, in den viele konkrete Klassen eingebunden werden.

Abstraktion als Entwurfshebel

Der mechanische Teil der Abstraktion — das Schlüsselwort abstract, die interface-Deklaration — ist klein. Der schwierige Teil ist die Wahl, welche Abstraktionen definiert werden sollen. Drei Muster, die immer wieder auftauchen:

  • Strategie. Definieren Sie ein Interface für „den Algorithmus." Verschiedene Implementierungen tauschen den Algorithmus aus, ohne den Code zu ändern, der ihn verwendet. Comparator ist das klassische Beispiel.
  • Schablonenmethode. Eine abstrakte Klasse implementiert den Gesamtablauf, mit abstrakten Methoden an den Variationspunkten. Unterklassen füllen die spezifischen Schritte aus. HttpServlets service-Methode ist ein bekanntes Beispiel.
  • Plugin / Erweiterungspunkt. Eine Bibliothek veröffentlicht ein Interface; Benutzer-Code implementiert es; die Bibliothek ruft es zurück. Servlet APIs, JDBC-Treiber, Springs BeanPostProcessor.

In jedem Fall ist der Gewinn derselbe: Code, der von der Abstraktion abhängt, ist gegen Änderungen an den Implementierungen geschlossen und offen für später hinzugefügte Implementierungen.

Kapselung vs. Abstraktion

Diese zwei sind enge Verwandte und werden oft verwechselt.

  • Kapselung verbirgt die Implementierung einer bestimmten Klasse (private Felder, kontrollierte Methoden). Es ist eine klassen-interne Angelegenheit.
  • Abstraktion verbirgt, mit welcher Klasse Sie über einen gemeinsamen Vertrag kommunizieren. Es ist eine klassen-externe Angelegenheit.

Eine Klasse mit private-Feldern und einer übersichtlichen öffentlichen API ist gekapselt, aber noch nicht abstrahiert — Aufrufer hängen immer noch von dieser spezifischen Klasse ab. Ersetzen Sie den Typ an der API-Grenze durch ein Interface, und Aufrufer hängen stattdessen vom Vertrag ab. Jetzt können Sie Implementierungen austauschen.

Um sie zusammenwirken zu sehen, lesen Sie das Kapitel zur Kapselung: Kapselung sperrt eine einzelne Klasse ab, Abstraktion ermöglicht es Aufrufern, zu ignorieren, welche Klasse sie halten.

Häufige Fehler

Einige Fallen erwischen Einsteiger bei der Abstraktion:

  • Versuchen, einen abstrakten Typ zu instanziieren. new Shape() ist ein Compilerfehler, wenn Shape abstract oder ein Interface ist. Sie instanziieren einen konkreten Untertyp (new Circle(2)) und weisen ihn der abstrakten Referenz zu.
  • Zu früh abstrahieren. Ein Interface mit genau einer Implementierung, das „für den Fall, dass wir später eine weitere brauchen" geschrieben wurde, ist meist unnötiger Ballast. Fügen Sie die Abstraktion hinzu, wenn die zweite Implementierung tatsächlich auftaucht oder wenn Sie wirklich zwei Module entkoppeln müssen. Vorzeitige Abstraktion fügt Indirektion hinzu, ohne Flexibilität zu bieten.
  • Den konkreten Typ durchscheinen lassen. Ein Feld oder Parameter als ArrayList statt als List oder als HashMap statt als Map zu deklarieren, bindet Aufrufer an diese spezifische Klasse und hebt die Abstraktion auf. Bevorzugen Sie den abstraktesten Typ, der noch ausdrückt, was Sie benötigen.
  • „Kein Rumpf" mit „tut nichts" verwechseln. Eine abstrakte Methode hat keinen Rumpf, weil Unterklassen einen liefern müssen. Eine konkrete Methode mit einem leeren Rumpf ist eine echte Methode, die nichts tut — ein sehr anderer Vertrag.

Ein ausgearbeitetes Beispiel

Führen Sie das folgende Programm aus. Es verwendet beide Mechanismen: eine abstrakte Shape-Klasse mit gemeinsamem describe-Code und ein reines Greeter-Interface. Die erwartete Ausgabe ist:

Circle area=12.566370614359172
Square area=9.0
total = 21.57

Dear Alice,
hey Alice!

Beachten Sie, dass totalArea und die Greeter-Schleife niemals Circle, Square, FormalGreeter oder CasualGreeter benennen — sie sprechen nur mit den Abstraktionen Shape und Greeter.

java— editable, runs on the server

Was kommt als Nächstes

Das nächste Kapitel behandelt die konkreten Mechanismen von abstrakten Klassen — abstrakte Methoden, was sie einer Unterklasse ermöglichen zu erben, und wann man sie Interfaces vorzieht.

Übungen

Übung
Was beschreibt am besten den Unterschied zwischen Kapselung und Abstraktion?
Was beschreibt am besten den Unterschied zwischen Kapselung und Abstraktion?
Übung
Wann sollten Sie ein Interface statt einer abstrakten Klasse wählen?
Wann sollten Sie ein Interface statt einer abstrakten Klasse wählen?
Was this page helpful?