[[PageOutline(1-2)]]
= Methoden =
'''Java Insel:''' [[http://openbook.rheinwerk-verlag.de/javainsel/javainsel_02_007.html#dodtp0e6fae8f-4221-41a2-9467-2728f1eff1c7|Methoden einer Klasse]]
Eine Methode ist ein geschlossenes Stück Programmcode, das wiederkehrende Aufgaben mit demselben Algorithmus löst. Durch die Verwendung von Methoden lässt sich Redundanz vermeiden.
Eine Methode besteht aus einem ''Methodenkopf'' und einem ''Methodenrumpf''. Alle Methoden, die nicht den Rückgabetyp `void` haben, müssen mittels `return` einen Wert vom Rückgabetyp zurückliefern. Tatsächlich ist `void` kein in Java existierender Datentyp sondern nur eine Kennzeichnung für "nichts".
Allgemeine Form::
{{{
#!java
Sichtbarkeitsattribut [static] [final] Rückgabetyp Methodenname(Parameterliste) {
Anweisungsblock / Methodenrumpf
}
}}}
Bedeutung der einzelnen Elemente des Methodenkopfes:
* Sichtbarkeitsattribut: gibt an, wo die Methode verwendet werden kann. Möglich sind die vier Werte `public`, `protected`, `private` sowie ''ohne Angabe''. Details siehe [[Skript/2. Java/ 8. Objektorientierte Programmierung|Objektorientierte Programmierung]]
* `static`, optional: Die Methode ist eine Klassenmethode und kann keine Attribute von [[Skript/2. Java/ 8. Objektorientierte Programmierung|Objekten]] verändern. '''Achtung! ''' die eckigen Klammern [] kennzeichnen nur, dass es sich um eine optionale Angabe handelt. Sie werden nicht im Methodenkopf notiert.
* `final`, optional: Die Methode ist final und kann nicht überschrieben werden. Details siehe [[Skript/2. Java/ 8. Objektorientierte Programmierung|Objektorientierte Programmierung]]. '''Achtung! ''' die eckigen Klammern [] kennzeichnen nur, dass es sich um eine optionale Angabe handelt. Sie werden nicht im Methodenkopf notiert.
* Rückgabetyp: jeder beliebige Typ oder `void`. Falls etwas anderes als `void` angegeben wird, muss die Methode einen Wert des angegebenen Typs mittels `return` zurückliefern.
* Parameterliste: besteht aus kommagetrennten Paaren aus Typ und Name, z. B.: `int xm, int ym, int radius`. Die Parameter sind Variable, die der Methode vom aufrufenden Programmteil übergeben werden, damit die Methode ihre Aufgabe erfüllen kann. Die entsprechenden Werte sind in der Methode unter den in der Parameterliste aufgeführten Namen verfügbar. Zuweisungen an Parameter innerhalb der Methode haben keinen Einfluss auf Variable im aufrufenden Programmteil, '''sofern es sich um primitive Datentypen handelt'''.
== Signatur ==
Die Signatur ist ein Teil des Methodenkopfes und besteht aus dem Methodennamen und der Parameterliste. Der Rückgabetyp ist kein Bestandteil der Signatur.
In ein und derselben Klasse darf es nicht mehrere Methoden mit derselben Signatur geben, da der Compiler sonst Methodenaufrufe nicht eindeutig zuordnen könnte.
== Externe Sicht und interne Sicht ==
Bei der Entwicklung von Software gibt es zwei Sichten auf Methoden.
=== Externe Sicht / Black Box ===
Die externe Sicht auf eine Methode spiegelt deren Verwendung wider: Entwickler wissen nicht zwingend, wie die Methode intern aufgebaut ist, kennen aber deren Signatur und eine Funktionsbeschreibung. Beispiel dafür sind die Methoden der [[https://docs.oracle.com/javase/8/docs/api/index.html?overview-summary.html|Java API]]. In der externen Sicht kann eine Methode als Black Box aufgefasst werden, deren Eingabedaten, Ausgabedaten und Zweck bekannt, deren konkrete Implementierung jedoch unbekannt ist:
{{{
#!html
}}}
Beispiel::
{{{
#!java
// Verwendung der Methode sqrt aus der Klasse java.lang.Math
// Externe Sicht: wir wissen nicht, wie sqrt implementiert ist
double r = Math.sqrt(42);
// Verwendung der Methode ggT, nicht aus java.lang.Math sondern aus dieser
// Klasse, in der wir sie notieren (s. u.)
// wir wissen zwar, wie ggT implementiert ist (s. u.), nehmen an dieser
// Stelle aber die externe Sicht ein, da wir die Methode verwenden.
int g = ggT(60, 18);
}}}
=== Interne Sicht ===
Die interne Sicht auf eine Methode spiegelt deren Implementierung wider: Entwickler haben ein konkretes fachliches Problem, für dessen Lösung sich eine Methode anbietet. Es wird definiert,
* welche Daten für die Lösung notwendig sind (daraus entsteht die Parameterliste),
* von welchem Typ der Rückgabewert ist,
* wie die Methode heißen soll (spiegelt üblicherweise das fachliche Problem wider).
Beispiel::
{{{
#!java
// Methode zur Berechnung des größten gemeinsamen Teilers zweier Ganzzahlen x und y
public static int ggT(int x, int y) {
// So lange y ungleich 0 ist wird der Rest der Division
// bestimmt und die Werte angepasst
while (y != 0) {
// Rest der Division sichern
int temp = x % y;
// y war vorher die kleinere Zahl und wandert nach x
x = y;
// der Rest muss kleiner sein als beide Zahlen -> wandert nach y
// y wird damit wieder die kleinere Zahl
y = temp;
}
// Rückgabe des Ergebnisses an den Aufrufer
return x;
}
}}}
Bei der Entwicklung von Programmen findet häufig eine Vermischung von interner und externer Sicht statt: man programmiert eine Methode, die man in einem anderen Programmteil dann verwendet.
Beispiel::
{{{
#!java
import java.util.Scanner;
public class Beispiel {
// Methode zur Berechnung des größten gemeinsamen Teilers zweier Ganzzahlen x und y
public static int ggT(int x, int y) {
// So lange y ungleich 0 ist wird der Rest der Division
// bestimmt und die Werte angepasst
while (y != 0) {
// Rest der Division sichern
int temp = x % y;
// y war vorher die kleinere Zahl und wandert nach x
x = y;
// der Rest muss kleiner sein als beide Zahlen -> wandert nach y
// y wird damit wieder die kleinere Zahl
y = temp;
}
// Rückgabe des Ergebnisses an den Aufrufer
return x;
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.print("Geben Sie zwei Zahlen ein: ");
int a = scan.nextInt();
int b = scan.nextInt();
// Aufruf der Methode ggT ist Teil des Parameters von println
System.out.println("ggT(" + a + ", " + b + ") = " + ggT(a, b));
}
}
}}}
Im obigen Beispiel sieht man bei der Ausgabe mit `println` auch, dass Methoden in Ausdrücken überall dort verwendet werden können, wo ein Wert stehen darf, der vom Rückgabetyp der Methode ist. Es ist also nicht erforderlich, dass der Rückgabewert immer einer Variablen zugewiesen wird, sondern der Rückgabewert kann auch implizit in einem Ausdruck verwendet werden.
== Ausgabe vs. Rückgabe ==
Im Zusammenhang mit Methoden ist zwischen den Begriffen Ausgabe und Rückgabe zu unterscheiden:
* '''Ausgabe:''' Eine Ausgabe ist eine Information, die dem Benutzer bereitgestellt wird. Beispiele dafür sind Ausgaben auf der Konsole mittels `System.out.print...` oder die Anzeige von Daten in einer grafischen Benutzeroberfläche.
* '''Rückgabe:''' Ein Rückgabewert ist eine Information, die zwischen zwei Programmteilen ausgetauscht wird. Üblicherweise ist dies für den Benutzer nicht erkennbar. Konkret handelt es sich bei der Rückgabe von Werten um die Übermittlung des Ergebnisses einer Operation (z. B. Berechnung) an einen Programmteil, der diese Operation initiiert (eine Methode aufgerufen) hat. Methoden geben Rückgabewerte mit Hilfe des Schlüsselwortes `return` zurück.
== lokaler Scope ==
Variable gelten in Java nur in dem Block, in dem sie deklariert wurden. Dies gilt ebenso für Methoden. In Methoden gibt es zwei verschiedene Arten von lokalen Variablen: Parameter und lokal definierte Variable:
{{{
#!java
// Methode zur Berechnung der Fakultät einer Ganzzahl n
// n ist der formale Parameter der Methode. Er wird innerhalb der Methode wie eine normale Veriable verwendet.
public static int fakultaet(int n) {
// Zwischenergebnis
// f ist eine normale Variable, die nur innerhalb der Methode existiert.
int f = 1;
for (int i = 1; i <= n; i++) {
f = f * i;
}
// Rückgabe des Ergebnisses an den Aufrufer
return f;
}
}}}
Methoden können die Werte von Parametern verändern, indem sie ihnen Werte zuweisen. Sofern es sich um primitive Typen handelt, hat dies keinen Einfluss auf Variable außerhalb der Methode. Eine Methode kann Variable von primitiven Typen, die außerhalb der Methode deklariert wurden, '''nicht verändern'''. Man spricht dabei von ''call-by-value'': Beim Aufruf der Methode wird vom außerhalb der Methode befindlichen Wert eine Kopie angelegt und der Methode zur Verfügung gestellt. Die Methode hat ausschließlich Zugriff auf diese Kopie. Deswegen haben die Namen von Parametern nur lokale Bedeutung: Ein konkreter Parameter kann denselben Namen wie oder einen anderen Namen als ein formaler Parameter tragen: Es wird keine Verbindung zwischen den Werten innerhalb und außerhalb der Methode hergestellt.
Unabhängig davon ist es schlechter Stil, wenn man Parameter von primitiven Typen innerhalb einer Methode ändert. Man sollte statt dessen eine zusätzliche Hilfsvariable verwenden.
Beispiel für formale und konkrete Parameter::
{{{
#!java
// Methode zur Berechnung der Fakultät einer Ganzzahl n
// n ist der formale Parameter der Methode. Er wird innerhalb der Methode wie eine normale Veriable verwendet.
public static int fakultaet(int n) {
// Zwischenergebnis
// f ist eine normale Variable, die nur innerhalb der Methode existiert.
int f = 1;
for (int i = 1; i <= n; i++) {
f = f * i;
}
// Rückgabe des Ergebnisses an den Aufrufer
return f;
}
public static void main(String[] args) {
// n ist lokal in der Methode main
int n = 4;
// n wird zum konkreten Parameter beim Aufruf von fakultaet
// innerhalb der Methode fakultaet wird ein zweites n durch Kopie dieses n erzeugt
// es existieren also zwei Variable mit dem Namen n: eines in main, eines in fakultaet
int x = fakultaet(n);
// m ist lokal in der Methode main
int m = 5;
// m wird zum konkreten Parameter beim Aufruf von fakultaet
// innerhalb der Methode fakultaet wird n durch Kopie von m erzeugt
// es existieren also zwei Variable: m in main, n in fakultaet
int y = fakultaet(m);
}
}}}
Falls es sich bei den Parametern nicht um primitve Typen sondern um [[Skript/2. Java/ 8. Objektorientierte Programmierung|Objekte]] handelt, verhält sich die Veränderbarkeit von außerhalb der Methode befindlichen Variablen anders (Details siehe dort). In diesem Fall spricht man von ''call-by-reference'' (Aufruf mit einer Referenz oder Zeiger, Adresse).
== Detaillierter Ablauf eines Methodenaufrufes ==
Betrachten wir die folgende Methode `ggT`, die den größten gemeinsamen Teiler zweier Ganzzahlen mit dem beschleunigten Algorithmus von Euklid berechnet:
{{{
#!java
public static int ggT(int x, int y) {
while (y != 0) {
int temp = x % y;
x = y;
y = temp;
}
return x;
}
}}}
Irgendwo in Programm wird die Methode `ggT` aufgerufen:
{{{
#!java
...
int a = 60;
int b = 18;
int g = ggT(a, b);
...
}}}
Der zeitliche Ablauf des Kontrollflusses im Programm sieht dann wie folgt aus:
{{{
#!html
}}}
== Vorzeitiges Ende eines Methodenaufrufes ==
Sobald innerhalb einer Methode das Berechnungsergebnis feststeht, kann die Methode mit `return` verlassen werden. Dies ist insbesondere im Zusammenhang mit Schleifen und dem Rückgabetyp `boolean` ein häufig auftretendes Muster. Dies wird auch ''Methodenausbruch'' genannt:
{{{
#!java
// Die Methode soll feststellen, ob die übergebene Zahl n die Ziffer 3 enthält
// falls ja soll die Methode true zurückgeben, sonst false
public static boolean enthaeltDrei(int n) {
// Durchlaufe die gesamte Zahl bis nichts mehr von ihr übrig ist
while (n > 0) {
// ist die letzte Ziffer eine 3?
if (n % 10 == 3) {
// Methodenausbruch:
// wir haben festgestellt, dass eine 3 enthalten ist
// ==> geben true zurück, die Methode endet sofort
return true;
}
// Schneide letzte Ziffer ab
// Merke: hier wird der Parameter n geändert, dies hat aber nur
// lokale Auswirkungen innerhalb der Methode (call-by-value)
n = n / 10;
}
// gesamte Zahl wurde geprüft, der Programmablauf ist bis hierhin gekommen
// es kann also keine 3 enthalten gewesen sein, da die Methode sonst
// schon verlassen worden wäre ==> keine 3 in n enthalten
return false;
}
}}}
== Weitere Beispiele ==
{{{
#!java
import java.util.Scanner;
public class Methoden {
// Ausgabe(!!!) der Zeichenkette "hallo" auf der Konsole
public static void schreibeHallo() {
System.out.println("hallo");
}
// Ausgabe(!!!) von Sternen entsprechend des Wertes des Parameters n
public static void schreibeSterne (int n) {
// die Initialisierung könnte auch lauten i = 0 und die Bedingung wäre dann i < n
for (int i = 1; i <= n; i++) {
System.out.print("*");
}
}
// Rückgabe(!!!) der Fakultät des Parameters n (n!)
public static int fakultaet(int n) {
int f = 1;
for (int i = 1; i <= n; i++) {
f = f * i;
}
return f;
}
// Rückgabe des größten gemeinsamen Teilers der beiden Parameter
public static int ggT(int a, int b) {
while (a != b) {
if (a > b) {
a = a - b;
} else {
b = b - a;
}
}
return a;
}
// Verwendung der oben notierten Methoden
public static void main(String[] args) {
schreibeHallo();
schreibeSterne(3);
System.out.println();
schreibeSterne(17);
System.out.println();
int m = fakultaet(4);
schreibeSterne(m - 10);
System.out.println();
schreibeSterne(fakultaet(3));
System.out.println();
Scanner scan = new Scanner(System.in);
System.out.print("erste Zahl: ");
int x = scan.nextInt();
System.out.print("zweite Zahl: ");
int y = scan.nextInt();
int z = ggT(x, y);
System.out.println("ggT(" + x + ", " + y + ") = " + z);
}
}
}}}
== Gleichnamige Methoden mit unterschiedlicher Signatur ==
Die Vorgabe, dass innerhalb ein- und derselben Klasse nur Methoden mit unterschiedlicher Signatur enthalten sein dürfen, gestattet, dass Methoden mit demselben Namen in derselben Klasse existieren. In diesem Fall müssen sich die Parameterlisten der Methoden unterscheiden:
{{{
#!java
// Methoden mit dem gleichen Namen, aber
// unterschiedlicher Parameterliste ==> unterschiedlicher Signatur
public class F {
// leere Methode, funktionslos
public static void f() {
}
// Rückgabe der Summe 1 + 2 + .. + n
// Achtung! keine effiziente Implementierung
// Besser wäre return (n * (n + 1)) / 2;
public static int f(int n) {
int s = 0;
for (int i = 1; i <= n; i++) {
s = s + i;
}
return s;
}
// Rückgabe a + b
public static int f(int a, int b) {
return a + b;
}
// Verwendung der verschiedenen Methoden
public static void main(String[] args) {
// Variable namens f
int f = 7;
f = f(f);
f = f(f, f);
// beliebige Schachtelung von Methodenaufrufen ist möglich
f = f(f(f, f), f(f(f), f));
System.out.println(f);
}
}
}}}