Objektorientierte Programmmierung am Beispiel von Java . Abstrakte Klassen und Interfaces.

Keylearnings:

  • Was sind abstrakte Klassen?
  • Was sind abstrakte Methoden?
  • Was sind JAVA Interfaces?
  • Was ist die Java Mehrfachvererbung?
  • Wann interfaces und wann abstrakte Klassen verwenden?

Hast du schonmal ein U.V.O gesehen?

Wie du weißt nicht was ein U.V.O ist?

Naja, so ein undefiniertes Vierbeiner-Objekt halt.

Wir haben uns im letzten Artikel dieser Serie über die Vererbung in der objektorientierte Programmierung unterhalten.

Durch Vererbung haben wir einen Verbeiner zu einem Hund oder eine Katze werden lassen.

Wir hätten aber genauso gut ein Vierbeiner-Objekt erzeugen können, das weder ein Hund, noch eine Katze oder ein sonstiges Vierbeiner-Tier ist.

Mit anderen Worten: Wir hätten problemlos ein U.V.O kreieren können.

Und ganau das können wir mit Hilfe von abstrakten Klassen und Java Interfaces verhindern.

Was ist eine abstrakte Klasse?

In Programmier-Deutschen Worten bedeutet unser Problem, dass es keine Möglichkeit geben darf Instanzen der Klasse Vierbeiner anlegen zukönnen.

Und genau DAS können wir mit Hilfe von abstrakten Klassen erreichen.

Abstrakte Klassen sind genau die Klassen, von denen keine Instanz erzeugt werden kann.

Der Versuch über das Schlüsselwort new eine Instanz einer abstrakten Klasse zu erzeugen wird mit einem Fehler quittiert.

Wie werden abstrakte Klassen angelegt?

Gaanz einfach! Hierfür gibt es das Schlüsselwort abstract.

Schauen wir uns das anhand der Klasse Vierbeiner mal gleich in der Praxis an.

1: public abstract class Vierbeiner{
	
2:   private String vierbeinerName;
	
3:   public void rennen(){
4:    	System.out.println(vierbeinerName+" rennt los!");
5:    }
    
6:    public void setName(String pName){
7:    	vierbeinerName = pName;
8:    }
9:}

Einzigster Unterschied zu einer normalen Klasse ist das Wörtchen abstract gleich zu Beginnn der Klassendefinition.

Eine abstrakte Klasse besteht genauso wie eine „normale“ Klasse aus Objekt- und Klassenvariablen bzw. Objekt- und Klassenmethoden.

Unsere Vierbeiner Klasse hat als Objektvariable einen String, dem wir mit der setter Methode setName den Namen unseres Tierchen zuweisen können.

Außerdem haben wir eine Methode rennen() implementiert, die eine Bildschirmausgabe erzeugt.

Versuch jetzt mal von der Klasse Vierbeiner eine Instanz zu erstellen.

Vierbeiner tier = new Vierbeiner();

Keine Sorge! Du erhälst eine Fehlermeldung, dass von abstrakten Klassen keine Instanzen erzeugt werden können. Aber das ist ja genau das was wir wollen!

Aber wie verwenden wir abstrakte Klassen?

Naja, in einer Vererbungshierarchie funktioniert unsere Klasse Vierbeiner nach wie vor wie am Schnürchen. So kann unsere Katze Wilhelm noch immer von der abstrakten Oberklasse Vierbeiner erben.

1: public class Katze extends Vierbeiner{
		
2:	public Katze(){}
		
3:	public Katze(String pName){
4:	   super(pName);
5:	}
6: }

Auch eine abstrakte Klasse kann, wie wir es bereits hier gelernt haben, ihre Eigenschaften an eine Unterklasse vererben.

Die Unterklasse von Vierbeiner ist Katze, und da Katze keine abstrakte Klasse ist, können wir von dieser eine Instanz anlegen.

