Einführung in die (imperative) Programmierung: Methoden
2 Wiederholung: if-else Anweisung int number =?; if ( BEDINGUNG ) { if (number > 0) { SATZ; BLOCK System.out.println("Größer 0"); else { SATZ; BLOCK else { System.out.println("Kleiner gleich 0"); BEDINGUNG == WERT VARIABLE AUSDRUCK boolean
3 Wiederholung (Switch) switch ( AUSDRUCK ) { char color =?; case LITERAL 1 : { SATZ; BLOCK break case : case LITERAL n : { SATZ; BLOCK switch (color) { case 'R': { System.out.println("bremsen"); break; case 'O': { System.out.println("gas"); break; AUSDRUCK break int String enum case 'G': { System.out.println("fahren"); break;
4 Wiederholung (Schleifen) while ( BEDINGUNG ) { SATZ; BLOCK UPDATE int n =?; int iteration = 1; while (iteration <= n) { System.out.println(iteration * iteration ); iteration = iteration + 1; do { while SATZ; BLOCK ( BEDINGUNG ) ; int iteration = 1; do { System.out.println(iteration * iteration ); iteration = iteration + 1; while (iteration <= n); for ( ZUWEISUNG ; BEDINGUNG ; UPDATE ) { SATZ; BLOCK for(int iteration = 1; iteration <= n; iteration = iteration + 1) { System.out.println(iteration * iteration );
5 Don t repeat yourself Annahme: Java habe keinen Multiplikationsoperator Die Multiplikation ist die wiederholte Anwendung der Addition a* b = b + b +... + b = a a i= 1 b Vereinfachte Annahme: a, b >= 0 Diese mathematische Vorschrift lässt sich durch eine Schleife umsetzen: int a =?; int b =?; int result = 0; for (int i = 1; i <= a; i++) { result += b; Ohne Multiplikation müsste überall, wo der *-Operator genutzt würde, obiger Code stehen Der Code sähe allerdings immer gleich aus In der Programmierung herrscht das DRY-Prinzip: Don t repeat yourself
6 Don t repeat yourself Mit den aktuellen Mitteln führt allerdings nichts an DRY vorbei Es wird ein Konstrukt benötigt, mit dem immer wiederkehrender Code ausgelagert werden kann In der imperativen Programmierung: Funktion bzw. Methode bzw. Prozedur In Java: Methode Methodenkopf MODIFIKATOREN optional RÜCKGABETYP BEZEICHNER ( DATENTYP PARAMETER-, ) BEZEICHNER optional { SATZ; BLOCK return RETURN-WERT ; MODIFIKATOREN SICHTBARKEIT RETURN-WERT RÜCKGABETYP static final SICHTBARKEIT public protected private Beispiel: Main-Methode Methodenrumpf public static void main(string[] args) {
7 Statische Methode Methoden sind Teil von Javas OOP-Ansatz Die MODIFIKATOREN legen folgendes fest: Sichtbarkeit (public, private, protected): Von wo darf die Methode aufgerufen/ausgeführt werden Zugehörigkeit (static): Gehört die Methode zum Objekt oder zur Klasse Überschreibbarkeit (final): Kann die Methode überschrieben werden Für den imperativen Ansatz sind diese Festlegungen mit Ausnahme der Zugehörigkeit irrelevant Aktuell sind Objekte noch unbekannt Klassen wurden zumindest schon gesichtet Der Modifikator static steht für zur Klasse gehörig Daher vorerst: static RÜCKGABETYP BEZEICHNER ( DATENTYP PARAM- BEZEICHNER, ) { optional
8 Die Multiplikations-Methode Die Multiplikationsvorschrift als Methode: static int mult(int a, int b) { // Annahme: a, b >= 0 int result = 0; for (int i = 1; i <= a; i++) { result += b; return result; Mit dem BEZEICHNER wird der Name der Methode festgelegt (hier: mult) Die Methode hat zwei Parameter a und b vom Typ int Innerhalb der Methode sind a und b Variablen Die zuvor programmierte Schleife bildet den Methodenrumpf Der Methodenrumpf ist der Scope der Parameter-Variablen Zum Schluss wird das Ergebnis der Multiplikation zurückgegeben
9 Aufruf einer Methode Ein Aufruf der Methode erfolgt über seinen Namen: mult(27, 37); Pro Parameter muss ein Wert/Ausdruck des definierten Typs übergeben werden a und b wurden als int definiert, die Werte 27 und 37 sind vom Typ int Es gelten die Typumwandlungsregeln von Operatoren Werte eines kleineren Typs werden automatisch umgewandelt Werte eines größeren Typs müssen per Cast umgewandelt werden Typfremde Werte führen zu Compilerfehlern mult(27.0f, 37); mult((int) 27.0f, 37); mult(27, false); Die Werte werden in der Reihenfolge der Parameterdefinition initial zugwiesen a = 27 b = 37
10 Rückgabe einer Methode Eine Methode muss ein Ergebnis liefern Dieses Ergebnis muss vom angegebenen RÜCKGABETYP sein Die Rückgabe erfolgt über das Schlüsselwort return und muss der letzte ausgeführte Satz der Methode sein static int mult(int a, int b) { int result = 0; for (int i = 1; i <= a; i++) { result += b; return result; Die Multiplikation soll int liefern Die Variable result hält das Zwischenergebnis der n-ten Addition Sie ist vom Typ int Am Ende wird der zuletzt in result gespeicherte Werte zurückgegeben Methodenaufrufe können daher analog zu Operatoren in Ausdrücken und insbesondere Zuweisungen genutzt werden int somevalue = mult(27, 37) + 1; // somevalue == 1000
11 Wohin mit der Methode Im Default-Rahmen tauchte bereits eine Methode auf: die main-methode Methoden können nicht innerhalb von Methoden geschachtelt werden Methoden müssen innerhalb von Klassen definiert werden class Program { Methoden public static void main(string[] args){ Programmfluss class Program { static int mult(int a, int b) { int result = 0; for (int i = 1; i <= a; i++) { result += b; return result; public static void main(string[] args){ int somevalue = mult(27, 37) + 1; System.out.println(someValue);
12 Der spezielle Rückgabetyp void Die main-methode hat eine besondere Auffälligkeiten: Ihr Rückgabetyp ist der bisher unbekannte Datentyp void Zudem liefert sie keinen Rückgabewert bzw. endet nicht mit return Der Datentyp void steht für Nichts Ist der Rückgabetyp einer Methode void, so kann sie kein Ergebnis liefern (oder liefert, wenn man so will, automatisch DAS leeres Ergebnis) Es kann keine Variablen vom Typ void geben! (es gibt keinen Wert vom Typ void) Methoden mit void dienen zumeist zum Ausführen von Nebeneffekten Die bereits benutzte Methode println ist so eine Methode System.out.println("Hello World"); Sie liefert kein Ergebnis und veranlasst sozusagen nebenläufig, dass ein Wert auf der Console ausgegeben wird
13 Mehrfache return-aussagen Jede Methode mit einem Rückgabetyp (!=void) muss ein Resultat liefern Die return-aussage beendet die Methode D.h. nachfolgender Code wird in keinem Fall ausgeführt Mehr noch: Compilefehler return -1; System.out.println( ); error: unreachable statement Man betrachte folgende Methode, welche die modulo -Operation (Folie 7, Vorlesung 3) auf positiven ganzen Zahlen ohne den %-Operator und ohne Division implementiert static int modulo(int n, int mod) { if (n <= 0 mod <= 0) return 0; if (n == mod) return 0; falls n oder mod <= 0 Resultat: 0 n == mod => n % mod == n % n == 0 int temp = 0; while ((temp + mod) <= n) { temp += mod; return n - temp; Finde die größte Zahl <= n, die durch mod teilbar ist: Solange mod addieren, bis die nächste Addition > n ist Die Differenz der größten Zahl <= n und n liefert den Rest der ganzzahligen Division
14 Mehrfache return-aussagen Die Methode hat drei return-aussagen Das funktioniert, da die ersten beiden an eine Bedingung gebunden sind Der Compiler kann nicht feststellen, ob eine der Bedingungen eintritt, aber: Er sieht, dass jeder Zweig zu genau einem return führt modulo(n, mod); n <= 0 mod <= 0 return 0; n == mod return 0; return n - temp; Egal welche Eingaben gemacht wurde, der letzte ausgeführte Satz ist immer ein return
15 Methoden-Signatur Erinnerung: Innerhalb eines Gültigkeitsbereiches/Scopes sind Variablennamen eindeutig Der Gültigkeitsbereich einer Methode ist die Klasse, in der sie definiert wurde Auch Methoden müssen innerhalb ihres Scopes eindeutig sein Allerdings entscheidet sich die Eindeutigkeit einer Methode nicht einzig anhand ihres Namens sondern anhand ihrer Signatur BEZEICHNER ( DATENTYP, DATENTYP, ) optional static boolean test(int input, int expected) static int mult(int a, int b) static int modulo(int n, int mod) Signatur test(int, int) mult(int, int) modulo(int, int) Zwei Methoden sind gleich, falls es folgende Übereinstimmung gibt Name der Methode Typ der Parameter Reihenfolge der Parameter
16 Methoden-Signatur (II) Unter den Regeln sind folgende Methoden demnach alle verschieden: static int mult(int a, int b) static int mult(float a, float b) static int mult(double a, double b) static int mult(float a, int b) static int mult(int a, float b) static int mult(int a, int b, int c) mult(int, int) mult(float, float) mult(double, double) mult(float, int) mult(int, float) mult(int, int, int) Dieses Verhalten ist bereits vorgekommen: Operatorüberladung (Vorlesung 2, Folie 17ff) Bei der Methodengleichheit spielen keine Rolle: Parameternamen Rückgabetyp Sichtbarkeit Zugehörigkeit Überschreibbarkeit (nicht zu verwechseln mit Überladung!)
17 Methodenaufruf bei Überladung Welche Methode wird aufgerufen mult(27, 37.0f) mult(27, 37L) static int mult(int a, int b) static float mult(float a, float b) static double mult(double a, double b) static float mult(float a, int b) static int mult(int a, float b) static int mult(int a, int b, int c) static int mult(int a, int b) static float mult(float a, float b) static double mult(double a, double b) static float mult(float a, int b) static int mult(int a, float b) static int mult(int a, int b, int c) 1.Zuerst wird die Anzahl der Parameter abgeglichen 2.Dann die Typen der übergebenen Parameter verglichen 3.Falls keine Methodensignatur die nötigen Typen aufweist: Ist eine automatische Typumwandlung möglich, so dass die Parametertypen übereinstimmen Die Methode mit der größten Übereinstimmung wird aufgerufen
18 Rekursive Methoden Man betrachte folgende Methode static int mult(int a, int b) { switch (b) { case 0: return 0; case 1: return a; default: { return a + mult(a, b 1); mult(2, 3); b == 3 default: return 2 + mult(2, 2); b == 2 default: return 2 + mult(2, 1); b == 1 == case 1: return 2 6 b == 2 default: return 2 + (2 + 2); b == 2 default: return 2 + 2;
19 Rekursive Methoden Die Methode liefert das Ergebnis der Multiplikation von a und b (mit a, b >= 0) Der Methodenrumpf enthält keinerlei Schleifen (while, do-while oder for) Innerhalb des default-zweiges ruft die Methode sich selbst erneut auf Ein solcher Selbstaufruf wird Rekursion genannt Rekursion muss nicht immer so ersichtlich sein: Statt sich selbst, könnte mult eine andere Methode aufrufen, die dann ihrerseits wieder mult aufruft Eine rekursiver Aufruf sollte an eine Bedingung geknüpft sein Diese Bedingung wird Abbruchbedingung genannt Analog zu Schleifen muss jeder erneute Aufruf dafür sorgen, dass der Programmstatus sich der Abbruchbedingung nähert Im Beispiel: Mit jedem Selbstaufruf wird der Wert von b um 1 verringert b läuft gegen 1 und somit gegen case 1 Im Fall von b == 1 (oder auch 0) liefert mult ein Ergebnis ohne Selbstaufruf Rekursion wird beendet/abgebrochen
20 Rekursive Methoden: StackOverflowException Während eine unendliche Schleife meist unendlich weiterläuft, führt eine unendliche Rekursion häufig zum Abbruch des Programms static void call(int number) { call(number + 1); public static void main(string[] args) { call(251) Die Fehlermeldung: Exception in thread "main" java.lang.stackoverflowerror Als Stack wird der Platz im flüchtigen Speicher (RAM) bezeichnet, der dem Programm zum Aufruf von Methoden bereitgestellt wird Fordert das Programm mehr Speicher als zur Verfügung gestellt wurde, so kommt es zum Stack Overflow
21 Speicherkunde: Methodenaufruf Was passiert (sehr vereinfacht) bei der Ausführung einer Methode besetzt leer programm main-frame 37 27 call foo a = 37 b = 27 temp return 37 27 call foo a = 37 b = 27 temp temp = 999 return 1000 37 27 call foo 37 27 999 1000 37 27 call 1000foo 37 27 999 1000 37 27 1000 Man betrachte folgende Methode und deren Aufruf: static int foo(int a, int b) { int temp = n * m; return temp + 1; public static void main(string[] args) { int value = foo(37, 27);
22 Speicherkunde: Methodenaufruf (II) Der Bytecode eines Java-Programmes wird zur Ausführung in den flüchtigen Speicher (RAM) des Zielsystems geladen Der Speicher ist aufgeteilt in Zellen einer bestimmten Größe Der Speicherplatz, den ein Programm belegen darf, ist begrenzt Zur Laufzeit benötigen Variablen/Literale ebenfalls Speicherplatz Der Datentyp legt dabei die benötigte Größe fest Zur Anschauung vereinfacht(!): Ein int-werte benötigt eine Zelle Für jeden Methodenauf wird Speicherplatz benötigt Dieser Speicherplatz wird Frame genannt Die Methode main belegt in Java immer den ersten Frame Die Größe des Speicherbereiches richtet sich nach folgenden Kriterien: Rückgabewert Anzahl und Typ der Parameter Lokale Variablen (solche, die innerhalb der Methode deklariert werden)
23 Speicherkunde: Methodenaufruf (III) Im Beispiel: In main wird foo mit zwei Werten aufgerufen Beide Werte sind vom Typ int und belegen somit jeweils eine Zelle Die Rückgabe ist vom Typ int und wird value gespeichert value würde damit eine Speicherzelle belegen Es wird allerdings die Zelle des Aufrufes wiederverwendet Für den Aufruf von foo: Der Rückgabewert ist vom Typ int eine Speicherzelle Zwei Parameter vom Typ int zwei Speicherzellen Eine Hilfsvariable vom Typ int eine Speicherzelle Nach dem Aufruf wird der Rückgabewert in den main-frame kopiert Der foo-frame wird wieder vom Stack entfernt Die Werte 27 und 37 für den foo-aufruf können ebenfalls entfernt werden
24 Rekursive Methoden: StackOverflowException Zurück zu static void call(int number) { call(number + 1); public static void main(string[] args) { call(251) Mit jedem Aufruf von call() wird ein neuer Frame auf dem Stack erzeugt Da call keinerlei Abbruchbedingung enthält, wird auch die Rekursion nicht beendet Dementsprechend wird niemals ein call-frame wieder vom Stack entfernt Der für das laufende Programme nötige Speicher wird immer größer Der Speicher für Anwendung ist begrenzt Sobald diese Grenze erreicht wurde, informiert das Betriebssystem die JRE Diese wiederrum bricht das Programm mit einer StackOverflowException ab
25 Zusammenfassung Wie wird eine Methode definiert? Wo gehören Methoden hin? Wie wird eine Methode aufgerufen? Was ist der Rückgabetyp- und wert einer Methode? Was ist spezielle Rückgabetyp void? Was ist die Signatur einer Methode und welche Bedeutung hat diese? Was ist Rekursion? Warum führt eine endlose Rekursion zu einer StackOverflowException?
26 Aufgaben Aufgabe 1 Schreiben Sie eine Methode test, die zwei int-werte erwartet und true zurückliefert, falls diese identisch sind, ansonsten false Erweitern Sie die Methode test, so dass sie per Nebeneffekt ihr Rückgabeergebnis umschrieben ausgibt, in etwa: Die Eingaben sind identisch. Die Eingaben sind verschieden. Aufgabe 2: Schreiben Sie eine Methode mult zur Multiplikation zweier ganzzahliger Zahlen (int) ohne den mult-operator Achtung: In der Vorlesung wurde die Multiplikation nur für positive ganze Zahlen umgesetzt! Nutzen Sie wenn möglich die Methode aus Aufgabe 1 zum Testen
27 Aufgaben Aufgabe 3 Schreiben Sie eine Methode fib zur Berechnung der n-ten Fibonacci Zahl Fibonacci-Zahlen: {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55,... Nutzen Sie dabei Rekursion, indem Sie folgende mathematische Vorschrift umsetzen: 0 fib( n) = 1 fib( n 1) + fib( n 2) falls n = 0 falls n = 1 sonst Was fällt beim Aufruf von fib( )ab n > 60 auf?