Methoden

Mit Methoden lässt sich der Code einer Klasse organisieren. Das klingt wenig aufsehenerregend, auf Dauer ist jedoch eine angemessene Organisation des Codes die wesentlichste Eigenschaft guter Software.

MATERIALIEN

 

Methoden fassen wiederkehrende Code-Sequenzen unter einem eigenen Namen zusammen.

Aufgabe

Zum Einstieg ein nettes und gegen Ende recht kniffliges Spiel, an dem sich die Denkweise eines Programmierers üben lässt: LightBot

Methode

Erstellt von Seraina Hohl

In einer Methode kann eine (u.U. lange) Befehlssequenz zu einer Einheit zusammengefasst und dann bequem über den Methodennamen aufgerufen werden.

Analogie

Ein Befehl, auf dessen Zuruf der Seehund mit dem Ball einen vorher eingeübten Trick präsentiert. Vielleicht kann man dem Seehund dabei auch noch den Gegenstand zuwerfen, mit dem er den Trick machen soll – das wäre dann analog zum Übergabewert einer Methode.

Erstellt von Seraina Hohl

Zunächst ein kurzer Rückblick: Ganz zu Beginn hatten wir uns die grundlegende Struktur einer Java-Klasse angeschaut und festgestellt, dass es immer eine grün hinterlegte, äussere Klammer gibt. Innerhalb dieses Bereichs stehen ggf. Instanzvariablen (meist ganz oben) und dann einen oder mehrere gelb hinterlegte Bereiche. Mit diesen sogenannten Methoden wollen wir uns heute beschäftigen.

Methoden sind so etwas wie die Fähigkeiten einer Klasse, z.B. eines bestimmten Actors. Bis jetzt haben Sie fast ausschliesslich den Code innerhalb der act()-Methode verändert, also schauen wir uns diese Methode jetzt einmal genauer an:

public void act(){
    //hier steht der Code, der beim Aufruf der act()-Methode ausgeführt wird
}

Wenn Sie diesen Code mit anderen (gelb hinterlegten) Methoden in einem der Szenarien vergleichen, werden Sie feststellen, dass die Grundstruktur immer dieselbe ist:

Body/Körper: Es gibt immer ein Paar von geschweiften Klammern, das den gesamten auszuführenden Code umschliesst. Diese Klammern sind es auch, die den gelb hinterlegten Bereich definieren. Zusammen mit dem umschlossenen Code bilden sie den sogenannten «Körper» (body) der Methode, in dem deren Funktionsweise festgelegt wird.

Head/Kopf: Vor der öffnenden Klammer des Methodenkörpers steht der Methodenkopf (head) mit einigen Angaben, die definieren, um was für eine Art von Methode es sich handelt und wie man sie benutzt. Der Reihe nach finden sich hier mindestens drei Elemente (vgl. Code oben):

  • public – Dieser sogenannte Modifikator gibt an, wer ausser der umschliessenden Klasse diese Methode noch benutzen darf. Üblicherweise steht hier entweder private (d.h. andere Klassen dürfen diese Methode nicht aufrufen) oder public (d.h. externe Klassen dürfen diese Methode aufrufen) – für unsere Zwecke genügt es, an dieser Stelle immer public zu schreiben.
  • void – An der zweiten Stelle steht ein Variablentyp, z.B. int, boolean, double, String, …, oder eben void, was so viel bedeutet wie: leer, nicht vorhanden, kein Typ. Technisch gesprochen definiert diese Angabe den Typ des Rückgabewerts, void bedeutet, dass diese Methode nichts zurück gibt – dazu später mehr.
  • act() – An der dritten Stelle steht der Methodenname, gefolgt von einem Paar normaler Klammern. Im Normalfall ist der Methodenname frei wählbar, wie schon bei Variablennamen sollte man jedoch darauf achten (a) einen sprechenden Namen zu wählen, (b) den sogenannten camelCase zu befolgen (d.h. das erste Wort im Namen wird klein, alle folgenden dann gross geschrieben) und (c) Sonderzeichen oder Umlaute zu vermeiden. Die Klammern nach dem Methodennamen braucht es immer, manchmal stehen darin noch Angaben zu einem oder mehreren Übergabewerten. Auch dazu gleich Genaueres.