1: Katze wilhelm = new Katze("Wilhelm");
2: wilhelm.rennen();

Okay, was denkst du? Welche Auswirkung hat diese Zeile?

Eigentlich ist das nur neuer Wein in alten Schläuchen.

Wir erzeugen die Instanz einer Katze indem wir den Konstruktor von Katze mit dem Argument Wilhelm aufrufen, der über das Schlüsselwort super den Konstruktor der Oberklasse Vierbeiner aufruft und der Objektvariablen vierbeinerName den Wert Wilhelm zuweist.

In Zeile zwei darf sich Wilhelm dann noch ein wenig austoben. Wir erhalten die Programmausgabe:

Wilhelm rennt los!

Durch die Deklaration einer Klasse mit abstract zwingen wir den Prorammierer, der deine Vierbeiner Klasse verwenden möchte, diese durch Vererbung zu einem Hund, einer Katze oder einem sonstigen Vierbeiner zu spezifizieren.

Im folgenden Video zeige ich dir wie du die einzelnen Schritte in Eclipse umsetzt.

Eine abstrakte Klasse kann sogenannte abstrakte Methoden enthalten. Was das jetzt schon wieder ist, schauen wir uns als nächstes an.

Abstrakte Methoden

Kannst du dir einen Vierbeiner vorstellen, der nicht rennen kann?

objektorientierte Programmierung java interfaces

Also wenn du mich fragst, dann finde ich, dass ein solcher Vierbeiner definitiv eine Leistungslücke hat.

Und das wollen wir verhindern und genau dafür sind abstrakten Methoden da.

Natürlich sieht das Rennen bei einem Elefanten anders aus als wie bei einem Leoparden. Daher macht es Sinn sowohl in der Klasse Elefant, als auch in der Klasse Leopard eine eigene rennen() Methode zu implementieren.

Wichtig ist uns nur, dass in jeder Unterklasse von Vierbeiner eine solche Methode existiert. Denn ein Vierbeiner, der nicht rennen kann ist unvollständig!

Warte! Ich zeig dir was ich meine!!

Wir wandeln die Funktion rennen() in eine abstrakte Methode um.

Worin unsterscheidet sich eine abstrakte von einer „normalen“ Methode?

Es gibt genau zwei Unsterschiede. Den ersten wirst du ziemlich langweilig finden. Du musst bei einer abstrakten Methode ganz einfach das Schlüsselwort abstract in den Kopf der Methoden-Definition schreiben.

Der zweite Unterschied wird dich aber vom Hocker hauen. Also halt dich fest! Eine abstrakte Methode hat keinen Funktionsrumpf!

Waaas! Kim, das geht noch nicht. Was soll der ******.

Doch doch du hast richtigt gehört.

Die Funktion rennen() als abstrakte Methode definiert sieht wie folgt aus:

public void abstract rennen();

Okay, versuch mal dein Programm zu kompilieren! Wenn du alles richtig gemacht hast, dann erhälst du eine Fehlermeldung, denn bei abstrakten Methoden musst du zwei Dinge beachten.

Zum einen kannst du abstrakte Methoden nur in abstrakten Klassen definieren. Da wir die Klasse Vierbeiner bereits als abstrakt deklariert haben, ist das in unserem Fall kein Problem. Ich wollte es nur gesagt haben! 😉

Entscheidender ist das Zweite!

Eine abstrakte Methode hat keinen Funktionsrumpf und kann deshalb natürlich auch keine Funktionalität haben.

Einzigste Möglichkeit einer abstrakten Methode eine Funktionalität einzuverleiben, ist diese in der Unterklasse zu überschreiben.

Die Unterklasse von Vierbeiner ist die Klasse Katze. Der Compiler merkt, dass hier die in der Oberklasse als abstrakt deklarierte rennen() Methode nicht existiert und wirft einen Fehler!

Und das ist auch gut so!!

