Advanced Java Services | Methoden |
Methoden oder Funktionen sind Unterprogramme, die Teilaufgaben eines Programms erledigen. Durch diese wird die Modularisierung von Programmen unterstützt und die Wiederverwendbarkeit von Programmcode ermöglicht. Eine Funktion ist unabhängiges Unterprogramm, das für sich alleine existiert. Eine Methode ist ein abhängiges Unterprogramm, das nur im Zusammenhang mit einer Klasse existiert. Aus diesem Grund nannte man eine Methode früher auch memberfunction, weil sie eben Teil einer Klasse ist. Wir haben bereits eine Reihe von Methoden aus der Klasse Stdin und der Klasse Math verwendet (siehe Erster Kontakt mit Klassen) und wissen, wie man statische Methoden aufruft, nämlich mit dem dem Namen der Klasse, aus der sie stammen.
Die nachfolgende Graphik zeigt, wie man sich einen Unterprogrammaufruf vorzustellen hat. Bei einem
Statement, das einen Unterprogrammaufruf darstellt, verzweigt das Programm zu einem neuen Speicherblock,
dessen Code dann abgearbeitet wird. Damit hat jedes Unterprogramm seinen eigenen Speicherbereich und seine
eigenen lokalen Variablen. Am Ende eines Unterprogramms gibt es einen Rücksprung zum jeweils aufrufenden
Programm. Dieses return-Statement wird vom Compiler automatisch ergänzt wenn man eine Funktion oder
Methode schreibt. Mit diesem return-Statement kann das Unterprogramm auch einen Wert an das
aufrufende Programm zurückgegeben. In diesem Fall muß der Programmierer selbst ein passenden
return-Statement einfügen. Auch in main() selbst gibt es ein implizites return-Statement.
Dieses hat dann das Ende des Programms zur Folge. Der prinzipielle Vorgang ist für Funktionen und
Methoden derselbe. Natürlich kann in einem Unterprogramm ein weiterer Unterprogrammaufruf sein. Beim
jedem Aufruf eines Unterprogramms muß das Hauptprogramm sich eine Rücksprungadresse merken, damit nach
dem Ende des Unterprogramms das aufrufende Programm weiter abgearbeitet werden kann. Diese
Rücksprungadressen werden in einem eigenen Speicherbereich verwaltet, dem sogenannten Stack, der
in der Graphik nicht dargestellt wird. bei sehr vielen geschachtelten Unterprogrammaufrufen kann dieser
Stack eventuell zu klein werden. Ein dann auftretender stack overflow führt unweigerlich
zu einem Programmabsturz.
Jetzt wollen wir eigene Methoden schreiben. Dazu müssen wir folgende Schritte beachten:
Am besten schaut man sich das an einem Beispiel an. Im folgenden werden außer main noch vier weitere Methoden definiert.
public class MethodenDemo { public static void main(String args[]) { System.out.println("main-Methode"); } static void kriegtNixGibtNix() // Methode 1 { System.out.println("kriegtNixGibtNix-Methode"); } static void kriegtWasGibtNix(int a) // Methode 2 { System.out.println("kriegtWasGibtNix-Methode : "+a); } static char kriegtNixGibtWas() // Methode 3 { System.out.println("kriegtNixGibtWas-Methode"); return 'x' ; } static double kriegtWasGibtWas(int a) // Methode 4 { System.out.println("kriegtWasGibtWas-Methode : "+a); return a*a ; } static double mimmZweiGibEins(double a, double b) // Methode 5 { System.out.println("mimmZweiGibEins-Methode"); return a*b ; } }
Im obigen Beispiel sind die grundsätzlichen Möglichkeiten durchgespielt. Nach der main-Methode folgen fünf weitere selbstentworfene Methoden. Allen Methoden haben eine Kopfzeile, die den Namen und die Parameterliste festlegt, und einen Rumpf, der mindestens aus den geschweiften Klammern bestehen muß. Die erste Zeile mit dem Namen nennt man auch gerne die Signatur der Methode, diese taucht für gewöhnlich in der Dokumentation zu der Methode bzw. Klasse auf (siehe die Doku zur Klasse Stdin).
Die oben geschriebenen Methoden werden noch nirgends verwendet. Damit eine Methode abgearbeitet wird, muß sie aufgerufen werden. Erst dann kann sie etwas bewirken. Meist wird eine Methode aus einer anderen Methode heraus aufgerufen. Standardbeispiel: Innerhalb der main-Methode wird System.out.println() aufgerufen. Ein Methodenaufruf kann aber auch in einer Vereinbarung stehen und dort eine Variable initialisieren. In den folgenden zwei Beispielen demonstrieren wir Aufrufe der vier oben eingeführten Methoden.
public class Aufruf1 { public static void main(String args[]) { System.out.println("main-Methode"); Aufruf1.kriegtNixGibtNix() ; // Methode 1 wird aufgerufen Aufruf1.kriegtWasGibtNix(17) ; // Methode 2 wird aufgerufen char erg = Aufruf1.kriegtNixGibtWas() ; // Methode 3 wird aufgerufen int a = 34; double ret; ret = Aufruf1.kriegtWasGibtWas(a) ; // Methode 4 wird aufgerufen kriegtNixGibtNix() ; // Methode 1 wird nochmal aufgerufen double erg = Aufruf1.mimmZweiGibEins(a, ret) // Methode 5 wird aufgerufen } static void kriegtNixGibtNix() // Methode 1 { System.out.println("kriegtNixGibtNix-Methode"); } static void kriegtWasGibtNix(int a) // Methode 2 { System.out.println("kriegtWasGibtNix-Methode : "+a); } static char kriegtNixGibtWas() // Methode 3 { System.out.println("kriegtNixGibtWas-Methode"); return 'x' ; } static double kriegtWasGibtWas(int a) // Methode 4 { System.out.println("kriegtWasGibtWas-Methode : "+a); return a*a ; } static double mimmZweiGibEins(double a, double b) // Methode 5 { System.out.println("mimmZweiGibEins-Methode"); return a*b ; } }
Im obigen Beispiel werden alle Methoden aus main heraus aufgerufen. Beachten Sie die Art der Parameterübergabe und die Zuweisung des Returnwertes. Eine Methode kann also Daten aus einem laufenden Programm entgegennehmen (Parameterübergabe) und ein Datum an die aufrufende Methode zurückgeben (Zuweisung des Returnwertes).
Es fällt auf, daß im obigen Beispiel alle Methoden statisch sind. Dies hat zwei Gründe. Zum einen wurde bisher nur ein statischer Methodenaufruf behandelt. Zum anderen wollen wir unsere Beispielsmethoden aus main heraus aufrufen. Nun ist aber main selbst auch statisch und für statische Methoden gibt eine wichtige Einschränkung:
Bezüglich Parameterlisten und Returnwert bestehen jedoch zwischen statischen und nichtstatischen Methoden keine Unterschiede.
In objektorientierten Sprachen ist es möglich, denselben Namen für mehrere Methoden zu vergeben. Sprachen, die noch aus der vorobjektorientierten Zeit stammen, wie etwa C können dies nicht. Bei gleichen Namen erfolgt die Unterscheidung von Methoden über die Parameterliste. Der Returntyp und sonstige Modifier wie public oder static werden nicht zur Namensunterscheidung herangezogen. In der Parameterliste werden die Parameter nach Anzahl, Typ und Reihenfolge unterschieden. Im folgenden finden Sie einige Beispielsignaturen mit gleichem Namen, die der Compiler an Hand der Parameterliste unterscheiden kann.
In diesem Beispiel ist jede Methode zu jeder anderen unterscheidbar. Etwa : 1. von 2. wegen
unterschiedlicher Parameteranzahl, 2. von 3. wegen unterschiedlicher Parametertypen, 3. von 4. wegen
unterschiedlicher Parameteranzahl, 4. von 5. wegen unterschiedlicher Reihenfolgen der Typen. Sie
können im obigen Beispiel irgendzwei der fünf Methoden betrachten und feststellen, daß
die Signaturen unterscheidbar sind.
Das Überladen von Methoden (engl. method overloading) sollte nur dann angewandt werden, wenn die
Methoden ähnliche oder verwandte Aufgaben erledigen, die Methoden der Klasse Sort sind ein gutes
Beispiel.
Vorsicht ist allerdings beim Aufruf von überladenen Methoden geboten. Hier kann es schnell zu
nicht auflösbaren Mehrdeutigkeiten kommen. Wir betrachten die letzten beiden Signaturen (4. und 5.)
unseres obigen Beispiels und den folgenden Aufruf:
int r=2, s=3 ;
xx.beispiel(r, s) ;
Für diesen Aufruf findet der Compiler keine Signatur, die exakt paßt (exact match), also
versucht er eine Typanpassung. Eine Typanpassung von int nach double ist kein Problem, die Frage ist
nur, bei welchem Parameter sie vorgenommen werden soll, beim ersten oder beim zweiten. Da beide
Varianten gleichberechtigt sind, kann der Compiler hier keine Entscheidung treffen.
Bei der Übergabe von Parametern an Methoden wird grundsätzlich mit Kopien gearbeitet. Die Methode erhält nicht das Original des aktuellen Parameters, sondern eine Kopie desselben. Die Methode speichert diese Kopie in einem eigenen Speicherbereich. Etwaige Änderungen dieser Kopie können sich also nicht auf das Original auswirken. Diese Art der Übergabe beim Aufruf einer Methode nennt man "call by value" , Wertübergabe. Insofern könnte man sagen, daß es in Java eigentlich nur "call by value" gibt. Es macht jedoch einen qualitativen Unterschied, ob man die Kopie einer Variablen übergibt oder die Kopie einer Referenz auf eine Variable, deswegen spricht man bei der Übergabe einer Kopie einer Referenz auch gerne von "call by reference".
call by value |
Beim Aufruf von method() wird der Wert des aktuellen Parameters x der lokalen Variablen a von method() zugewiesen. Änderungen der Kopie a können sich nicht auf das Original x auswirken.
call by reference |
Hier wird die Übergabe einer Feldreferenz dargestellt. Beim Aufruf der Methode method() wird eine Kopie der Feldreferenz arr in die lokale Referenz brr abgelegt. Damit zeigen sowohl arr als auch brr auf das in main() angelegte Feld. Die Methode kann deshalb auf das Feld zugreifen und dort beliebige Änderungen vornehmen, die die Datensituation in der aufrufenden Methode ( hier main() ) beeinflußen. Eine Änderung der Referenz brr innerhalb der Methode method() hat dagegen keinen Einfluß auf main() .
Die folgende Methode arbeitet mit zwei Parametern. Der erste Parameter ist eine Feldreferenz, der zweite Parameter eine int-Variable. Mit dem zweiten Parameter kann man z.B. den Mittelwert nur der ersten Hälfte des Arrays ermiteln, so wie es hier geschieht.
public class Aufruf2 { public static void main(String args[]) { int brr[] = { 2, 4, 6, 20, 18, 16, 7, 12, 12, 17 } ; double mittel = Aufruf2.mittelWert(brr, brr.length/2) ; System.out.println("Mittelwert der ersten Hälfte " + mittel); } static double mittelWert(int arr[], int len) { double mittel=0; for(int i=0; i<len; i++) mittel += arr[i] ; return mittel/len ; } } // end main class
Dieses Beispiel zeigt, daß auch der Returntyp einer Methode ein Referenztyp sein kann. Es wird eine Feldreferenz übergeben. In der Methode wird ein zweites Feld angelegt. Dieses Feld enthält Kopien der Elemente des Ausgangsfeldes, nur in umgekehrter Reihenfolge. Das Ausgangsarray wird also nicht verändert.
public class Aufruf3 { public static void main(String args[]) { int brr[] = { 2, 4, 6, 20, 18, 16, 7, 12, 12, 17 } ; int rev[] = Aufruf3.reverse(brr) ; for(int i=0; i<rev.length; i++) System.out.println("rev["+i+"] = " + rev[i]); } static int[] reverse(int arr[]) { int feld[] = new int[arr.length] ; for(int i=0; i<arr.length; i++) feld[i] = arr[ (arr.length-1) - i] ; return feld ; } } // end main class
Wahrscheinlich ist Ihnen aufgefallen, daß alle bezeichner für Methoden mit kleinem Buchstaben beginnen. Dies ist kein Zufall, sondern eine sehr nützliche Konvention, die es uns erleichtert, uns in eigenen und fremden Programmen zurecht zu finden. Es hat sich auch eingebürgert, jedes neue Wort im Bezeichner mit großen Buchstaben beginnen zu lassen. Hier einige Beispiele aus der Klassenbibliothek:
Und noch etwas sieht man an den obigen Namen: Bezeichner sollen nicht kurz sein, sondern selbsterklärend. Das spart Erklärungen in Kommentaren.
Einfache Methoden
Kalenderberechnungen
Finanzmathematische Methoden
Mathematische Methoden