1. Die act()-Methode in Greenfoot

Methodennamen sind im Normalfall frei wählbar, es gibt allerdings zwei Ausnahmen von dieser Regel – und der erste dieser Sonderfälle ist schon gleich die act()-Methode. Wie Sie ja bereits wissen, wird die act()-Methode einmal ausgeführt, wenn der Benutzer auf den «Act»-Knopf in der Greenfoot-Oberfläche drückt. Ein Klick auf «Run» bewirkt, dass die act()-Methode immer wieder aufgerufen wird, wobei der Schieberegler «Speed» bestimmt, wie schnell hintereinander das geschieht.

Wie wird nun eine Methode aufgerufen? Eigentlich ganz einfach: man nennt sie beim Namen – inkl. der folgenden Klammern – und schon wird der im Methodenkörper definierte Code ausgeführt. Da also ein Klick auf den «Act»-Knopf genau die Methode act() aufruft, haben wir hier keine Wahl beim Methodennamen. Ausserdem bedeutet das, dass die act()-Methode die einzige ist, die sozusagen automatisch ausgeführt wird – zumindest wenn das Szenario gestartet wurde (z.B. mit «Run»).

Die act()-Methode ist also etwas speziell, weil sie von Greenfoot automatisch aufgerufen werden kann – sie ist damit ein praktischer Startpunkt, um das Verhalten eines Greenfoot Actors zu definieren, und genau deshalb haben Sie ihren Code bisher meist im Körper der act()-Methode platziert. Die Frage ist natürlich, wozu es dann andere Methoden braucht.

Eigentlich gibt es eine sehr einfache Antwort auf diese Frage: Methoden sind dazu da, Code besser zu organisieren. Stellen Sie sich vor, Sie wollen einen Actor mit einem etwas komplizierteren Verhalten programmieren – nehmen wir beispielsweise an, Turtle Joe soll zuerst ein Dreieck und dann ein Quadrat zeichnen. Zuerst das Dreieck:

    for (int i=0; i<3; i++){
        moveAndDraw(50);
        turn(120);
    }

Aufgabe

Fügen Sie diesen Code in die act()-Methode eines Joe-Szenarios (Joe.zip) ein und probieren Sie es aus. (Zur Erinnerung: Szenario herunterladen, entpacken, greenfoot.projekt doppelklicken.)

2. Methoden auslagern

So weit, so gut, man könnte jetzt den entsprechenden Code für das Quadrat einfach darunter schreiben und wäre fertig. Was aber, wenn danach noch ein Fünf-, Sechs- und Siebeneck gezeichnet werden soll? Man kann natürlich einfach so weiter machen, aber irgendwann wird der Code sehr lang und unübersichtlich – schon für das Dreieck (s. oben) ist es gar nicht so einfach, mit einem Blick herauszufinden, was Joe da eigentlich tut. Dem kann abgeholfen werden, und zwar indem man den Dreiecks-Code in eine eigene Methode auslagert und diese mit einem sinnvollen Namen versieht (ändern Sie ihr Szenario entsprechend):

public void act(){
    drawTriangle();  //hier wird die neue Methode aufgerufen
}

public void drawTriangle(){ //hier wird die neue Methode definiert
    for (int i=0; i<3; i++){
        moveAndDraw(50);
        turn(120);
    }
}

Wie man sieht, ist eine Methode eigentlich nur so etwas wie ein Stück Code mit einem Namen, ein sogenannter Block. Von sich aus werden Methoden nicht aktiv; der Code im Methodenkörper wird erst dann ausgeführt, wenn die Methode aufgerufen wird – so wie oben innerhalb der act()-Methode.

Wenn wir mit dem Code für das Quadrat, das Fünfeck, usw. entsprechend verfahren ist Joes act()-Methode deutlich einfacher zu verstehen:

drawTriangle();
drawSquare();
drawPentagon(); 
...

Schreiben Sie zumindest die drawSquare()-Methode und den zugehörigen Methodenaufruf in act().

