Abstrakte Methoden in Java Klassen und Interfaces

Ensinger

Lt. Junior Grade
Registriert
Sep. 2013
Beiträge
335
Servus, Einsteigerfrage von einem Informatikstudent. Ich lerne gerade für die Info 2 Klausur. Unter anderem lernen wir, dass sowas wie abstrakte Klassen, Methoden und Interfaces existieren. Abstrakte Klassen sind zb. Klassen, die selbst nicht instanziiert werden können sondern nur dafür dienen, entsprechende Methoden und Attribute an Unterklassen zu vererben.

Dabei gibt es auch sowas wie abstrakte Methoden. Diese erhält eine Klasse, wenn sie als Unterklasse von einer abstrakten Oberklasse erbt oder wenn sie ein Interface implementiert, welches abstrakte Methoden mitliefert.

Abstrakte Methoden sind dadurch definiert, dass sie nicht implementiert sind, also keinen Methodenrumpf haben. Die konkrete Implementierung findet dann erst in der erbenden Unterklasse bzw. der Klasse, die das Interface implementiert statt.

Aber welchen Nutzen hat denn dann die abstrakte Methode? Wenn ich den Methodenrumpf, also die Implementierung in der Unterklasse ohnehin anlegen muss, dann kann ich mir das Vererben doch auch direkt sparen? Wieso gebe ich in der Oberklasse eine Methode an, die dann erst in der Unterklasse implementiert wird?
 
Ensinger schrieb:
Aber welchen Nutzen hat denn dann die abstrakte Methode? Wenn ich den Methodenrumpf, also die Implementierung in der Unterklasse ohnehin anlegen muss, dann kann ich mir das Vererben doch auch direkt sparen?
Nö, durch das Vererben hat die erbende Klasse ja auch den Typ der abstrakten Klasse. Damit das funktioniert, muss aber klar sein, dass auch die erbende Klasse die Funktion implementiert.
Das heißt, dass du mit der abstrakten Klasse festlegst, dass alle erbenden Klassen diese Funktion implementieren müssen.
 
Eigentlich werden die abstrakten Methoden genauso verwendet wie bei einem Interface. Du hast einfach ein gewisses Set an Funktionalität, die sich alle erbenden Klassen teilen, aber spezifische Funktionen muss dann jede Klasse für sich implementieren. Ein Beispiel in Java selbst findest du z.B. bei der Klasse "Number".
 
Als klassisches Beispiel für sowas wird häufig eine abstrakte Klasse "Auto" verwendet, die Methoden wie "MotorStarten()" bietet. Da "Auto" aber abstrakt ist und nicht selbst instanziiert werden kann und somit auch nicht definiert ist wie genau "MotorStarten()" tatsächlich funktionieren soll, kann sie auch (noch) nicht implementiert werden.

Eben genau diese Implementierung geschieht in den erbenden Klassen wie zB "VerbrennerAuto" und "ElektroAuto". Durch die abstrakte Basisklasse ist aber definiert, dass sie jeweils die "MotorStarten()" Methode implementieren müssen, weil das in diesem Beispiel nun mal zu einem "Auto" gehört. Würdest du in der "Auto" Klasse die Methode "MotorStarten()" nicht bereits vorgeben, könnte es auch erbende Klassen geben, die eben keine MotorStarten() Methode beinhalten oder sie heißt vielleicht "StarteMotor()". Würdest du nun munter Objekte vom allgemeinen Typ "Auto" durch die Gegend schieben, könntest du dich nicht darauf verlassen, dass sie tatsächlich eine "MotorStarten()" Methode mitbringen und es knallt, wenn du versuchst, diese Methode aufzurufen, sie aber nicht existiert.

Im Prinzip gibt man mit so einem Konzept die kleinste gemeinsame Schnittmenge innerhalb der Klassenstruktur wieder, als abstrakte Oberklasse innerhalb der Vererbungshierarchie.

Interfaces funktionieren ganz ähnlich. In einem Interface werden auch Methoden vorgegeben, die dann von der nutzenden Klasse implementiert werden. Interfaces sind jedoch klassenübergreifend und damit nicht zwingend auf - in diesem Fall - Autos beschränkt. Mit einem Interface inkl. "MotorStarten()" Methode, könnte man beispielsweise Motorrad, Flugzeuge, Rasierer, Mixer, Kassenbänder und Ventilatoren ausstatten. Obwohl sie klassenmässig nichts miteinander gemein haben, haben sie alle einen Motor, den man starten kann ;)


Vereinfacht ausgedrückt kann man durch diese Techniken beispielsweise eine Schleife programmieren, die durch eine Liste von Objekten mit Basisklasse "Autos" läuft und bei jedem den Motor startet. Das kann man sich ruhig bildlich vorstellen - ein Typ, der auf einem Parkplatz von Auto zu Auto geht und den Motor startet, egal ob es ein VerbrennerAuto oder ein ElektroAuto ist. Im Falle von Interfaces ist das genauso, nur dass man eben nicht über einen Parkplatz läuft wo nur Autos stehen, sondern ein Mixer, 3 Rasierer, 7 Flugzeuge und 4 Ventilatoren, bei denen man überall "MotorStarten()" aufruft - und jedes Objekt davon geht mit dieser Methode individuell um, anhand ihrer eigenen spezifischen Implementierung.