Ansonsten hätten wir nämlich fehlerhafte Hunde-Objekte erzeugt, die nicht rennen können.

Gut, damit dir die Idee von abstrakten Klassen in Fleisch und Blut übergeht, ergänzen wir die Klasse Katze um eine Methode rennen() und schreiben außerdem eine Elefanten-Klasse.

Hierbei konzentrieren wir uns auf das Wesentliche und schmeißen allen schnick schnack, der nichts mit abstrakten Klassen zu tun hat raus!

Zuerst die Klasse Katze.

1: public class Katze extends Vierbeiner{
		
2:   public Katze(){
3:	rennen();
4:   }
		
5:    private void rennen(){
6:	System.out.println("Deine Katze rennt!");
7:    }
		
8:}

Und als nächstes die Klasse Elefant:

1: public class Elefant extends Vierbeiner {
	
2:	public Elefant(){
3:	   rennen();
4:	}
	
5:	private void rennen(){
6:	   System.out.println("Dein Elefant stampft los!");
7:	}
	
8:}

Die beiden Klassen sind sehr ähnlich. Beide verwenden den Standardkonstruktor um die Methode rennen() aufzurufen.

Allerdings gibt es einen Unterschied darin wie die Klassen die Methode rennen() überschreiben.

Die Implementierung der Methode (Zeile 6) nimmt Rücksicht darauf, dass nur die Katze geschmeidig rennen kann und der Elefant elegant losstampft.

Wie es bei uns Codeadventurern zur Tradition gehört, wollen wir testen, ob unser gecode auch wirklich funktioniert. Hierfür legen wir jeweils eine Instanz der Klasse Katze und Elefant an.

1: Katze willie = new Katze();
2: Elefant bennie = new Elefant();

Die Ausgabe des Programms zu erraten dürfte dir mittlerweile nicht mehr schwerfallen. Als erstes rennt deine Katze und danach stampft unser Elefant los.

Deine Katze rennt!
Dein Elefant stampft los!

Abstrakte Methoden können nicht nur in abstrakten Klassen verwendet werden, sondern finden auch in dem Konzept der Java Interfaces ihre Anwendung.

Im folgenden Video zeige ich dir, wie du die einzelnen Schritte in Eclipse durchführst.

Zusammenfassend können wir also festhalten, dass wir abstrakte Methoden verwenden um eine Minimalfunktionalität, in den von der abstrakten Oberklasse abgeleiteten Unterklassen, zu erzwingen.

Was sind JAVA Interfaces?

Schön, eine Katze ist also ein Vierbeiner genau wie ein Elefant.

Dennoch gibt es zweifellos Unterschiede!

So kann ein Elefant ein Nutztier sein, wohingegen ein kleines schnuckeliges Kätzchen ein Knuddeltier ist.

Ein Elefant sollte also eine Methode last_tragen() und ein Kätzchen eine Methode knuddel_mich() implementiert haben.

Allerdings kann auch ein Kamel eine Last tragen und ein Hund geknuddelt werden.

Häufig ist es sogar so, dass ein und dasselbe Objekt mehrere Rollen übernimmt. Eine Katze wird gerne geknuddelt, jagt aber auch sehr gerne Mäuse und ist deshalb ein Jagd- und Knuddeltier gleichzeitig.

Mehrfachvererbung mit java interfaces

Um dieses Dilemma zu lösen stellt JAVA das Konzept der Interfaces (Schnittstellen) zur Verfügung.

Implementierung eines JAVA-Interfaces

Ein JAVA Interface sieht einer abstrakten Klasse, die nur abstrakte Methoden enthält sehr ähnlich.

Ich schlage vor, dass wir uns das gleich im Ernstfall ansehen und die Interfaces Knuddeltier, Jagdtier und Nutztier erstellen.

Um ein Interface zu definieren gibt es das Schlüsselwort interface.