Jetzt zeichnet Joe also zuerst ein Dreieck und dann ein Quadrat, und dieses Verhalten ist im Methodenkörper der act()-Methode auch sehr leicht herauszulesen – wir haben einfach die tatsächliche Ausführung in die entsprechend benannten Methoden verschoben. Allerdings mussten wir uns die gewonnene Übersichtlichkeit mit ein paar zusätzlichen Zeilen Code erkaufen – was es durchaus Wert sein kann. Es geht aber noch schlauer:

3. Übergabewerte

Wenn Sie den Code innerhalb der beiden Methoden drawTriangle() und drawSquare() vergleichen, dann sehen Sie hoffentlich, dass sich diese beiden Methoden eigentlich nur in zwei Punkten unterscheiden:

  • ob drei oder vier Seiten (edges, Kanten) gezeichnet werden (Anzahl Durchgänge der for-Schleife)
  • ob die Drehung (=Winkel in Grad/degrees) an den Ecken 120 oder 90 Grad beträgt

Da die Summe aller Winkel sich zu 360 Grad addieren muss, kann man den Winkel ganz einfach aus der Anzahl der Seiten berechnen:

degree = 360/edges;

Wüssten wir also die gewünschte Anzahl an Seiten, dann könnten wir eine Methode schreiben, die ein Vieleck/Polygon mit beliebig vielen Seiten zeichnet:

public void drawPolygon(){ //hier wird die neue Methode definiert
    for (int i=0; i<edges; i++){
        moveAndDraw(50);
        turn(360/edges);
    }
}

Kopieren Sie diese neue, flexible Methode in die Klasse Joe.

Selbst wenn Sie den entsprechenden Methodenaufruf in der act()-Methode platzieren, wird es allerdings noch nicht funktionieren, da die Variable edges nicht bekannt ist – wie auch, sie wird schliesslich nirgends initialisiert. Innerhalb der drawPolygon()-Methode wird an zwei Stellen auf den Wert der Variable mit dem Namen edges (bzw. auf den Inhalt der Kiste mit der Anschrift edges) zugegriffen, obwohl es diese Variable/Kiste gar nicht gibt. Was also tun?

Sie haben ja bereits Methoden benutzt, denen man beim Aufruf zusätzliche Informationen mitgeben muss, damit sie funktionieren. Ein Beispiel ist die Methode turn() – hier muss man innerhalb der Klammern einen Wert (eine ganze Zahl) angeben, die bestimmt, um wie viel Grad der Actor sich dann dreht. Auch bei move() oder moveAndDraw() braucht es eine Ganzzahl (int), die dann bestimmt, um wie viele Pixel sich der Actor nach vorne bewegt. Die Methode drawPolygon() soll jetzt so umgestaltet werden, dass auch ihr ein Wert übergeben werden kann, der dann die Anzahl der Seiten bestimmt. Dazu bedarf es zweier kleiner Änderungen:

  • der Methodenaufruf muss angepasst werden, damit er einen Wert übergibt. Die Klammern nach dem Namen dürfen jetzt also nicht mehr leer sein, sondern müssen die Anzahl der Seiten enthalten, z.B.
    drawPolygon(3); //für ein Dreieck
  • der Methodenkopf muss angepasst werden, so dass die Methode erwartet, vom Aufruf einen ganzzahligen Wert übergeben zu bekommen. Dazu schreibt man in die Klammern nach dem Methodennamen einen zum übergebenen Wert passenden Typ und Namen, z.B.
    public void drawPolygon(int edges){...} //erwartet eine ganze Zahl und nennt sie edges

Nehmen Sie diese Änderungen in ihrem Code vor. Jetzt sollten Sie Joe beliebige Vielecke zeichnen lassen können – durch Angabe verschiedener Übergabewerte im Methodenaufruf, oder indem Sie die Methode mit einem Rechts-Klick auf Joe interaktiv aufrufen und im erscheinenden Fenster die Anzahl Seiten festlegen.

Um die Flexibilität der neuen Methode zu testen, könnte man jetzt natürlich den zu übergebenden Wert auch wieder an eine Variable koppeln, anstatt die Zahl direkt hin zu schreiben. Sehen Sie welche Figur sich ergibt, wenn Sie Joes act()-Methode so gestalten (erst nachvollziehen, dann ausprobieren):

