Heute werden wir uns ansehen, wie wir in Java Methoden überladen können.
Inhaltsverzeichnis
1. Rückblick auf unser Praxisbeispiel
Auf den folgenden beiden Abbildungen sehen wir das Programm, das wir bereits in den vergangenen Beiträgen zusammengestellt haben. Es gibt darin die Klasse Car und die Klasse Program, in der sich die Main-Methode befindet:
Die Klasse Car
Die Klasse Program
Wie bereits erwähnt, werden wir im heutigen Blogartikel in Java Methoden überladen. Damit du verstehen kannst, was es damit auf sich hat, sehen wir uns das Ganze nun mal anhand der drive-Methode an.
2. Der drive-Methode einen Parameter übergeben
In der drive-Methode geben wir aktuell lediglich den Satz „das Auto fährt“ aus:
public void drive() {
System.out.println("Das Auto fährt…");
}
Genauso könnten wir darin einen Integer Wert übergeben, den wir „speed“ nennen und womit sich zusätzlich angeben lässt, wie schnell das Auto fährt:
public void drive(int speed) {
System.out.println("Das Auto fährt " + speed + " km/h");
}
Anschließend bewegen wir uns zurück in unser Programm und speichern dieses. Unter der Zeile, in der wir unser Objekt mit den Werten „Grün“, „VW“ und „130“ instanziieren, schreiben wir die folgende Zeile:
Car car1 = new Car("Grün", "VW", 130); //Instanziierung eines Objekts
car1.drive();
Jetzt benötigen wir noch einen Parameter zwischen dem runden Klammerpaar. Das Programm erwartet darin einen Integer-Wert für den Speed. Beispielhaft übergeben wir darin 30, was bedeuten soll, dass das Auto 30 km/h fährt.
Car car1 = new Car("Grün", "VW", 130); //Instanziierung eines Objekts
car1.drive(30);
Beim Ausführen des Programms sehen wir in der Konsole, dass das Auto 30 km/h fährt:
Output:
Das Auto fährt 30 km/h
Bis zu diesem Punkt ist das für uns noch nichts Neues. Jetzt könnte es allerdings sein, dass wir dem Benutzer ermöglichen möchten, direkt einen Wert mitzuübergeben, wodurch er sich unmittelbar die Geschwindigkeit ausgeben lassen könnte. Oder aber, dass wir ihm die Gelegenheit bieten möchten, die drive-Methode ohne Parameter aufzurufen, wenn er zum aktuellen Zeitpunkt noch nicht weiß, wie schnell das Auto fährt.
Das war bisher allerdings nicht machbar, weil wir wissen, dass man einen Methodennamen nur einmal vergeben kann, da der Computer sonst nicht wüsste, was er ausführen soll. Genau hier kommen wir auf das eigentliche Thema dieses Artikels zurück: Methoden überladen.
3. So kann man in Java Methoden überladen
Dafür kopieren wir zunächst die drive-Methode und fügen diese direkt darunter noch einmal ein:
public void drive(int speed) {
System.out.println("Das Auto fährt " + speed + " km/h");
}
public void drive(int speed) {
System.out.println("Das Auto fährt " + speed + " km/h");
}
Jetzt zeigt uns das Programm auch schon einen Fehler an:
Duplicate method drive(int) in type Car
Diese Meldung möchte uns mitteilen, dass wir die gleiche Methode fälschlicherweise doppelt verwenden. Wenn wir diese Methode allerdings überladen, indem wir in der kopierten Version den Parameter speed entfernen und die println-Methode ändern, ist der Fehler auch schon verschwunden:
//Schritt 1:
public void drive(int speed) {
System.out.println("Das Auto fährt " + speed + " km/h");
}
//Schritt 2:
public void drive() {
System.out.println("Das Auto fährt…");
}
Das liegt daran, dass der Computer nicht nur Methodennamen unterscheidet, welche in unserem Beispiel offensichtlich gleich sind, sondern auch die sogenannte Signatur.
4. Was ist eine Signatur?
Bei der Signatur handelt es sich um die Übergabeparameter, die wir in einer speziellen Reihenfolge übergeben. Der Computer kann darin die Datentypen erkennen, was konkret bedeutet:
Er sieht, dass eine Methode namens drive existiert, in der ein Integer übergeben wird und eine weitere Methode drive, in welcher wir nichts übergeben.
Aus diesem Grund können beide drive-Methoden gleichzeitig bestehen. Wenn wir also beispielsweise die drive-Methode in der Main-Methode aufrufen und den Integer-Wert 30 übergeben, weiß der Computer, dass es sich um die obenstehende drive-Methode handelt, weil darin ein Parameter erwartet wird. Somit führt er deren println-Methode aus:
Car car1 = new Car("Grün", "VW", 130); //Instanziierung eines Objekts
car1.drive(30);
Output:
Das Auto fährt 30 km/h
Löschen wir den Parameter allerdings wieder, weil wir beispielsweise nicht wissen, wie schnell das Auto fährt und führen das Programm aus, sehen wir lediglich „das Auto fährt...“ in der Konsole:
Car car1 = new Car("Grün", "VW", 130); //Instanziierung eines Objekts
car1.drive();
Output:
Das Auto fährt...
Diesmal wurde also die andere drive-Methode ausgeführt:
public void drive() {
System.out.println("Das Auto fährt…");
}
Auf diese Weise haben wir die Möglichkeit, nach Belieben unsere Java Methoden zu überladen.
Die Parameterliste in der Signatur ändern
Das bedeutet, dass wir die zweite drive-Methode auch nochmals kopieren und darunter einfügen können:
public void drive() {
System.out.println("Das Auto fährt…");
}
public void drive() {
System.out.println("Das Auto fährt…");
}
So sollten wir unser Programm natürlich nicht stehen lassen, da beide Methoden noch die gleiche Signatur besitzen, nämlich keine Parameter. Zwischen die Klammern „int speed“ zu schreiben wäre ebenfalls nicht machbar, weil die Signatur unserer dritten drive-Methode dann mit der ersten drive-Methode übereinstimmen würde. Sie hätten demnach sowohl den gleichen Methodennamen als auch den gleichen Parameter.
Was wir an dieser Stelle noch tun könnten, um einen Unterschied hinzuzufügen, ist Folgendes. Wir übergeben beispielhaft neben dem Integer speed noch einen String mit dem Namen des Fahrers:
public void drive(int speed, String name) {
System.out.println("Das Auto fährt…");
}
Auf diese Weise würde der Code wieder funktionieren. Wir haben damit also die Möglichkeit, unser Programm vielseitig zu gestalten und dem Client, der die Klasse verwendet, verschiedene Möglichkeiten zu bieten, eine Methode zu benutzen.
Wenn er weiß, wie schnell das Auto fährt, kann er die Zahlen direkt mit übergeben. Weiß der Client das hingegen nicht, soll er die Möglichkeit haben, die Methode aufzurufen, auch ohne einen Parameter beim Aufruf mit übergeben zu müssen:
car1.drive();
Ist der Name des Fahrers noch wichtig, soll der Nutzer einfach die Methode aufrufen, in der zudem der Name des Fahrers mit übergeben wird.
Intern wird dieser Name dann entsprechend verarbeitet, je nachdem, was wir im Code dafür vorgesehen haben.
Das ist auch schon das ganze Geheimnis, das hinter dem Überladen von Methoden steckt!
5. Den Konstruktor überladen
Im vergangenen Beitrag haben wir den Konstruktor kennengelernt, welcher im Prinzip nichts anderes als eine Methode ist:
public Car(String color, String brand, int horsePower) {
this.color = color;
this.brand = brand;
this.horsePower = horsePower;
}
Das bedeutet, dass wir auch den Konstruktor überladen können!
Um das zu veranschaulichen, werden wir uns an dieser Stelle einen Konstruktor erstellen, dem wir keine Parameter übergeben:
public Car() {
}
Wie wir sehen, unterscheiden sich die Signaturen der beiden Konstruktoren. In einem Konstruktor übergeben wir drei Parameter, im anderen hingegen keinen. Wir können nun beispielsweise in unserem zweiten Konstruktor eine println-Methode ergänzen:
public Car() {
System.out.println("Auto wurde erzeugt.");
}
Anschließend rufen wir diesen Konstruktor im Programm auf und stellen fest, dass sich dieses problemlos ausführen lässt.
Car car1 = new Car("Grün", "VW", 130); //Instanziierung eines Objekts
Car car2 = new Car();
Output:
Auto wurde erzeugt.
Das Auto fährt...
So funktioniert also das Überladen von Konstruktoren. Wir können sowohl normale Java Methoden überladen als auch statische Methoden und Konstruktoren. Auf diese Weise lässt sich das eigene Programm deutlich flexibler gestalten.
6. Inwiefern müssen sich Signaturen unterscheiden?
Was zu guter Letzt noch klargestellt werden muss, ist, dass sich die Signatur nicht nur auf die Anzahl der Parameter bezieht. Wir könnten also beispielsweise auch in den Klammern unseres ursprünglich parameterlosen Konstruktors drei Parameter übergeben:
public Car(String test1, String test2, String test3) {
System.out.println("Auto wurde erzeugt.");
}
Und wie wir sehen, würde das Programm immer noch funktionieren, obwohl sich jetzt bei beiden Konstruktoren drei Parameter in der Signatur befinden:
Zweiter Konstruktor:
public Car(String test1, String test2, String test3) {
System.out.println("Auto wurde erzeugt.");
}
Erster Konstruktor:
public Car(String color, String brand, int horsePower) {
this.color = color;
this.brand = brand;
this.horsePower = horsePower;
}
Wo liegt also der Unterschied? Die Signatur bezieht sich speziell auf die Typen sowie die Reihenfolge. Das heißt, der Computer erkennt, dass wir gerade drei Parameter übergeben haben, die alle jeweils vom Typ String sind.
public Car(String test1, String test2, String test3) {
System.out.println("Auto wurde erzeugt.");
}
Beim anderen Konstruktor erkennt er, dass erst zwei Strings angegeben sind und anschließend ein Integer folgt:
public Car(String color, String brand, int horsePower) {
this.color = color;
this.brand = brand;
this.horsePower = horsePower;
}
Aus diesem Grund kann der Computer sie eindeutig voneinander unterscheiden und das Programm funktioniert.