Das Stichwort lautet also Vereinheitlichung. Natürlich kann man das auch links liegen lassen und einfach so jeder Klasse die entsprechende Methode mitgeben, aber man kann es eben auch vergessen und kann vor allem nicht mehr klassen-neutral über eine Liste unterschiedlichster Objekte mit unterschiedlichen Klassen iterieren. Um bei dem Parkplatzbeispiel zu bleiben: Du würdest von Auto zu Auto gehen, aber du musst jedes Mal suchen wo und wie die Kiste angeht, weil die Methode mal "MotorStarten()" heißt, mal "StarteMotor()" und mal "MotorStaten()" <-- man beachte den Typo....
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: DefconDev
Hallo zusammen,

im Grunde nichts "falsches" was bisher geschrieben wurde. Ich habe aber einen anderen Blickwinkel auf Interfaces und Abstract Classes.

tl;dr
Interface: Design - Vertrag nach Außen
Abstrakte Klasse/Methode: Implementierung - Platzhalter, der später zum Leben erweckt wird.

Generelle Anmerkung: Das Schlüsselwort/Feature "default" lasse ich mal bewusst weg.

Beispiel:

Mit einem Interface möchte ich einen Vertrag definieren mit dem sich die Außenwelt sicher sein kann, dass ein Objekt diese Eigenschaften erfüllt. Welche Implementierung dahintersteht, ist vorerst egal; Hauptsache der Vertrag wird eingehalten.

Mit einer Abstrakten Klasse/Methode realisiere ich eher eine zentrale Stelle von Dingen, die "später implementiert" werden.
Wie ja schon bereits erwähnt, gibt es keine Instanzen einer abstrakten Klasse. Allerdings kommt es oft vor, dass eine Logik - bis auf ein Detail - für alle erbenden Klassen passt. Diese kann in der abstrakten Klasse platziert werden. Das Detail lasse ich abstrakt, d.h. ich tue erst einmal so, als ob es da wäre: eine abstrakte Methode. Wie sich genau dieses Detail verhalten muss, wird dann später in der erbenden Klasse definiert.

Anbei ein sinnfreies Beispiel zur Verdeutlichung:
  • Der Außenwelt teile ich durch das Interface mit, dass ich eine Liste von Zahlen aufsummieren kann: Totalizer.sumUp
  • Die Logik wie eine Liste aufzusummieren ist (iteriere durch alle Elemente) ist immer gleich und demnach in einer abstrakten Klasse BaseTotalizer platziert, den Vertrag nach Außen erfüllt (implements).
  • Das Detail (ok, ziemlich unsinnig), wie die einzelnen Elemente der Liste "modifiziert" werden sollen bevor sie aufsummiert werden, wird offen gelassen, indem eine abstrakte Methode getFactor genutzt wird.
  • Wie getFactor sich nun konkret verhalten soll, bestimmen die konkreten Klassen, die BaseTotalizer erweitern.

Bash:
$ java Totalizer.java
SingleTotalizer: 14
DoubleTotalizer: 28

Java:
interface Totalizer {

    static void main(String[] args) {
       List<Integer> numbers = List.of(1, 3, 3, 7);
       System.out.println(SingleTotalizer.class.getSimpleName() + ": " + new SingleTotalizer().sumUp(numbers));
       System.out.println(DoubleTotalizer.class.getSimpleName() + ": " + new DoubleTotalizer().sumUp(numbers));
    }

    int sumUp(List<Integer> numbersToSumUp);

    abstract class BaseTotalizer implements Totalizer {
       @Override
       public int sumUp(List<Integer> numbersToSumUp) {
          int result = 0;
          for (Integer number : numbersToSumUp) {
             result += number * getFactor();
          }
          return result;
       }

       protected abstract int getFactor();
    }

    class SingleTotalizer extends BaseTotalizer {
       @Override
       protected int getFactor() {
          return 1;
       }
    }

    class DoubleTotalizer extends BaseTotalizer {
       @Override
       protected int getFactor() {
          return 2;
       }
    }
}

Eine andere Idee für das Beispiel wäre:

Es sollen nur Zahlen aufsummiert werden, die ein Kriterium erfüllen. Die "Prüfmethode" kann vorerst abstrakt bleiben. Die Logik, die entscheidet ob das Kriterium erfüllt ist, wird in den Kindklassen hinterlegt (z.B. {Even, Odd}Totalizer).
 
Uuuuuund @Ensinger ? Hast du es jetzt verstanden? Wenn ja, gehört es eigentlich zum guten Ton, sich in irgendeiner Form zu melden. Ein "Danke" erwartet man heutzutage ja schon gar nicht mehr, aber irgendwas wäre schon ganz nett.... Sonst kannst du beim nächsten Mal ja auch eine KI deiner Wahl fragen. Die hat wenigstens keine Freizeit, die sie für eine Antwort opfern muss.
 
Zurück
Oben