public void act(){
    for (int i=3; i<8; i++){
        drawPolygon(i);
    }
}

Und jetzt nehmen wir an, Sie wollen die oben entstehende komplexe Figur zehn Mal zeichnen, jeweils gedreht um 36 Grad. Dazu lagern Sie obigen Code in eine neue Methode (z.B. drawMultiPoly()) aus und rufen diese neue Methode dann 10-mal innerhalb der act()-Methode auf, jeweils mit einer Drehung von 36 Grad zwischen den Aufrufen. Ausprobieren.

Wie Sie sehen, ist die neue Methode drawPolygon() recht vielseitig (sie hat drawTriangle(), drawSquare() und weitere ähnliche Methoden überflüssig gemacht), und wir können sie aus dem Körper jeder anderen Methode gebrauchen/aufrufen, indem wir einfach den Methodennamen angeben und die Klammern dahinter (daran erkennt man einen Methodenaufruf!) – falls die Methode erwartet, einen Wert übergeben zu bekommen (s. Methodenkopf), dann müssen wir einen vom Typ her passenden Wert in die Klammern des Aufrufs schreiben. Wie eigentlich immer gilt auch hier, dass man den Wert direkt hinschreiben kann – z.B. move(10) –, oder aus einer Variable holen – z.B. move(meineZahl), vorausgesetzt meineZahl ist vom Typ int –, oder zuerst ausrechnen – z.B. move(meineZahl + 10).
Was aber, wenn man mehr als einen Wert übergeben möchte?

Als Beispiel schauen wir uns nochmal die Methode drawPolygon() an – im Moment haben die Vielecke immer eine Seitenlänge von 50 Pixel. Auch das kann man variabel gestalten:

  • im Methodenkopf festlegen, dass ein zweiter Wert erwartet wird, z.B.
public void drawPolygon(int edges, int edgeLength){...}

den neuen Wert im Körper der Methode verwenden, also

moveAndDraw(50);

ersetzen durch

moveAndDraw(edgeLength);

im Aufruf einen zweiten Wert übergeben, z.B.

drawPolygon(5,20); //ein Fünfeck mit 20 Pixeln Seitenlänge

Ändern Sie die Methode drawPolygon() entsprechend. Ändern Sie anschliessend den Code in der act()-Methode so, dass insgesamt 5 Fünfecke gezeichnet werden, mit einer Seitenlänge von 30, 40, 50, 60 und 70 Pixeln.

Aufgabe

Lösen Sie die Aufgaben 1-3 des Aufgabenblatts: Greenfoot_Methoden.pdf

4. Rückgabewerte

Wie man Information (Werte) in eine Methode hineinbekommt, haben Sie jetzt gesehen – aber wie bekommt man Information wieder heraus? Schreiben Sie zunächst das Beispiel ab und schauen Sie sich das Resultat einer Ausführung von act() an:

Quelle: Oinf

Arbeitet man mit Rückgabewerten, so gilt es drei Dinge zu beachten:

(1) Im Methodenkopf muss definiert werden, dass die Methode einen Wert zurückgibt. Dazu schreibt man an die zweite Stelle (anstelle des void) den Typ des Rückgabewerts, z.B. double, boolean oder int. Z.B:

public double saldo(double notenWert){...} //diese Methode gibt eine Fliesskommazahl zurück
Dies könnte die Definition einer Methode sein, welche angibt, wie viele Saldopunkte eine Einzelnote liefert – und zwar durch die Rückgabe eines entsprechenden Zahlenwerts.
Rückgabewert und Übergabewert (sofern vorhanden) müssen nicht von gleichem Typ sein.

(2) Im Methodenkörper muss dafür gesorgt werden, dass ein Wert des entsprechenden Typs auch tatsächlich zurückgegeben wird, meist nachdem man ihn berechnet hat. Dazu braucht man das Schlüsselwort return und direkt dahinter schreibt man den zurückzugebenen Wert.
In der obigen Methode saldo() haben wir diese Rückgabe an zwei möglichen Orten genutzt (danach wird die Methode sofort verlassen). Man könnte aber auch den Wert zunächst in einer Variablen speichern z.B. saldopunktzahl = notenWert - 4; und dann ganz am Schluss zurückgeben: return saldopunktzahl;