Merke: Nach gängiger Konvention beginnt der Name eines Interfaces immer mit einem Kleinbuchstaben.

Wir beginnen mit dem Interface Knuddeltier.

1: public interface Knuddeltier {	
2:   void knuddel_mich();	
3:}

Machen wir weiter mit dem Jagdtier.

public interface Jagdtier {
	void auf_zur_jagd();
}

Und last but not least das Nutztier.

1: public interface Nutztier {	
2:	void trage_last();	
3:}

Ein interface enthält nur Funktionsköpfe. Außerdem ist jede Funktion automatisch von der Sichtbarkeit public.

Kim, ich schätze damit ich den Funktionen eine Funktionalität einverleiben kann, muss ich diese in meine Klasse einbinden und dort überschreiben. Aber wie geht das?

Ja, sehr richtig! Es ist genau wie bei einer abstrakten Klasse. Wir müssen die Methoden im Inteface in unseren Klassen einbinden und überschreiben.

Um ein Interface in eine Klasse einzubinden gibt es das Schlüsselwort implements.

Der Hammer hierbei ist, dass wir mehr als nur ein Interface einbinden können. Das ist ein wichtiger Unsterschied zu einer abstrakten Klasse.

Jede Klasse kann in JAVA nämlich nur eine EINZIGE mit dem Schlüsselwort extends eingebundene Oberklasse haben.

Deshalb spricht man bei Java Interfaces auch von der JAVA Mehrfachvererbung.

Schauen wir uns das zur Verdeutlichung mal anhand der Klasse Katze an.

Einbindung von JAVA Interfaces

Unsere Katze ist ein Knuddeltier, und weil sie gerne Mäuse jagt auch ein Jagdtier. Also binden wir die Interfaces Knuddeltier und Jagdtier in die Klasse Katze ein.

1: public class Katze extends Vierbeiner
2:	implements Knuddeltier, Jagdtier{
		
3:	public Katze(){
4:	  rennen();
5:	}
		
6:	public void rennen(){
7:         System.out.println("Deine Katze rennt!");
8:	}		
9: }

Kim, haben wir nicht was vergessen?

Ups, ja du hast das ganz richtig verstanden.

Momentan meckert unser Compiler, da wir in den Interfaces Knuddeltier die Methode knuddel_mich() und in Jagdtier die Methode auf_zur_jagd() stehen haben.

Weil wir beide Schnittstellen über das Schlüsselwort implements eingebunden haben, werden wir vom Compiler gezwungen diese in unserer Klasse Katze zu überschreiben.

So ist sichergestellt, dass jedes Jagdtier jagen und jedes Knuddeltier geknuddelt werden kann.

Also Action! Überschreiben wir die Methoden aus den Interfaces.

1: public class Katze extends Vierbeiner
2:	implements Knuddeltier, Jagdtier{
		
3:	public Katze(){
4:	   rennen();
5:	}
		
6:	public void rennen(){
7:	   System.out.println("Deine Katze rennt!");
8:	}
		
9:	public void knuddel_mich(){
10:           System.out.println("Knuddel, knuddel!");
11:	}
	   
12:	public void auf_zur_jagd(){
13:           System.out.println("Auf zur Jagd!");
14:	}
15:}

Hinzugekommen sind die Methoden knuddel_mich() (Zeile 9 bis 11) und auf_zur_jagd. Also genau die Methoden aus den beiden Interfaces Knuddeltier und Jagdtier.

Und jetzt lässt sich unser Programm auch übersetzen.

Auch wenn die Funktionalitäten der beiden neuen Methoden keinen Applaus auslösen, wollen wir sie doch wenigstens einmal in Betrieb nehmen.

Also wieder einmal das gleiche prozedre. Wir legen eine Instanz der Klasse Katze an. Rufen die Funktionen auf und staunen über die Bildschirmausgabe. Hier hast du den Programmcode:

1: Katze willie = new Katze();
2: willie.knuddel_mich();
3: willie.auf_zur_jagd();

Und die Programmausgabe wird dich alten Fuchs bestimmt nicht überraschen:

Deine Katze rennt!
Knuddel, knuddel!
Auf zur Jagd!

Wie du siehst unterscheidet sich das Konzept der abstrakten Klassen und des JAVA Interfaces nur sehr gering, deshalb sollten wir die Vor- und Nachteile der Konzepte mal näher unter die Lupe nehmen.

Im folgenden Video zeige ich dir, wie du die einzelnen Schritte in Eclipse durchführst.

Abstrakte Klasse oder JAVA Interface?

Nachteil eines Interfaces ist, dass dieses nur abstrakte Methoden enthalten darf, denn abstrakte Methoden erschweren eine nachträgliche Programmerweiterung enorm.

Stell dir vor du hast ein Inteface gebastelt, das bereits in hunderten von Klassen verwendet wird.

Wenn du jetzt auf die Idee kommst dieses um eine Methode zu erweitern, dann hast du einen Haufen Arbeit vor dir, denn du musst in jeder Klasse, die deine Schnittstelle implementiert die neue Methode deines Interfaces überschreiben.

Nachteil einer abstrakten Klasse ist, dass du mittels des Schlüsselwortes extends immer nur EINE einzige abstrakte Klasse einbinden kannst, deshalb ist mit abstrakten Klassen keine Mehrfachvererbung zu realisieren.

Deshalb mein Vorschlag: Verwende Schnittstellen immer dann wenn du mit Objekten arbeitest, die unterschiedliche Rollen gleichzeitig annehmen (wie z.B. eine Katze, die ein Jagd- und Knuddeltier gleichzeitig ist) und verwende ansonsten abstrakte Klassen um die problemlose Erweiterbarkeit deines Programms zu gewährleisten.

Wann verwendest du JAVA Interfaces und wann abstrakte Klassen? Ab in die Kommentare mit deiner Meinung.

Hat dir der Artikel gefallen? Dann folge uns doch am besten auf Facebook.

Hallo ich bin Kim und ich möchte ein großer Programmierer werden. Machst du mit?

Kommentare (7)

  • Antworte

    Hey! Danke für deinen Bog =)
    Ich habe wohl einen kleinen Fehler gefunden. InterfaceBezeichner fangen mit Großbuchstaben an… http://www.oracle.com/technetwork/java/codeconventions-135099.html
    grüße

    • Hallo Chris, danke für den Hinweis. Dein Einwand ist absolut korrekt. Ich habe den Artikel entsprechend angepasst.

  • Antworte

    Hey,

    sehr gut erklärt, vielen Dank! 🙂

    Ich bin allerdings etwas verwirrt bei der Klasse Elefant im Abschnitt „Abstrakte Methoden“. Fehlt hier nicht auch ein „extends Vierbeiner“ oder verstehe ich etwas falsch?

    Viele Grüße

    • Hallo Lena, ja, sorry! Da hast du natürlich völlig recht. Hier fehlt ein extends. Ich werde das korrigieren. Viele Grüße Kim

  • Antworte

    Hey Kim,
    du hast offenbar den Code, aber nicht deine Erläuterungen geändert. Im Text steht:
    „Merke: Nach gängiger Konvention beginnt der Name eines Interfaces immer mit einem Kleinbuchstaben.“

    Nach http://www.iwombat.com/standards/JavaStyleGuide.html#Class%20and%20Interface%20Names sollten Interfaces außerdem immer Adjektive sein. Vielleicht möchtest du das ja ebenfalls noch hinzufügen.

    Liebe Grüße

  • Antworte

    Hey, danke für die tollen Erklärungen.
    VG

    PS Das Adjektiv einzige kann nicht gesteigert werden (nicht böse gemeint!).

    • Sehr gerne! Viele Grüße Kim

Hinterlasse ein Kommentar