(3) Beim Aufruf der Methode sollte man dafür sorgen, dass mit dem Rückgabewert auch etwas angefangen wird, z.B. könnte man den zurückgegebenen Wert in einer Variablen speichern:

double punktzahl = saldo(5.5);

oder man kann ihn direkt benutzen, beispielsweise in einer Verzweigung oder Ausgabe:

System.out.println(saldo(5.5));

Aufgabe

Nun zum Aufgabenblatt Greenfoot_Methoden.pdf ab Aufgabe 4.

Anderes Beispiel:

  • Im Methodenkopf muss definiert werden, dass die Methode einen Wert zurückgibt. Dazu schreibt man an die zweite Stelle (anstelle des void) den Typ des Rückgabewerts, z.B. boolean oder int. Z.B.:
    public boolean isEven(int zahl){...}
    //diese Methode gibt einen Wahrheitswert zurück
  • Dies könnte die Definition einer Methode sein, welche die Frage beantwortet, ob eine Zahl gerade ist oder nicht – und zwar durch die Rückgabe eines entsprechenden Wahrheitswerts.
  • Im Methodenkörper muss dafür gesorgt werden, dass ein Wert des entsprechenden Typs auch tatsächlich zurückgegeben wird, meist nachdem man ihn berechnet hat. Dazu braucht man das Schlüsselwort return und direkt dahinter schreibt man den zurückzugebenen Wert.
    Für die obige Methode isEven() würde eine einzige Zeile Code genügen: return zahl%2 == 0;
  • Beim Aufruf der Methode sollte man dafür sorgen, dass mit dem Rückgabewert auch etwas angefangen wird, z.B. könnte man den zurückgegebenen Wert in einer Variablen speichern:
    boolean isEvenAnswer = isEven(17);
    oder man kann ihn direkt benutzen, beispielsweise in einer Verzweigung:
    if(isEven(meineZahl)){...}
  • Definieren Sie die Methode isEven() in der Klasse Joe und testen Sie (interaktiv oder über die Konsolenausgabe), dass Sie wie gewünscht funktioniert.
  • Schaffen Sie es, den bisherigen Code so abzuändern, dass jede zweite Seite eines Vielecks in einer anderen Farbe gezeichnet wird? Tipp: Benutzen Sie die neue isEven()-Methode und die setColor()-Methode von Turtle.

5. Existierende Methoden benutzen

Selbst Methoden schreiben ist das eine – wie Sie an den obigen Beispielen hoffentlich gesehen haben, kann das Auslagern von Codes in sinnvoll benannte Methoden ein Programm übersichtlicher machen, es kann das Programm flexibler machen, und man kann sich oft eine Menge Schreibarbeit ersparen, indem man für häufig gebrauchte Funktionen eine entsprechend flexible Methode definiert und benutzt.

Praktischerweise enthalten sowohl die Programmiersprache Java als auch das Programm Greenfoot viele vordefinierte Klassen, deren ebenfalls vordefinierte Methoden man benutzen kann. Eigentlich ist Programmieren also ein bisschen wie Lego spielen – es gibt einen Kasten mit vielen unterschiedlichen Bauteilen ( = Klassen), die unterschiedliche Eigenschaften und Funktionen ( = Instanzvariablen und Methoden) haben, und sein Modell ( = Programm) bastelt man sich zusammen, indem man die existierenden Bauteile benutzt und zu neuen, komplizierteren Bauteilen kombiniert. Woher aber weiss man, welche Bauteile man verwenden kann und wie man sie benutzt?

Genau dafür gibt es APIs (Anwendungs-Programmier-Schnittstelle, bzw. Klassendokumentation). Die Dokumentation der Greenfoot-Klassen finden Sie:

Schauen Sie sich die Dokumentation der Actor-Klasse an.
Einige der unter „method summary“ aufgeführten Methoden sollten ihnen bekannt vorkommen, z.B. act(), move(), getX(), etc.

Wie Sie sehen ist in der Dokumentation der Methodenkopf angegeben, zusammen mit einem kurzen Kommentar, der die Funktionsweise dieser Methode erklärt, z.B:

Quelle: OInf
Screenshot von Greenfoot-API

Diesen Informationen kann man also entnehmen, wie diese Methode zu benutzen ist: beim Aufruf muss eine Ganzzahl übergeben werden, zurückgeben wird diese Methode nichts. Falls das noch nicht reicht, finden sich unter „method details“ noch ein wenig detailliertere Informationen, insbesondere zu Übergabe- und Rückgabewerten ( = Parameters):

Quelle: OInf.ch
Screenshot von Greenfoot-API

Zum Benutzen einer Methode braucht man also nur Informationen zum Methodenkopf, über den Code im Körper muss man nichts wissen. Das ist eigentlich ziemlich praktisch, denn auf diese Weise kann man existierende Klassen mit all ihren Fähigkeiten benutzen, ohne verstehen zu müssen, wie genau diese Fähigkeiten zustande kommen – z.B. wie genau der Actor nun gedreht wird.

Hinweis: Die Dokumentationsansicht wird übrigens automatisch aus dem eigentlichen Code erstellt – eigentlich werden nur die Körper der Methoden versteckt und die restlichen Informationen (Methodenköpfe und Methodenkommentare) etwas übersichtlicher dargestellt. Sie können das sehen, wenn Sie in einer eigenen Klasse rechts oben “Documentation” wählen anstatt “Source Code”. Genau aus diesem Grund sind die Methodenkommentare sehr wichtig für die Benutzbarkeit von Code.

Eine weitere interessante Methode des Actors sieht so aus:

Quelle: OInf.ch
Screenshot von Greenfoot-API

Diese Methode braucht also keinen Übergabewert, aber sie gibt einen Wert des Typs World zurück. Man könnte sie daher folgendermassen benutzen:

World myWorld = getWorld();  //speichert die zurückgegebene World in der Variablen myWorld

Die getWorld()-Methode der Actor-Klasse ist ziemlich nützlich, denn so kommt man an die Welt, in der der Actor lebt; damit kann man dann all deren Methoden im Code der Actor-Klasse aufrufen, z.B:

World myWorld = getWorld();
int breite = myWorld.getWidth(); //Breite der Welt erfragen und speichern

oder knapper, die beiden obigen Zeilen zusammenfassend:

int breite = getWorld().getWidth(); //der Rückgabewert kann also auch direkt benutzt werden

Nochmal in Kürze: Um eine Methode aufrufen zu können, muss man Zugriff haben auf das Objekt, in dem sie definiert ist. Auf Methoden der eigenen Klasse und aller übergeordneten Klassen kann man immer zugreifen, für den Aufruf genügt der Methodenname. Methoden anderer Klassen ruft man mit Punktsyntax auf, also indem man vor den Methodennamen das entsprechende Objekt schreibt (meist den Namen der Variablen, in der es gespeichert ist), gefolgt von einem Punkt.

Um Ihr Verständnis des oben Gesagten zu testen, versuchen Sie sich bitte an der folgenden Aufgabe:

Definieren Sie in der Klasse Joe eine Methode drawCross(), die ein Kreuz zeichnet, dessen Mittelpunkt genau auf der Mitte der Welt liegt, und dessen Balken jeweils ein Drittel so lang sind wie die Welt hoch, bzw. breit ist. Erfragen Sie dafür die entsprechenden Werte von der Welt, so dass sich das Kreuz entsprechend anpasst, wenn die Dimensionen der Welt geändert werden. 

Selbständige Übung

Aufgabe

In Joe2.0.zip finden Sie ein erweitertes Joe-Szenario, das in der Oberklasse Turtle noch weitere Zeichnen-Methoden zur Verfügung stellt. Programmieren Sie hier einen Joe (und später vielleicht noch weitere Turtle-Unterklassen), der interessante, selbst ausgedachte Muster zeichnet – am besten interaktive, die sich verändern, wenn der Benutzer die Maus bewegt oder klickt.

Ein Beispiel dafür, was man mit dem Joe-Szenario anstellen kann, finden Sie in Joe2.1.zip. Bei den meisten Mustern bewirkt das Klicken oder Bewegen der Maus eine Veränderung. Mit der Space-Taste wechselt man zu einem anderen Muster.