Ganz großartig. Für die, die zu spät gekommen sind,
ich zeige es nochmal. Es ist einfach zu schön.
Ja, ganz toll. Das ist das Projekt Turtle Graphics,
das ihr letzte Woche ja schon in den Übungen gesehen habt.
Ich habe da mal so ein bisschen was programmiert,
dass das ein bisschen netter aussieht.
Ja, sehr schön. Das zeige ich deshalb, weil
diese Woche der TGCC startet. Ja, es ist sensationell.
Ich weiß, viele von euch haben Sehnsüchte darauf gewartet.
Das ist der Turtle Graphics Creativity Contest
Ja, wir haben ihn über Jahre angeboten und im letzten Jahr
ist er ausgefallen und dieses Jahr gibt es ihn wieder.
Die Idee ist, dass ihr mit Turtle Graphics und den Hilfsmitteln,
die ihr jetzt auf Level 1 und 2 kennengelernt habt,
das ist relativ schwierig.
Also ihr dürft keine Race verwenden, ihr dürft keine Sammlungen verwenden.
Es gibt so einige Sachen, die ihr nicht verwenden dürft.
Ihr sollt damit etwas bauen, was so ähnlich ist wie das,
was ich gerade gezeigt habe. Also irgendwie eine Animation,
irgendwas, was nett aussieht, kann auch ein Cartoon sein,
kann auch einfach nur ein Bild sein,
was nett aussieht.
Man kann ja mit Turtle Graphics
durchaus auch zeichnen
Das Einzige, was sein muss, wer an dem Wettbewerb teilnehmen will,
der sollte ein solches Blue-Jay-Projekt einreichen, gezippt.
Und darin sollte dann eine oder mehrere Klassen sein,
die dieses Turtle-Graphics-Projekt hier verwenden.
Also hier den Dompteur, wie in unseren Spiraldompteur oder ähnliches.
Also das kennt ihr ja schon aus der Übung und ihr
werdet auch in dieser Woche damit nochmal ein bisschen was programmieren.
Aber die Idee ist halt, ihr könnt hier einfach irgendwas reinreichen,
was nett aussieht, was irgendwie kreativ ist,
was wie auch immer
Ich habe hier nochmal einen anderen Teilnehmer aus den Vorjahren.
Das war ein Gewinner, glaube ich, mehr oder weniger.
Hier Pong, auch sehr schön, ein nettes, kleines Projekt.
Das war noch ein bisschen Älteres. Ups, Moment.
Ach so, Pong, so, jetzt hier, Start.
Und das sieht dann so aus, die haben sich also
richtig Mühe gegeben, da so ein bisschen was zu machen.
Hier kommen die Protagonisten, zwei Pongstäbe.
Ich weiß nicht, ob ihr Pong kennt,
das ist das allererste Fernseh-Videospiel
Ja, die haben das noch so ein bisschen abgewandelt.
Die haben jetzt in der Mitte noch so einen Gravitationspunkt.
Das heißt, der Ball wird,
wenn er durch die Mitte geht, so ein bisschen abgelenkt.
Und dann, ups, ist das Spiel zu Ende.
Also sowas kann man auch machen, so eine Animation.
Auch sehr schön. Der hat anscheinend verloren hier. Und
das geht auch. Kann man also alles mit Turtle Graphics
ganz schön machen. Die Teilnahmebedingungen für den Turtle Graphics
Creativity Contest sind jetzt im Netz. Das heißt,
die müsst ihr hier irgendwo auch das Komsi aufhaben.
Wo ist es denn hier? Da ist es.
Da SE1 kommen sie
Ist jetzt ein Material mit Turtlegraphics, Creativity Contest.
Alle Teilnahmebedingungen, was wir ermutigen, ist,
dass Programmieranfänger teilnehmen, weil die nämlich bevorzugt behandelt werden.
Ihr sollt in dem Projekt auch ein bisschen Rekursion reinbauen,
was wir ja heute kennenlernen. Also dann geht es um Rekursion.
All das werdet ihr dann nachlesen können. Wie gesagt,
die Teilnahmebedingungen, die Preisverleihung ist dann vor Weihnachten.
So als Bescherung gibt es dann großartige Gewinne.
So, das erst mal nur zum Turtlegraphics Creativity Contest
Ich möchte euch wirklich alle ermuntern, da irgendwas einzureichen.
Es ist, es macht einfach Spaß. Tannahmebedingungen,
ab morgen im Kommen, sie stimmen gar nicht schon,
ab heute im Kommen sie. Gut. Ich weiß ja nicht,
wie es euch so geht, bei diesen Temperaturen,
wenn ich so auf dem Fahrrad sitze und so richtig vor mich hinzittere,
dann wird mir unglaublich weihnachtlich.
Und ich habe hier ein Gedicht mitgebracht von einem Herrn von Bülow,
der ist auch bekannt als Loriot und ein ziemliches Genie gewesen.
Der hat einiges Gutes fabriziert fürs deutsche Fernsehen
Unter anderem auch dieses wunderbare Gedicht und ich will es euch
nicht vorenthalten, ich werde ein bisschen daraus vorlesen.
Das werden wir dann nachher nochmal ein bisschen verwenden,
dieses Gedicht, um es zu analysieren.
Wir machen heute Gedichtanalyse, fortgeschrittene Gedichtanalyse. Es blaut die
Nacht, die Sternlein blinken, Schneeflöcklein leis, herniedersinken.
Auf Edeltenleins grünem Wipfel häuft sich ein kleiner weißer Zipfel.
Und dort von Ferne her durchbricht den dunklen Tannen ein helles Licht.
Im Forsthaus kniet bei Kerzenschimmer die Försterin im Herrenzimmer.
In dieser wunderschönen Nacht hat sie den Förster umgebracht.
Er war ihr beides Heimes,
Pflege seit langer Zeit schon sehr im Wege,
so kam sie mit sich überein,
am Niklas Abend muss es sein
Und als das Hießlein ging zur Ruhe, das Rehlein tat
die Augen zu, er legte sie direkt von vorn,
den Gatten über Kim und Korn.
Vom Knall geweckt rümpft nur der Hase 2-3,
viermal die Schnuppernase und ruht weiter süß im Dunkeln,
der Wald die Sterne traulich funken.
Und in der guten Stube drinnen, da läuft das Försters Blut von hinnen.
Ja, also ich finde, das macht einen so feierlich.
Und wir werden das noch ein bisschen fortsetzen,
dass noch nicht alles, es geht weiter.
Sehr schön. Heute geht es um Rekursion.
Im ersten Teil geht es um Rekursion,
im zweiten Teil geht es dann um Strings
Und da werden wir dann auch so ein bisschen uns
diese Gedichtzeilen als Zeichenketten mal etwas näher ansehen.
Aber erstmal müssen wir jetzt mal so ein bisschen uns mit Rekursionen beschäftigen.
Wir haben letzte Woche kennengelernt, wie man Wiederholungen programmieren kann.
In Java mit den Schleifenkonstrukten, weil Four Duvile und Rekursion
ist jetzt eine alternative Möglichkeit in solchen Programmiersprachen, Wiederholungen zu programmieren.
Und darum geht es heute, Rekursion.
Bevor ich da jetzt in die Folien reingehe,
will ich nochmal einmal was ganz Banales zeigen,
nämlich dieses hier, das ist ein Windows Explorer-Fenster.
Das kennt ja fast keiner mehr, weil ja jetzt alle Macs haben.
Recht so. Also es gibt auch noch Windows-Rechner und die
haben eben halt solche Explorer-Fenster, das kennt ihr alle.
Also so ein Explorer Window mit so Ordnern. Und was
ich zeigen will, ist, dass diese Ordner ein Beispiel sind für Rekursionen. Also wenn ihr seht,
was in so einem Ordner drin sein kann,
also zum Beispiel hier, Vorlesung 7 ist dieser Orte,
da drin sind jetzt irgendwie mehrere Bluejay-Projekte und andere Dinge,
man sieht hier irgendwelche Dateien, dann ist es tatsächlich so,
ich kann also in einem Ordner,
kann ich wiederum einen Ordner enthalten haben
Was kann man beliebig miteinander fotten, an aller Schachtel?
Selten ist es so, dass man wirklich denselben Ordner in
sich selbst schachtelt. Das passiert nicht. Aber den Typordner,
der kann ineinander geschachtelt werden. Und das ist ein,
sagen wir, relativ anschauliches und alltägliches Beispiel für Rekursion.
Ich finde das immer ganz wichtig, dass man eben halt
versucht, es anschaulich zu machen.
Ich bin auch kein Freund von diesen mathematischen Beispielen,
aber das erste Beispiel, das wir uns jetzt ansehen zur Rekursion,
ist dann leider doch ein mathematisches Beispiel.
Im Großen und Ganzen versuche ich es ein bisschen von der Mathematik,
ich habe nichts gegen die Mathematik,
ich finde nur vieles meist anschaulicher mit Dingen aus
dem Alltag.
So, also das erste Beispiel ist hier mal die Fakultät einer Zahl.
Kennt ihr bestimmt, geschrieben irgendwie mit N-Ausrufezeichen.
Ist das Produkt aller natürlichen Zahlen von eins bis Ende.
Also N-Fakultät, die natürlichen Zahlen von eins bis Enden multipliziert.
Also vier Fakultät beispielsweise ist einmal, zweimal, dreimal vier.
Also 24. Das lässt sich in der Mathematik relativ einfach definieren.
Die Fakultät der Zahl 0 ist eins und die Fakultät einer
natürlichen Zahl N mit N größer als 0 ist N mal
N minus eins Fakultät
Das ist eine rekursive Definition, weil in der Definition der Fakultät
wieder die Fakultät benutzt wird. In der Mathematik gar kein Problem.
Kennt ihr vermutlich nur so gut? Das Schöne ist,
und deswegen nehmen wir das als Einstiegsbeispiel erst mal hier,
weil ich will erst mal nur die Mechanik von Rekursion in Programmiersprachen wie Java erklären.
Das lässt sich direkt abbilden in eine Java-Methode.
Ich habe hier einfach eine Methode Fakultät,
die soll mir für einen Parameter N,
also vom Typ Int,
einen Wert berechnen,
der die Fakultät darstellt
Ich gebe also hier das N rein, der Parameter wird hier angenommen, als formaler Parameter.
Dann habe ich hier eine lokale Variable in Result.
Dann habe ich hier eine Abfrage, wenn N gleich Null ist,
also mehr oder weniger genau diese erste Zeile hier,
dann sagen wir, ist die Fakultät 1. Ansonsten sagen wir einfach,
das Resultat, was wir berechnen wollen, ist gleich N,
dass wir zwar als Parameter bekommen haben,
mal die Fakultät von N minus 1. Und am Ende haben wir entweder direkt N gleich Null,
Ende Asylt hat gleich 1 gesetzt,
oder wir haben hier einen weiteren Aufruf gemacht,
einen Reco-7-Aufruf.
Ja,
und am Ende wird das Resultat
einfach zurückgegeben
Das ist, wie gesagt, erst mal ganz einfach und
ich habe das Beispiel natürlich auch als Blue-Jay-Beispiel hier.
Das heißt, hier habe ich diese Rekursion einfach mal als Klasse dabei.
Genauso wie hier eigentlich auf der Folie, also Fakultät,
Parameter N, Resultat, FN gleich 0 und so weiter. Und man kann hier sehen,
wenn ich jetzt hier ein Exemplar erzeuge von dieser Rekursionsklasse,
die dann mir diese Methode anbietet,
Fakultät von N und ich gebe jetzt hier irgendwie vier ein,
dann kommt da 24 raus und wenn ich jetzt hier sowas eingebe wie 5,
dann kommt da 120 raus, also Fakultät.
Scheint also zu funktionieren. Das ist jetzt erstmal so ein bisschen Magic,
warum das funktioniert, weil was ihr wahrscheinlich noch nie gesehen habt bisher,
ich weiß nicht,
wen ihr nicht das schon vorher mal gemacht habt mit Rekursionen zu programmieren,
ist ja, dass innerhalb des Rumpfes der Methode Fakultät hier Fakultät aufgerufen wird.
Das heißt, die Methode ruft sich selbst auf.
Das ist erstmal, das ist erstmal irgendwie nichts,
was jetzt, sagen wir sofort,
einem in den Sinn kommt
Was ich ganz nützlich finde, ist, sich klarzumachen,
und jetzt kommen wir nämlich so in Bereiche rein,
wie ist das eigentlich, wie funktioniert Java eigentlich,
wenn man so hinter die Kulissen ein bisschen guckt,
wenn so eine Methode aufgerufen wird, dann gibt es immer Verwaltungsinformationen,
die der Compiler zur Laufzeit anlegt, für jeden Methodenaufruf.
Und also man sieht hier, das ist der rekursive Aufruf.
Und ich zeige das mal hier an einem Kontrollflussbeispiel, weil wir ja immer sagen,
der Kontrollfluss ist für euch wichtig, ihr müsst verstehen,
dass der Kontrollfluss wie so ein roter Pfeil sozusagen durch den Quelltext durchläuft.
Also nehmen wir mal an, ich rufe jetzt diese Fakultätmethode auf mit dem aktuellen Parameter
3. Dann ist es so, wenn jetzt dieser Aufruf erfolgt,
dann wird hier erstmal sequentiell der Rumpf der Methode ausgefüllt,
ausgeführt, N ist nicht null, also Combat in Els-Zweig,
also wird es hier dazu kommen,
dass hier jetzt dieser Ausdruck berechnet wird, N mal,
Fakultät in minus 1.
So, also kommt der Kontrollfluss hier hin. Jetzt kommt
dieser komische rekursive Aufruf. Und was ganz wichtig ist,
ist, dass man sich jetzt klar macht,
dass jetzt für diesen rekursiven Aufruf ein neues Exemplar der Methode
angelegt wird,
eine neue Instanzierung
Das heißt also hier wird tatsächlich ein neuer Fakultätsaufruf angelegt.
So, also aktiviert. Man könnte auch besser wären,
vielleicht von Aktivierungen zu sprechen, weil das ist nicht so wirklich objektorientiert.
Also es sind verschiedene Aktivierungen. Die erste Aktivierung hat der
N gleich 3. Jetzt hat diese Aktivierung die zweite hier,
die kriegt ja jetzt N minus 1. Also es war vorher 3,
also kriegt das hier als Parameter 2. Und da läuft das jetzt wieder genau so,
dass der Kontrollfluss da weiter fließt bis zum Els,
hier bis zur Fakultät.
Aha, da muss erstmal dieser Ausdruck ausgewertet werden,
bevor diese Zuweisung ausgeführt werden kann.
Also eine neue Aktivierung
Hier N gleich 1. Auch hier geht es erstmal wieder runter,
weil N ist noch nicht null.
So, Fakultät wird wieder rekursiv aufgerufen. Und jetzt haben
wir die Situation, dass tatsächlich N gleich null ist.
Der aktuelle Parameter ist gleich null. Das heißt,
der Kontrollfluss geht nur direkt runter. Das heißt also von
N ist gleich null, also mit Resultat 1 gesetzt.
Das ist hier schon rot hervorgehoben. Wir springen ans Ende
. Und jetzt wird mit Return dieses Resultat zurückgeliefert.
Also wird hier jetzt eine 1 zurückgegeben. So,
also hier eine 1. Dann ist diese Aktivierung beendet.
Die Methode ist ja zu Ende, also wird sie
wieder weggenommen
Und hier kommt jetzt, also die Eins,
und weil wir hier N gleich Eins haben,
und hier kommt die Eins für dieses Ergebnis zurück,
haben wir hier einmal, Einmal Eins ist dann entsprechend Eins.
Also kommt auch hier eine Eins zurück. Dann haben wir hier N,
gleich zwei. Das Ergebnis des Aufrufs liefert eine Eins.
Also kommt hier eine Zwei zurück. Sodass an letzter Konsequenz
in dieser ersten Aktivierung der Methode Fakultät hier zwei zurückkommt,
NS3, also wird hier eine Sechs zurückgegeben.
Und das ist ja das, was wir eben auch schon
gesehen haben. Ich habe es für vier und vier,
fünf gezeigt.
Das ist ganz wichtig
Sich das erstmal so vorzustellen. Also zur Aktivierung, dass man
also eine Methode mehrfach aktivieren kann. Und das gilt eigentlich
für alle Methoden auch, dass das eigentlich irgendwie Aktivierung gibt.
Wie das verwaltet wird, das ist ganz wichtig,
dass man da zumindest ein mentales Modell davon hat.
Deswegen beschreibe ich jetzt hier den sogenannten Aufruf-Stack.
Das ist der Call-Stack oder Function-Stack. Das ist eine Speicherstruktur,
die tatsächlich zur Laufzeit, wenn ein Programm läuft,
diese Informationen, über die gerade aktiven Methoden speichert.
In sogenannten Stack-Frames, also jede Methodenaufruf,
jede Aktivierung wird in einem Stack-Frame gehalten
Da muss unter anderem die Rücksprungadresse, also wo ist denn
der Aufruf gewesen, wo ich nachher wieder zurück muss?
Und auch die lokalen Variablen, wir wissen ja,
in Methoden können lokale Varianten deklariert werden.
Und die können natürlich, die müssen natürlich irgendwo im Speicher liegen.
Und ja, die Datenstruktur, die Informatik dazu erfunden hat,
ist der sogenannte Stack oder Stapel. Das hier ist eine
Veranschaulichung eines Stapels. Auch sehr schön in dem Sinne,
dass man vermutlich die Äpfel hier nicht seitlich rausziehen wird,
weil das würde ihnen ziemlich wehtun,
sondern man nimmt nicht immer den obersten Apfel von diesem,
von diesem Stapel runter
Und wenn ich einen neuen Stapel, einen neuen Apfel drauflege,
kommt der oben drauf und der kommt dann,
also was zuletzt draufkam, kommt als erstes wieder runter.
Und das ist so ähnlich wie mit diesen Aktivierungen von den Methoden,
die zuletzt aufgerufene Methode wird zu Ende ausgeführt.
Dann kommt man zurück zu der nächsten Methode. Und so
könnt ihr euch auch tatsächlich diese Aktivierung von Methoden aufrufen vorstellen.
In höheren Programmiersprochen wie Java ist diese Struktur nicht zugänglich.
Aber es ist trotzdem nützlich, ein mentales Modell davon zu haben,
dass man sich das ein bisschen vorstellen kann.
Ich habe das auch nochmal ein bisschen anders veranschaulicht.
Also hier haben wir so eine Struktur, die wir uns jetzt so vorstellen,
wie so, das sind immer solche Stackframes.
Und jetzt können wir uns das mal hier angucken,
für jeden rekursiven Aufruf, ein neues Deckframe angelegt.
Und wir haben jetzt ein Beispiel aus, also in irgendeiner Methode,
deren Stackframe jetzt gerade auf den Aufrufstack liegt,
da steht irgendwie 23 plus Fakultät von 4. Ziemlich albernes, konzentriertes Beispiel.
Das heißt, irgendwo wird hier als lokale Variable die 23
irgendwie abgelegt sein oder muss ja keine Variable sein,
was auch immer.
Und jetzt wird hier die Fakultät aufgerufen. Und dann ist es tatsächlich so,
es wird ein Stackframe für Fakultät angelegt auf diesem Aufrufstapel
mit diesem Parameter
Dann haben wir das gesehen, wie das
sozusagen zu einer weiteren Aktivierung führt, zu einem Reco-7-Aufruf.
Die werden da sozusagen draufgelegt. Und am Ende hat man dann die Situation,
jetzt wurde die Null übergeben und entsprechend wird als Ergebnis hier eins produziert.
Das bleibt dann auf dem Stack liegen,
wird einberechnet in die Berechnung von der Methode vorher.
Also kommt hier die Eins quasi da rein, einmal eins ist eins.
Das bleibt als Ergebnis zurück, kommen wir zurückgeliefert.
Als Ergebnis wird das hier eingetragen, dann hat man zweimal zwei.
Nee, dann hat man doch zweimal zwei.
Dann hat man hier die zwei, einmal 200 Titty 2.
Und das wird hier eingetragen und dann ist das letztendlich
die Sechs
Und dann hat man hier letztendlich tatsächlich bei der 24,
der Pakulierte von 4, die 24. Das heißt,
so könnt ihr euch das ungefähr vorstellen,
dass tatsächlich der Stack zur Laufzeit aussieht.
Und für jeden Prozess, den ihr in Java ausführt,
ist so ein Stapel da. So ein Aufrufstapel, wo
alle Methodenaufrufe nach und nach draufgelegt werden. Nicht nur rekursive,
sondern alle Methodenaufrufe, die da so nacheinander erfolgen,
aus Methoden aufrufen heraus, sind da so abgelegt.
Gut. Und dann hat man am Ende hier die 24,
die dann irgendwie in diese Berechnung mit reingeht.
Ich habe beim letzten Mal was erzählt, über Sichtbarkeit und Lebensdauer.
Jetzt geht es hier vor allen Dingen auch um Lebensdauer.
Das heißt, wir haben gerade gesehen,
in diesen Stackframes werden die lokalen Variablen verwaltet.
Ich habe letzte Woche erzählt, was Lebensdauer ist,
was wir darunter verstehen. Das ist einfach die Zeit,
die einer Variablen oder auch einem Objekt im Speicher Platz eingeräumt wird.
Während der Laufzeit. Also mehr oder weniger ist, dass die Zeit,
für die eine Variable oder ein Objekt existiert im Speicherplatz.
Und das können wir uns auch nochmal jetzt an diesem Beispiel hier veranschaulichen.
Ich habe hier gesagt, Hier geht es N gleich 3 als aktuellen Parameter.
Und hier gibt es eine lokale Variable Result.
Und wir haben ja aber hier vier Aktivierungen. Das heißt,
es gibt eigentlich vier lokale Variablen im Speicher zu einem bestimmten Zeitpunkt.
Nämlich Vin Dillons gerade in dieser Ausführung befinden.
Und die Lebenszeit der Variablen ist natürlich, abhängig davon,
wie lange es braucht, die geschachtelten Methoden aufzurufen.
Es ist auf jeden Fall so, dass diese lokale Variable,
die lebt ja von hier Kontrollfluss und dann mindestens so lange
wie diese drei Aktivierungen laufen, solange diese Methoden ausgeführt werden,
ist auch die Lebenszeit von dieser Variante Result gegeben.
Und erst wenn alles wieder abgeräumt ist,
dann wird hier die Methode zu Ende ausgeführt,
dann wird hier das Ergebnis zurückgegeben,
dann ist auch die Lebenszeit dieser lokalen Variablen beendet.
Während diese hier in dem Fall nur eine Ausführung lang lebt.
Also diese lokale Variable Result, die lebt halt tatsächlich nur
diesen einen Aufruf lang, weil es sehr schnell geht.
Das ist, finde ich, nochmal ganz,
ganz anschaulich, um sich klarzumachen,
dass tatsächlich Variablen
unterschiedlich lange leben
Okay, Rekursion, schön, schön.
Ich kann ja nochmal vielleicht hier das an diesem Beispiel zeigen.
Ich kann ja hier nochmal einen Haltepunkt setzen.
Und dann kann ich das ja nochmal ausführen.
Das ist ja immer noch mal ganz schön zu sehen.
Meine Maus, die ist immer so ein bisschen komisch hier.
Ich mache das mal mit der drei,
wie wir das eben in dem Beispiel hatten.
Jetzt kann ich tatsächlich hier sehen, hier ist Rekursion Fakultät
einmal aufgerufen. Also wir sind jetzt genau einmal,
haben wir Fakultät aktiviert und Anis gleich drei
Das mache ich hier einen Schritt über. Und wir sehen,
er führt tatsächlich diese Zeile als nächstes aus,
diese Zeile, die gelb hinterlegt ist.
Also hier Resultat N-Ma-Fakultät plus Ende. Die hat er
noch nicht ausgeführt, wenn er das jetzt macht,
dann ruft er ja die Fakultät rekursiv auf.
Also wenn ich jetzt einen Schritt übermache, einen Schritt nicht über,
sondern hinein, dann springt er in diese Ausführung hinein.
Also bekomme ich jetzt hier. Jetzt bin ich hier,
habe ich eine weitere Aktivierung. Man kann also diese Aktivierungen
auch im Bluedray, in dem Debugger, sehr schön sehen.
Man sieht tatsächlich hier, ist jetzt die Fakultät zum
zweiten Mal aktiviert. Und hier sieht man auch Annes
gleich zwei
Für diese Aktivierung ist N3. Und da stehen wir gerade an dieser gelben markierten Stelle.
Da sind wir noch nicht fertig. Hier stehen wir jetzt da,
ich mache wieder einen Schritt über,
einen weiteren Schritt hinein und schon haben wir eine weitere Aktivierung.
Wir sind jetzt wieder an den Anfang von der Ausführung gesprungen,
aber von der dritten Aktivierung. Also mache ich das nochmal,
Schritt hinein, das haben wir die vierte Aktivierung.
Also wir haben jetzt schon vier Aktivierungen von dieser Methode Fakultät.
Und jetzt sehen wir Endes gleich null. Und bei den
anderen Revierungen enden gleich 1, end gleich 2,
end gleich 3, die stehen alle da und warten darauf,
dass dieser Ausdruck zu Ende ausgewertet wird
So, und hier wissen wir jetzt aber,
Schritt übermacht Resultat gleich eins. Also können wir das zurückliefern
. Wenn wir jetzt hier weitergehen, springen wir zurück.
Das werden auch langsam diese Stapel wieder abgebaut.
Das ist also nach und nach werden die hier wieder runtergenommen.
Das heißt, jetzt haben wir hier N gleich eins.
Auch da können wir das Ergebnis jetzt berechnen. Das heißt also,
hier wird jetzt einfach N mal das, was da zurückkam, gerechnet.
Also können wir das zurückliefern. Der Stackframe baut sich ab und so weiter,
bis wir dann am Ende unser Resultat haben, sechs.
Also auch das ist nochmal ganz schön zu sehen,
dass der Debugger von Blue Jay dieses Deckframes tatsächlich visualisiert und
man sich tatsächlich angucken kann für jede Aktivierung,
wie denn die lokalen Variablen gerade belegt sind.
Sehr nützlich. Okay. Ich werde gleich ein bisschen mehr
dazu sagen, wann man Rekursion verwenden sollte und wann nicht.
Was wir gesehen haben, ist für so einen Rekursin-Aufruf,
muss ja relativ viel Aufwand getrieben werden.
Also ich muss zum Stackframe anlegen und diese Datenstruktur muss natürlich
verwaltet werden und so weiter.
Es lässt sich natürlich das Gleiche, was wir gerade gemacht haben,
die Fakultät,
auch iterativ berechnen
Und das ist so ein Folklore-Resultat der Informatik.
Das ist eigentlich alles, was man iterativ machen kann,
also mit Schleifen, auch rekursiv machen kann und umgekehrt.
Die sind eigentlich äquivalent. Also von ihrer Berechnungsmächtigkeit. Kein Wunder,
weil letztendlich wissen wir ja, wird ja alles,
was da so programmiert wird, wieder ausgeführt auf so einen von Neumann-Rechner,
der ja ziemlich sequenziell funktioniert.
Gut, also hier haben wir jetzt diese alternative Implementation,
der Fakultät, hier eine lokale Variable, die erstmal annimmt,
die fakuliert ist, eins.
Und dann wird hier irgendwie eine Schleife, eine Vorschleife geführt.
I wird auf eins initialisiert, solange I kleiner gleich N
ist,
wird I erhöht
Mal multipliziert. So, hätte man vielleicht auch vielleicht
bei zwei schon anfangen können, wie auch immer.
Aber ihr seht, das ist tatsächlich dann nicht irgendwie rekursiv,
so ruft sich selber auf und ist so geschachtelt irgendwie,
sondern tatsächlich einfach straightforward werden da diese Dinger innerhalb einer Schleife durchgeführt.
Das heißt, wir brauchen nur einen Methodenaufruf und brauchen dann
eben halt so ein bisschen Aufwand, um diese lokalen Variablen hier zu verwalten.
Mathematiker lieben eher das Rekursive,
weil es ihre eigenen Definitionen so nahe ist,
in der Programmierung gibt es aber dann oft Überlegungen,
wann ist eine Iteration besser, wann ist eine Rekursion besser.
Das ist etwas, was wir hier auch nicht abschließend beantworten können,
aber wir können Hinweise geben,
weil ihr ja erstmal jetzt hier
Rekursion kennenlernen sollt
Okay, versuche ich jetzt mal so ein bisschen zu verallgemeinern,
was ich gerade erzählt habe. Rekursion tritt auf,
wenn eine Methode M sich während der Ausführung ihres Rumpfes,
falls während der Ausführung uns rum ist, M erneut aufgerufen,
ausgeführt wird. Und damit dieser Prozess nicht endlos läuft,
also damit er irgendwie terminiert, ist eine Abbruchbedingung notwendig.
Man braucht immer eine Abbruchbedingung. Wir hatten eben hier die Abbruchbedingungen,
wenn N gleich Null ist, dann liefern wir eins zurück.
Dabei können wir unterscheiden, was wir gerade gesehen haben,
eine direkte Rekursion, also in der Methode M wird M direkt aufrufen,
dann kann ich das sehen, dass es eine Rekursion ist.
Es kann aber auch sein, dass sich aus M1 heraus
M2 aufrufe und dann M2 irgendwann wieder M1 aufruft.
Das ist dann vielleicht nicht mehr ganz so durchsichtig,
kommt aber durchaus auch häufig vor
Und ganz wichtig für diese Rekursionsidee ist,
dass der Grundgedanke ist, dass eine Methode einen ersten Teil
eines Problems selbst löst und den Rest in kleinere Probleme zerlegt.
Und sich selbst dann mit diesen kleineren Problemen aufruft.
Das ist eine Grundidee von Algorithmik in der Informatik,
dass man Probleme versucht zu zerlegen,
um sie besser in den Griff zu bekommen.
Die White and Conquer wird das auch gerne genannt,
also Teile und Herrsche. Hier ist nochmal so diese Grundstruktur,
wie ich sie jetzt am Exemplar an einem Beispiel gezeigt habe.
Irgendwie gibt es einen Ergebnis-Typ,
dann heißt diese Methode Löseproblem, ganz generisch,
bekommt irgendwelche formalen Parameter
So, jetzt wird anhand der formalen Parameter irgendwie geprüft,
ist das Problem einfach lösbar.
Haben wir gerade gesehen. Das Problem ist einfach lösbar,
wenn es die Fakultät von null sein soll.
Dann kann ich auch direkt das einfache Ergebnis liefern.
Das ist eine Abbruchbedingung, die sollte eine Rekursion,
eine rekursive Methode immer haben. Dass es irgendwie ein Ende
hat. Und dann im Elzzweig stellen wir fest,
na gut, das Problem ist noch zu groß,
wir zerlegen das Problem möglicherweise.
Haben wir jetzt hier so ein bisschen gemacht.
Wir haben das in eine Multiplikation und eine rekursive Berechnung aufgespalten
Und hier ist es dann so angedeutet,
man kann also durchaus auch mehrere löse Problemaufrufe haben,
also mehrere rekursive Aufrufe. Und was die dann zurückliefern,
wird irgendwie zusammengematscht und dann hat man ein Gesamtergebnis.
Und das ist erstmal nur technisch die Grundstruktur.
Das ist einfach hier Mechanik, die wir euch beibringen.
Wie ihr Rekursion wann einsetzt,
werden wir auch in SR1 noch an diversen Stellen uns ansehen und
ihr werdet das durch euer ganzes Informatikstudium letztendlich immer wieder brauchen,
dieses Konzept von Rekursion.
Es gibt auch ein
sehr schönes Gegenbeispiel
Mit denen man sehr schön zeigen kann,
dass Rekursion nicht für alles das Allheilmittel ist.
Und nehmen wir mal das Beispiel hier der Fibonacci-Zahlen.
Hier so ein bisschen angedeutet, es gibt ja so ein Buch,
wo dann die Fibinatci-Zahlen auch eine Rolle spielen.
Die 500 Schiedsalen sind wieder mal mathematisch definiert, ganz einfach.
Die erste Fibinatci-Zahl ist null. Die zweite Fibinatci-Zahl ist eins.
Und die Ente Fibinatci-Zahl ergibt sich aus der Summe der N-minus
ersten und der M-minus zweiten Fibonacci-Zahl.
Also den beiden vorherigen. Also es ist die dritte Fibinatci-Zahl,
die nach eins, die vierte zwei und so weiter.
Das ist ja auch hier unten angedeutet. Hier sieht man so
diese Zahlenfolge. So, wenn man diese Definition hat,
wunderbar, dann kann man sie direkt hernehmen und wir haben ja gesehen,
das lässt sich direkt abbilden in eine rekrusive Methode.
Also haben wir hier unser Fibonachi und übergeben einen N,
sollen für N berechnen, wie die Fibonachi-Zahl ist.
Und machen das hier mit einem Switch. Und ihr seht,
in diesem Switch wird hier,
Case 1 ist 0 Case 2 1 und default,
also wenn es keiner von den beiden Fällen ist,
dann sagen wir, na gut,
das ist Fibonachi N minus 1 plus 400 Show N minus 2. Das heißt,
wir haben hier im Rumpf dieser Methode 2
rekursive Aufrufe
Nebenbei bemerkt, hier fehlt kein Break, sondern das,
was hier steht, ist ja ein Return und ein Return
ist so gut wie ein Break.
Ein Break verlässt nur das Switch in dieser sehr kleinen Methode,
kann man auch gleich dann mit Return den Ausgang wählen.
Gut, das heißt, hier kommen also tatsächlich zwei Fibonacci-Aufrufe in diesem Rumpf vor.
Und die werden natürlich dann auch nacheinander ausgeführt. So,
wo ist das Problem jetzt? Das sieht doch eigentlich
ganz prima aus. Da können wir doch sofort sehen,
dass das richtig definiert ist. Mathematisch korrekt. Das Problem ist,
Das, wenn wir das jetzt mal uns veranschaulichen,
mit einem aktuellen Wert von 4, also hier Fibunacci von 4,
dann haben wir hier quasi dann einen Switch mit der 4.
Das heißt, dann wird rekursiv aufgerufen, Fibunacci von 3,
Fibunacci von 3, würde erst mal wieder genauso funktionieren,
dass dann ein Switch gemacht wird.
Dann wird festgestellt, es ist weder 0 noch eins,
also wird dann 490 von 2 aufgerufen und dann Fibunacci von
1 und dann würde da direkt die 1 geliefert werden.
Und Fibunacci von 0 würde auch noch aufgerufen werden. Okay,
und das liefert auch Das liefert dann null. So,
und dann ist das hier nochmal das Gleiche mit Fumunity von
3. Das heißt also, hier entsteht so ein Aufrufbaum.
Und der wird tatsächlich in dieser Reihenfolge durchlaufen.
Also wie ich das gerade angedeutet habe. Dann geht das
hier weiter. Wenn also dieser linke Fibonacci-Aufruf rekursiv fertig ist,
geht es hier wieder von vorne los.
402, naja, hm, können wir nicht direkt beantworten.
Müssen wir erst 404 von 1 aufrufen. Und 4090 von 0. So,
und was da jetzt passiert ist, hier ist schon so angedeutet,
wenn man jetzt das mit 5 machen würde,
würde dieser ganze Baum irgendwie links stehen und rechts oder etwas kleinerer Baum stehen.
Das heißt also, dieser Aufruf, Baum würde sehr groß werden für größere Zahlen. Das heißt,
wir haben bei 400 Chief von 2,
haben wir zwei rekursive Aufrufe bei 3, 4,
bei 4, 8 und bei 5 sind es dann nicht 16,
wie ich letztes Jahr erzählt habe, sondern 13. Aber größenordnungsmäßig ist das hier exponentiell.
Es ist nicht ein Quadrat, 2 hoch N,
sondern es ist etwas eine kleinere Zahl, hoch N.
Aber es ist tatsächlich hier exponentiell. Das heißt,
es verdoppelt sich mit jeder größeren Zahl. Das heißt,
wenn ich jetzt die Fibunacci-Zahl von 40 berechnen will in Java,
dann dauert das auf diese Art und Weise
schon relativ lange
Und das ist natürlich klar, die 400 Schiza ist iterativ,
total einfach zu berechnen. Gar kein Problem. Und
das dauert richtig lange. Also könnt ihr euch vorstellen,
größenordnungsmäßig habe ich jetzt gesagt, zwei hoch N. Das heißt,
wenn es, wenn ich die zehnte Firma Nachi-Zahl berechne,
wir sind das ungefähr 1000 Aufrufe.
Wenn ich die zwanzigste Firbonati-Zahl berechnen will, dann sind das schon
eine Millionenaufrufe, die da so stattfinden, um größenordnungsmäßig.
Wenn ich 30, die 30. 400 hier haben will,
das ist eine Milliarde.
Also eine Milliarde, solche rekursiven Aufrufe. Das ist natürlich
Blödsinn. Das ist noch viel blödsinniger, weil ja eigentlich
diese Teilbäume immer wieder
das Gleiche berechnen
Deswegen gibt es Programmiersprachen, die versuchen, solche Rekursionen irgendwie zu optimieren.
Aber das ist dann sozusagen der Versuch der Technik, da irgendwo das,
was eigentlich an Konzeptionen nicht in Ordnung ist, wieder aufzufangen.
Das ist einfach nicht schlau, das so rekursiv zu machen,
weil tatsächlich hier aus jedem Aufruf wird sozusagen verdoppelt.
Es sind zwei rekursive Aufrufe. Das also bitte eher weglassen,
werdet ihr aber in den Übungen euch dann auch nochmal ansehen.
Glaube ich. Okay, das heißt,
man muss aufpassen, Rekursion muss man sehr vorsichtig verwenden.
Also insbesondere, wenn man so mehrfach rekursive Aufrufe hat,
ist es schwierig
Es gibt aber ganz viele rekursive, sehr viele Fälle,
in denen man Rekursionen sehr gut anwenden kann, wo es einfach elegant ist.
Also alle Baumstrukturen in der Informatik und die wählt ihr
zuhauf kennenlernen, wenn ihr euch mit Informatik beschäftigt.
Eine Ordnerstruktur im Windows Explorer oder in jedem Betriebssystem
ist auch nichts weiter als eine Baumstruktur.
Das heißt also, wenn ich irgendwie was suchen will,
dann muss ich das Wurzelverzeichnis angeben und dann wird tatsächlich rekursiv,
werden alle unter Verzeichnisse durchsucht.
Es ist die Datei, die ich suche,
in dem Verzeihnis. Nein, gut,
dann gucke ich jetzt in das erste Verzeichnis rein und dann
wieder rekursiv weiter
Viele sehr gute Satirverfahren sind rekrusiv definiert. Quick Sword,
Merch Sword, Quick Sword werden wir auch noch kennenlernen hier in SE1.
Wenn wir uns mal ein bisschen genauer angucken. Und viele
Probleme auf Grafen auch. Das werden wir uns im allerletzten Aufgabenblatt
nochmal ansehen. Lassen sich einfach sehr elegant mit Rekursionen lösen.
Das heißt also, ihr werdet noch in SE1,
sondern auch in anderen Veranstaltungen, noch einige gute Beispiele sehen.
Es gibt auch von einem Menschen Steve McConnell,
der ein sehr gutes Buch geschrieben hat, über Softwareentwicklung.
Code Complete heißt das. Der hat das auch nochmal ein bisschen
auf den Punkt gebracht. Es gibt eine relativ kleine Menge von Problemen,
da ist Rekursion sehr gut um Einfache und elegante Lösungen zu produzieren.
Dann gibt es eine etwas größere Menge von Problemen,
da sind die Lösungen zwar einfach und elegant, aber schwer zu verstehen.
Das ist dann so eine Sache. Will man Software schreiben,
die schwer zu verstehen ist? Manchmal ist das ein Vorteil,
während man sie warten kann. Und für die meisten Probleme
führt die Benutzung von Rekursion zu sehr komplizierten Lösungen. Das heißt,
wenn ihr Rekursion verstanden habt und das irgendwie toll findet und
euch irgendwie von dieser Schönheit berauscht, dann lasst euch nicht wegreißen,
dass ihr nur noch alles mit Rekursion machen wollt,
sondern überlegt euch immer genau, wann ist das angemessen?
Also sollte sehr selektiv eingesetzt werden.
Vielleicht noch mal so ein bisschen zur Erläuterung jetzt, was ich gerade erzählt habe.
Ich habe ja diesen Aufruf-Stack schon dargestellt. Das ist also
irgendwie diese Datenstruktur, auf der dieses Deck-Frames abgelegt werden.
Man kann sich tatsächlich das Speichermodell,
also wie sozusagen der Hauptspeicher des Rechners benutzt wird,
für eine Programmiersprachenausführung wie Java, kann man sich ungefähr so vorstellen,
es gibt diesen Bereich für den Aufruf-Stack.
Dann gibt es den sogenannten Hieb. Das ist der Hieb,
der Haufen, die Halde, wie auch immer.
Das ist ein bestimmter Speicherplatz, in dem werden dynamisch erzeugte Objekte angelegt.
Das heißt also immer, wenn ihr mit New ein Exemplar einer Klasse erzeugt,
dann wird der Speicherplatz für dieses Objekt genau auf diesem Hieb abgelegt.
Und das wird durch einen Garbage-Collector-Verwalt, das werden wir uns dann
auch mal ein bisschen genauer angucken, wie das funktioniert.
Und dann ist es auch üblicherweise so,
dass natürlich das Programm selber, also der Programmtext im Java,
der Bitecode, der muss ja auch irgendwo im Speicher liegen,
damit er von da aus dann abgerufen werden kann.
Das ist so hier angedeutet,
das haben wir auch so ein bisschen durchgängig gemacht,
also der Aufrufsdeck eher so blau gefärbt,
der hieb eher gelb und der Programmcode dann so ein bisschen grau.
Nochmal der Hieb, den Stapel haben wir eben schon gesehen,
der Hieb ist dieser Bereich, in dem Objekte der Speicherplatz für Objekte angelegt wird.
Das Besondere am Hieb ist, dass diese Speicheranforderungen ja beliebig kommen können.
Und das heißt also, man weiß auch nicht,
wie groß die Objekte sind, die da angefordert werden.
Das heißt, bei so einem Stackframe, bei so einem Aufrufstapel,
da kann man sozusagen immer davon ausgehen, dass es relativ überschaubar ist.
Aber so ein Objekt kann ja auch sehr groß werden,
wenn sehr viele Exemplarvariablen hat,
dann braucht man sehr viel Platz für so ein Objekt.
Und es ist eben halt nicht so wie bei dem Stapel,
dass man so diese gestrickte Reihenfolge hat,
zuletzt gerufene Methode wird auch als erste beendet,
sondern die Freigabe dieses Speicherplatzes kann in beliebiger Reihenfolge erfolgen
Okay, und deswegen wird das auch eine dynamische Speicheranforderung genannt. Wenn man tatsächlich es schafft,
so viele Objekte zu erzeugen und den Speicherplatz nicht wieder freizugeben für diese Objekte,
dann kann es zu einer Out of Memory Acception kommen.
Das, wenn ihr das mal versucht,
von Hand zu erzeugen mit den Mitteln von SE1,
Level 1 und 2, ist es noch nicht möglich,
dann auf Level 3 und 4 werden wir das dann auch nochmal bauen können.
Hier habt ihr einfach das Problem,
dass ihr zwar viele Objekte erzeugen könnt,
aber wenn der Garbage Clactor gut ist,
dann gibt er das auch wieder frei,
was ihr vor zwei Sekunden erzeugt habt,
sodass also immer wieder Platz frei wird.
Also es ist relativ schwierig, out of memory error zu erzeugen.
Also wenn einfach dieser Platz ausläuft. Und das ist tatsächlich
so für eine Programmausführung zum Beispiel von Java,
wird statisch erstmal ein bestimmter Hieb, eine bestimmte Hiebgröße reserviert.
Und die wird dann tatsächlich zur Verfügung gestellt. Also,
Hieb und Stack, ganz wichtige Speicherstrukturen, auch dem Aufrufsdeck,
dieses Deckframes, strickt umgekehrte Reihenfolge immer.
Und auf dem Hieb dann so unterschiedlich große Stücke,
die da so rausgebissen werden für so dynamische Objektspeicheranforderungen.
Was man aber sehr gut erzeugen kann,
ist ein Stapelüberlauf, also ein Stackoverflow,
also auch bei einem Aufrufs-Stack,
der Speicherplatz dafür kann irgendwann
voll sein
Das passiert insbesondere dann, wenn ihr eine sehr tiefe Rekursion habt.
Und ich kann euch mal eine sehr, sehr leicht,
eine sehr tiefe Rekursion zeigen, nämlich hier,
indem ich bei diesem Beispiel mit der Rekursion,
bei der Fakultät jetzt einfach minus eins angebe.
Ihr erinnert euch, der sagt jetzt einfach,
ist es null, nein, es ist nicht,
dann mache ich jetzt mit N minus eins den nächsten Aufruf.
Das heißt, wenn ich jetzt minus eins als Parameter übergebe,
was er eigentlich nicht korrekt ist,
die Spezifikation funktioniert ja mit natürlichen Zahlen,
hier steht extra Param N eine natürliche Zahl.
Ich mache jetzt hier immer etwas mit einer ganzen Zahl
Einfach nur, um zu zeigen, was dann passiert.
Also bitte mit minus zwei, minus vier, minus vier und
so weiter und läuft erstmal schön so bis theoretisch bis minus zwei Milliarden.
Und das ruft sich jedes Mal rekursiv auf. Wir haben
ja gesehen, er macht ja jedes Mal einen rekursiven Aufwand.
Das heißt, auf diesen Aufrufsdeck werden jetzt ganz viele Stackframes draufgepackt.
Und das führt dazu, dass dann hier, ach so,
jetzt habe ich hier natürlich diesen blöden Haltepunkt,
da muss ich die mal eben wegnehmen.
So, und mal fortsetzen und dann rumpelt das hier
richtig schön vor sich hin.
Java Lang Stack overflow error null. Also dann sieht man
hier auch in der Konsole
relativ viele Meldungen
Dass das irgendwie, ja, ganz schön weit gelaufen ist.
Gut, wie weit das geht, ist ganz lustig,
können wir uns mal angucken. Wir machen mal einen ganz einfachen Test.
Ich führe mal eine private In-Zähler ein. So,
ja, und jetzt mache ich hier vor dem rekursiven Aufruf,
erhöhe ich diesen Zähler. Also sage ich plus plus Zähler
. Ja, ihr seht, was ich hier mache.
Ich versuche jetzt sozusagen jeden rekursiven Aufruf mitzuzählen. Das heißt,
diese Klasse hier, dieses Objekt,
was ich jetzt hier erzeuge, Rekursion erzeugen,
mich da jetzt diese Rekursion nochmal mit der Minus 1 aufrufe,
dann kommt es irgendwann zu dem Stack-Overflow und dann sollte das Exemplar ja irgendwie diesen Zähler hochgezählt haben.
Flup, zack, da ist es.
Hier ist das Objekt und wir sehen 8.942 rekursive Aufrufe.
Das ist gar nicht so viel. Also schon bei minus 8.942 ungefähr,
sagt er so, jetzt ist mir hier,
das ist mir zu doof, mein Stack ist voll.
Das Stack ist üblicherweise relativ klein, weil man selten so
tiefe Rekursionen braucht. Also selbst wenn ihr sehr große,
rekursive Strukturen tranversiert,
ist die Aufruftiefe
Also wie viele Stackframes sind aktuell gerade gleichzeitig auf dem Stapel?
Also aus einer Methode, dann wieder eine weitere,
dann wieder eine weitere und so weiter. Das ist meist
nicht sehr tief. Also 8.900 ist schon ziemlich viel.
Das braucht man eigentlich sehr selten. Sehr, sehr selten
. Okay, sehr schön. Hier ist es mal für
ein Unix-System. Ihr kennt möglicherweise Unix-Systeme, ja,
also Apple, iOS, nicht iOS, macOS.
Ten ist letztendlich basiert auf einem Unix-System,
ist ein sehr weit verbreitetes System. Ein schönes Betriebssystem,
was die Informatiker sehr ausführlich analysiert haben,
was es auch schon ziemlich lange gibt
Und da haben die es tatsächlich so gemacht, naja,
wenn der Stack und der hieb, die können ja beide groß werden,
je nachdem, wie das Programm so sein Verhalten hat.
Und die machen das einfach so, die sagen, ja,
das Deck wächst halt in die Richtung und der Hieb wächst in die Richtung.
Und dann ist es so, wenn dann mal ausnahmsweise der
Stack sehr viel Platz braucht, ist das kein Problem.
Und wenn der Hieb mal viel Platz braucht, ist das auch kein Problem,
weil die sozusagen in der Summe sollte es dann irgendwie gehen.
Das heißt, hier ist sozusagen dieser Platz etwas dynamischer genutzt dazwischen.
CP ist der Konstantenpool, das Programm,
oder dann gibt es einfach noch den Betriebssystemkern,
das ist so eine Veranschaulichung für das, was in Unix so passiert.
Was ich nochmal zeigen will, ist das hier,
Das kennt ihr schon, dieses Bild, ja,
das ist dieses Objektdiagramm von der Uhrenanzeige.
Also eine Uhrenanzeige enthält zwei Referenzen auf Exemplare der Klasse-Nummernanzeige.
Wunderbar. Das ist jetzt hier mal gelb eingefärbt, um deutlich zu machen.
Ja, das liegt tatsächlich auf dem Hieb.
Diese Speicherbereiche für diese Objekte liegen auf dem Hieb.
Ich habe hier auch nochmal das Projekt dazu.
Das ist die Zeitanzeige hier und man sieht hier jetzt genau
diesen Unterschied nochmal zwischen Das ist ein Schnappschuss vom Hieb,
auf dem Hieb liegen tausende von Objekten, die da gerade reserviert sind.
Aber das ist sozusagen nur die Objekte, die uns gerade interessieren,
in ihrem Zusammenhang. Und das ist der statische Zusammenhang.
Wir haben zwei Klassen, die sich gegenseitig benutzen.
Also die eine benutzt die andere. So,
wenn ich jetzt hier mal was mache,
ich gehe jetzt mal hier in diesen Konstruktor rein und ihr seht hier in der Uhrenanzeige,
wird jetzt einfach eine Nummer, werden zwei Nummernanzeigen erzeugt,
also Nummernanzeige mit der Laufweite 24 und für die Stunden und 60 für die Minuten.
Die beiden werden also hier, erzeugt mir nicht jetzt hier
das ausführen würde, würde ich genau sehen,
aber wir können es ja vielleicht mal eben machen.
Ich setze hier mal in so einen Haltepunkt hin.
Wir brauchen nicht gerade zwei. Wenn ich jetzt hier mal so ein Exemplar erzeuge,
dann kann man tatsächlich sehen, wie so ein Konstruktoraufruf auch tatsächlich,
nicht 23 Uhr, 58 Uhr, sagen wir mal.
So, dann kann ich jetzt hier sehen,
ich bin jetzt in dem Konstruktor von Uhrenanzeige und man sieht hier die beiden Exemplarvariablen,
Nummernanzeige, Stunden und Nummernanzeige unterstrich Minuten, die sind noch null.
Ich habe die ja noch nicht angelegt. Wenn ich jetzt
einmal einen Schritt übermache, wird das hier ausgeführt,
jetzt ist hier tatsächlich zu sehen, Objektreferenz abgelegt.
Also in den Exemplarvariablen von diesem neu erzeugten Objekt,
dass ja dieser Konstrukt ja jetzt gerade initialisiert,
ist jetzt tatsächlich eine Nummernanzeige abgelegt.
Das heißt, er kann ja auch draufklicken,
an dem Debugger kann ich mir tatsächlich hier die Referenz dann den Objektinspektor zeigen lassen.
Also das ist schon mal ganz hübsch. So,
und dann kann ich hier einen weiteren Schritt übermachen und man
sieht dann auch, wie die zweite Exemplarvariable hier initialisiert wird.
Und dann hat man hier eine weitere Objektreferenz, die das hier macht.
Und dann stehen wir an einer bestimmten Stelle jetzt hier vor diesem Sätze Uhrzeit,
wo jetzt hier noch hier noch die 23,58 gesetzt wird.
Wenn ich da jetzt noch den Schritt drüber mache,
wir dürfen jetzt in diesen beiden Exemplaren da oben 23,58 gesetzt werden.
Und dann ist dieser Konstrukt da auch zu Ende
Und dann kann ich tatsächlich hier mir das Exemplar anzeigen lassen, ja, Uhrenanzeige.
Das ist das Exemplar. Hier sind die beiden Referenzen.
Wenn ich hier draufklicke, wird das nach vorne geholt,
wenn ich hier draufklicke, wird das nach vorne geholt.
Also das ist tatsächlich hier quasi in BlueJ das Objektdiagramm für die Uhrenanzeige konstruiert.
Und diese Situation habe ich jetzt gerade hier auch nochmal eben
zu sehen gehabt. Was ich jetzt nochmal diskutieren will,
ist nämlich auch nochmal, was passiert eigentlich zur Laufzeit?
Also dieser Zusammenhang zwischen statischen und dynamischen Eigenschaften.
Zur Übersetzungszeit gibt es jede Methode nur einmal.
Ihr seht das ja im Quelltext vor euch.
Ebenso wie die Exemplarvariablen, die da einfach im Quelltext stehen
Die sind also statisch in der Klassendefinition beschrieben. Zur Laufzeit
gibt es für jedes Exemplar einer Klasse einen eigenen Satz von Zustandsfeldern.
Das ist klar. Jedes Objekt hätte seinen eigenen Zustand.
Und logisch kann man sich auch vorstellen, es gibt einen Satz von Methoden.
Also, das kann man sich so vorstellen,
wenn ich drei Exemplare zum Beispiel einer Klasse habe,
dann sind das hier sozusagen die Zustandsfelder und gekapselt von den Methoden.
Man kann sich also vorstellen, jedes Objekt hat seine eigenen Methoden.
Aber das ist nur die logische Sicht. Eigentlich ist es besser,
sich das, wenn man sich die Speichersicht klarmachen will,
das so vorzustellen, dass auf dem Hieb nur die Exemplarvariablen eines Objektes sind,
und zwar zusammenhängend, Also hier ist es dann tatsächlich so,
da sind diese eckigen Speicherbereiche für die Felder.
Also die Felder eines Objektes. Und die haben dann eine
Referenz auf ihren Programmcode und da könnt ihr euch das so vorstellen,
dass sozusagen die Methoden nur einmal da sind.
Also logisch, jedes Objekt hat seine eigenen Methoden.
Im Speicher sieht es anders aus. Das wäre ja Quatsch,
wenn jedes Objekt eine Kopie der Methoden hätte.
Weil dieses, das braucht man ja nur einmal.
Also wird das hier so dargestellt. Und das ist hier jetzt nochmal,
was ich gerade gezeigt habe, dieses Live-Beispiel,
dass ich im Konstruktor von der Uhrenanzeige war.
Da habe ich also hier irgendwie das aufgerufen mit 23.15
Uhr und
Und dann war es so,
da gab es irgendwie eine Referenz auf das Exemplar der Uhrenanzeige.
Das wiederum verweist auf ihren Bitecode der Uhrenanzeige.
Und dann haben wir gesehen, wie sozusagen nach und nach hier
diese beiden Referenzen gesetzt wurden auf Exemplare der normalen Zeit.
Man sieht einfach, auf dem Hieb liegen die Objekte,
also die Felder eigentlich der Objekte, mit so ein paar Zusatzinformationen,
unter anderem zu welcher Klasse das gehört.
Und auf dem Stackframe werden dann hier so immer diese Aufrufframes verwaltet. Okay,
das, und jetzt kann man sich nämlich nochmal eine Sache zum Schluss klar machen,
Ihr habt sicherlich schon gehört, Java hat einen Garbage-Collector.
Das heißt, ihr braucht euch nicht darum kümmern,
was mit den Objekten passiert, die ihr nicht mehr benutzt.
In anderen Programmiersprachen ist das anders. NCPlus,
wenn ihr dynamische Objekte erzeugt, also Speicherplatz anfordert,
müsst ihr den auch wieder freigeben.
Das kann ein ziemlicher Schmerz sein, das zu verwalten.
Java müsst ihr das nicht. Wie funktioniert das in Java
? Ganz einfach. Wir haben jetzt gesehen,
es gibt diesen Stack und diesen Hieb.
Alle Objekte eines Java-Programms liegen im Hieb.
Das ist einfach so. Als mentales Modell sehr direkt verständlich.
Auf dem Aufrufsdeck liegen ja immer die lokalen Variablen.
Diese lokalen Varianten können Referenzen sein auf Objekte
Und jetzt ist es ganz einfach. Nur die Objekte,
die von dem aktuellen Aufrufstack aus erreichbar sind, spielen für die Programmausführung eine Rolle.
Also tatsächlich transitiv erreichbar. Ich kann also Referenzen in einer lokalen Methode haben,
die auf Objekte zeigen und die haben wieder vielleicht Referenzen auf weitere Objekte.
Alles das, was erreichbar ist, ist relevant.
Alles andere ist nicht erreichbar und damit, das sind tote Objekte.
Das heißt, der Garbage-Regler macht genau das,
in regelmäßigen Abständen wird vom Hieb aus, vom Stack aus,
werden alle Referenzen verfolgt im Hieb und alle Objekte markiert.
Und alles, was nicht markiert ist, kann dann einfach weggeräumt werden.
Nennt sich Mark and Sweep oder Markin und Abräumen,
also ein sehr Erstmal im Prinzip leicht verständlicher Algorithmus,
aber da gibt es inzwischen sehr viel schlauere Konzepte,
um das effizient zu machen in Java.
Gut, das ist die Zusammenfassung jetzt von dem,
was ich in der ersten Hälfte erzählen wollte.
Also rekursive Methodenaufrufe, alternative Möglichkeit für Wiederholungen.
Jede Wiederholung lässt sich sowohl iterative als auch rekursiv formulieren,
jeweils mit spezifischen Vor- und Nachteilen.
Und softwaretechnisch ist es halt vor allen Dingen wichtig,
sich zu überlegen, was ist denn verständlicher?
Was ist denn klarer? Und hinter den Kulissen moderner Programmiersprochen
hat man eigentlich immer sowas wie einen Aufrufsdack und einen Hieb.
Das sind zentrale Strukturen. Gut, dann habe ich
hier nochmal deine Verständnisfrage.
Wir haben ein bisschen Storesi
Juhu. Ich starte mal die Abstimmung. Und zwar hier laden wir das mal.
Das ist eine schöne Aufgabe für euch jetzt für die Pause.
Könnt ihr euch jetzt Gedanken zu machen. Diese Methode hier
ist eine rekursive Methode. Ich weise darauf hin, sie
ist rekursiv. Was passiert, was wird sie liefern,
wenn ich die mit dem aktuellen Parameter von drei aufrufe.
Also da könnt ihr jetzt ein bisschen euch Gedanken drüber machen.
Ich zeige der Wahl vielleicht nochmal
ein bisschen
Ja, ich zeige die Methode gleich nochmal, Moment,
Moment, ich will nochmal eben ganz kurz hier noch ein bisschen,
der Schnee ist ja nicht mehr,
leistet nicht mehr lange auf sich warten.
So, haben wir auch ein bisschen Schnee. So,
ja, also, hier bitte überlegt euch das,
was liefert diese rekursive Methode, wenn man sie mit dem aktuellen Parameter 3 aufruft.
Das ist auch wieder so eine klassische Aufgabe,
wie wir sie gerne in der Klausur drannehmen.
Übrigens, wir haben inzwischen mal so ein bisschen geguckt,
wie viele von euch schon diesen Selbsttest für Level 1 gemacht haben.
Es sind noch nicht so viele. Es wäre schlau,
wenn ihr das macht. Also wenn ihr wirklich jetzt begleitend
zu den Stufen diese Selbsttests durchführt. Wir haben, glaube ich,
jetzt so 100 Leute, die es mal irgendwie sich angeguckt haben,
aber nur ungefähr 40 haben den Test wirklich durchgezogen.
Wer von euch hat den Test gemacht und hat irgendwie Schwierigkeiten gehabt?
Hat jemand Schwierigkeiten gehabt? Da so ein bisschen, ja
. Okay, das war mir das mit dem Browser.
Ja, das ist ein bisschen das Problem,
was wir so ein Tool einsetzen,
was da vielleicht nicht ganz so gut funktioniert
So, wir haben jetzt schon 25 Stimmen,
das ist ja noch sehr zögerlich, dass ihr euch noch nicht traut.
Also die möglichen Antworten sind hier 12, 15, 18 und 21. Ja.
Okay. Wir machen jetzt fünf Minuten Pause.
Also wir machen das dann nach der Pause weiter.
Ihr habt einfach Zeit jetzt. Fünf Minuten Pause.
So, ich glaube, die fünf Minuten sind rum.
Das heißt, die Kollegen da draußen kann ich nochmal
eben reinholen
So, sehr schön. Ja, 166 Stimmen habe ich aufgesammelt.
Ich Rattenfänger, ich. Ich beende das jetzt mal.
168 Stimmen. Jetzt ist natürlich wieder spannend, was ist
so die Verteilung, die ihr hier so getroffen habt?
Es sind doch ziemlich viele der Meinung,
dass es 18 die richtige Antwort ist
71 Prozent. Das ist ausgesprochen erfreulich, weil das ist
auch die richtige Antwort. Das liegt ganz einfach daran,
dass man sich hier das veranschaulichen kann.
P wird mit drei zuerst aufgeholfen und in diesem,
dieser rekursive Aufruf wird nur stattfinden, wenn P größer Null ist.
Also mit drei, mit zwei, mit eins wird es aufgerufen.
Das heißt, hier wird dreimal drei gerechnet, plus dreimal
zwei, plus dreimal eins. Dreimal drei ist 9,
plus dreimal zwei ist 6 ist 15 plus deiner 1 ist 3, 18.
Das heißt, in diesem Im Fall ist das hier die richtige Antwort.
Es heißt also, ihr habt überwiegend Rekursion richtig verstanden.
Das ist großartig. Dann können wir uns ja jetzt dem nächsten Thema widmen.
Aber bevor ich das mache, möchte ich doch noch mal
ein bisschen hier den letzten Rest von dem Gedicht vorlesen.
Nun muss die Försterin sich eilen, den Gatten sauber zu zerteilen.
Schnell hat sie ihn bis auf die Knochen nach Weitemanns Sitte
aufgebrochen. Voll Sorgfalt legt sie Glied auf Glied,
was der Gemahl bisher vermieht. Behält einen Teil Filet zurück
als festtägliches Bratenstück und packt so dann,
es geht auf vier
die Reste in Geschenkpapier
Von Fernetöns wie Silberschellen im Dorfe hört man Hundebellen. Wer ist es,
der in so tiefer Nacht so spät noch seine Runde macht?
Knecht Rubrecht kommt auf goldenem Schlitten mit einem Hirsch herangeritten.
Sag gute Frau, habt ihr noch Sachen, die armen Menschen Freude machen?
Die sechs Paketke Heiligermann, das ist alles, was ich
geben kann. Knecht Rubrecht macht sich auf die Reise,
die Silberschellen klingen leise, im Forster,
im Försterhaus die Kerze brennt, die Glocke klingt,
es ist Advent.
Nächsten Sonntag, ganz besinnlich, Advent, sehr schön.
Gut, ja,
danke, danke
Das ist ein Text, der steckt hier in diesem Java-Projekt
drin. Jetzt hier geht es nämlich um reguläre Ausdrücke.
Das will ich ganz kurz erläutern, was dieses Projekt macht.
Das ist hier eine klasse Zeilen-Einleser. Die macht nichts weiter
als diesen Text. Das ist also ein dieser Gedichtstext liegt
einfach als Textdatei in diesem Verzeichnis von diesem BlueJA-Projekt.
Ich kann also jetzt diese Zeilen, also einzelne Zeilen dieser Textdatei, als Zeichenketten auffassen.
Zeichenketten habt ihr schon in Übungen ein bisschen kennengelernt.
Wir gucken uns gleich Zeichenketten etwas genauer an. Und dieser Zeilen,
ein Leser macht nichts weiter, dem sage ich irgendwie,
hier ist eine Datei, lies mal bitte mir,
liefern wir mal eine Zeile nach der anderen.
Und dann haben wir hier eine klasse Musterfinder.
Und die gucken wir uns mal ein bisschen genauer an.
Die sagt hier, er zockt sich einen neuen Zeileneinleser und sagt dem,
hier, nimm mal den Adventstext.
Und dann kann er von dem Leser sich die nächste Zeile geben lassen.
Also die erste Zeile, die erste Gedichtszeile ist das in dem Fall.
So, und solange das, was dieser Leser da liefert,
nicht Nall ist, also wirklich noch eine Gedichtszeile da ist,
macht er hier was Eigenartiges.
Er bekommt ja hier ein Muster übergeben als Parameter,
ein String
Und er guckt jetzt, ob dieses Muster auf diese jeweilige Gedichtzeile passt.
Das heißt, er geht alle Gedichtzeilen durch und prüft,
hey, Zeile, passt du auf dieses Muster?
Und was das für ein Muster ist,
das werden wir uns gleich mal angucken.
Aber es werden quasi alle die Zahlen dann mit System Outprenting
einfach ausgegeben, auf die ein bestimmtes Muster zutrifft.
Ja, und dann ist das hier einfach Teil der nächsten Zeile.
Also einfach eine Waldschleife über alle Zeilen, dieses Gedichtes.
Und das werden wir uns gleich genauer ansehen,
was man da mit so ein Hübsches tun kann.
Aber erstmal muss ich ein bisschen was erzählen über Zeichenketten.
Zeichenketten Gibt es eigentlich in jeder vernünftigen Programmiersprache?
Eine Zeichenkette ist eine Folge von einzelnen Zeichen, üblicherweise.
Also wir haben jetzt hier sowas wie vierter Advent-Fragezeichen.
Das ist eine Zeichenkette mit einem Leerzeichen drin.
Und üblicherweise in der Informatik sehr weit verbreitet,
werden diese einzelnen Zeichen einer Zeichenkette beginnend bei null adressiert.
Das heißt also, wenn ich mich auf das erste Zeichen
hier beziehen will, dann muss ich das mit Index null zugreifen.
Also das Zeichen an der Position null ist dann hier die
Vier in der Position 1, einen Punkt und so weiter.
Die Anzahl einer Zeichenkette ist ihre Länge
Den meisten Programmiersprachen, zumindest auch in Java,
ist es so, dass eine Zeichenkette unbegrenzte Länge haben kann.
Klar, der Speicher ist irgendwann voll,
aber ihr könnt theoretisch zum Beispiel das ganze Gedicht,
dieses ganze Adventsgedicht, könnt ihr als eine lange Zeichenkette auffassen.
Das kann man in Java ohne weiteres machen. Es gibt so andere Kontexte,
zum Beispiel in Datenbanken gibt es teilweise so Einschränkungen,
das sind dann bestimmte Zellen in einer Datenbank spalten,
nur einen Datentyp zu lassen,
der dann irgendwie maximal 256 Zeichen lang ist oder ähnliches.
Aber im Allgemeinen können wir davon ausgehen,
dass die unbegrenzt sind
Und immer, wenn ihr Texte verarbeiten wollt,
wie jetzt zum Beispiel hier dieses tolle Gedicht,
muss man natürlich irgendwie eine Unterstützung in der Programmiersprache haben für Zeichenketten.
Und in objektorientierten Sprachen werden tatsächlich Zeichenketten üblicherweise als Objekte modelliert.
Also wir haben jetzt wieder mal, wenn wir
mit der Typbrille drauf gucken, was ist eine Zeichenkette?
Eine Zeichenkette ist ein Typ. Also wir können sagen,
es ist die Menge aller Zeichenketten, beliebiger Länge.
Das ist die Wertemenge. Und dann gibt es hier so Operationen darauf,
wie lange ist eine Zeichenkette,
liefern wir mal die Subzeichenkette vom dritten bis zum siebten Zeichen dieser Zeichenkette,
liefen wir mal das Zeichen an, Position X und so weiter.
Das sind so die typischen Operationen, die man
auf Zeichenketten haben will
In Java ist das sehr speziell gemacht. Und ich bin
der Meinung, dass es ein kleiner Geniestreich war,
den die da hingelegt haben, die Java-Designer,
weil sie nämlich die Zeichenketten unveränderlich gemacht haben.
Und da gehe ich gleich nochmal genauer drauf ein,
auf der nächsten Folie. Erstmal, was haben wir?
Wir haben für Zeichenketten die Klasse String.
Es gibt auch noch andere Klassen in Java für Zeichenketten,
aber String ist schon sehr prominent.
Definiert, wie alle Klassen in Java einen Typs,
also wirklich eine Klasse String.
Das heißt, Exemplare dieser Klasse sind Zeichenkette, Zeichenketten.
Und String ist aber auch speziell unterstützt in Java,
hat also
eine Sonderbehandlung
Sieht man an einigen Situationen, dass es eine Spezialbehandlung gibt. Zum einen,
Stringliterale, die der Compiler ja kennt, diese doppelten Anführungsstrichen,
die sind speziell behandelt und haben aber den Typ String.
Also wenn ich zum Beispiel jetzt hier in der Direkteingabe in
Blue Jay irgendwas hinschreibe, irgendwas wie Hallo,
dann sagt er mir, das ist ein String.
Da wird mir der Typ direkt angezeigt, dass es dieses Literal ist ein String.
Und jetzt kann ich hier Außerdem habe ich eine Spezialbehandlung in Java,
dass ich diesen Operator plus verwenden kann auf Strings.
Also ich kann ja hier jetzt irgendwie sagen, stringen,
ach ne, ich kann ja auch einfach sowas machen wie,
ja, ach, machen wir mal sowas wie Stringen,
S ist gleich.
Hallo, ja, dann wird das hier sozusagen gleich initialisiert.
Dann habe ich also S enthält den Wert. Hallo,
jetzt kann ich sagen S plus Leerzeichen, Welt,
ja, dann liefert das Hallo Welt.
Also ich kann tatsächlich mit dem Plus-Operator Zeichenketten konkatinieren, verknüpfen.
Das habt ihr in den Übungen schon teilweise benutzt
Und was ihr noch nicht so euch explizit ansehen solltet bisher,
was ihr aber jetzt explizit passieren wird,
ihr werdet euch die Dokumentation der Klasse String ansehen.
Die Dokumentation von den Klassen in den Blue-Jay- Projekten habt ihr schon gesehen.
Ihr konntet im Blue-Jay-Editor umschalten und sagen, zeig mir mal diese Dokumentationsansicht,
wo ich nur die Informationen sehe, die für einen Klienten relevant sind.
Das ist hier auch versucht. Das ist jetzt tatsächlich mit die erste Klasse,
die ihr kennenlernt, wo die Dokumentationen nicht im Blue-Jay-Projekt drinsteht,
sondern zur sogenannten API von Java gehört,
aber diese Klasse String gehört zur einem speziellen Paket Java Leng
und deswegen müsst ihr damit nichts Zweiter tun.
Ihr könnt Strings einfach verwenden. Aber hier ist eine Beschreibung,
was String so für eine Schnittstelle hat.
Man sieht hier also, es ist relativ viel Zeug,
steht auch irgendwas. Strings are constant, ja,
also die sind Emutable und dann stehen hier so einige Informationen.
Man kann also durchaus auch im Klassenkommentar mal sinnvolle Dinge hinschreiben,
die so wirklich informativ sind. Und dann kommen hier
so einige Konstruktoren, das ist relativ viel Zeug.
Und dann gibt es hier Methoden und dann sehen wir genau sowas wie Char-Ed,
ja, für einen Index liefert irgendwie das Zeichen.
Oder ich kann fragen, ob ein bestimmter, eine bestimmte Char-Sequence,
das ist sowas ähnliches wie ein String enthalten ist.
Ich kann Hier irgendwie fragen, ob zwei Strings gleich sind und
so weiter. Also man sieht hier so einige Operationen,
das ist relativ viel Zeug, Index of, man kann auch fragen,
welchen Index hat denn ein bestimmtes Zeichen in der Zeichenkette?
Genau, dann gucken wir gleich nochmal ein bisschen genauer drauf.
Also haben wir hier diese ganzen Operationen,
längst Concat, Substring, Char Ad und so weiter.
Okay, das Erste, was man lernen muss mit Stringliteralen,
ist, dass man ja immer diese blöden Anführungsstriche angeben muss.
Hier ist so ein Beispiel mit diesem ein Bitter-Einmal-A-Sagen,
wenn ich das jetzt so versuchen würde als Zeichenkette zu schreiben,
also zum Beispiel System-Outprinting, bitte einmal A sagen,
wird das nicht funktionieren, weil ja dann hier die Zeichenkette schon zu Ende wäre.
Habt ihr in der Übung schon dieses Beispiel gesehen?
Das heißt, man muss mit einer Escape-Sequenz sagen,
hallo, ich möchte gern dieses Zeichen einmal quoten.
Das heißt also, das soll nicht als das Ende der Zeichenkette
interpretiert werden, sondern es soll in die Zeitenkette mit hinein.
Kann man also tatsächlich relativ leicht machen an dieser Stelle.
Was aber das Zentrale ist für Strings, was ich sehr,
sehr wichtig finde, ist, dass Strings unveränderlich sind.
Das heißt, ihr könnt auf einem String alle möglichen Operationen aufrufen,
ihr werdet aber den String selbst, das Objekt,
an dem ihr diese Operation aufruft, die werdet ihr,
das werdet ihr nicht verändern können
Also was wir gerade gesehen haben, war zum Beispiel hier,
dass sich diese Plus-Operation ausgeführt habe,
das hat aber nicht den String-ass verändert.
Es ist nach wie vor hallo. Sondern S-Plus-Welt war ein Ausdruck und
dieser Ausdruck liefert ein Ergebnis und das ist ein neuer String.
Das Beispiel, was hier auf der Folie ist, ist,
es ist gleich FCKW, das kann ich auch mal hier irgendwie sagen.
Es ist gleich F-C-KW W, so, so groß klein geschrieben.
Und wenn ich jetzt sage, S to upper case,
Das kann ich ja hinschreiben.
Dann liefert dann mir das FC Kaffee. Es liefert mir
als Ergebnis ein String, in dem alle Buchstaben groß sind.
Aber es selber ist immer noch unverändert.
Und das ist ganz wichtig, dass euch das klar ist.
Ein String, wenn er einmal erzeugt ist, ist unveränderlich.
Das bringt ganz viele Vorteile für viele Programmiersituationen. Und deswegen
ist das, wie gesagt, ein Design mäßig ein Ginny-Streich.
Sie sind natürlich damit dann sehr untypische Objekte.
Also ihr kennt normalerweise diese Idee, ein Objekt hat
einen Zustand und den kann man verändern.
Das ist bei Strings nicht so
Im Grunde genommen sind Strings, wie man ja auch schon gesehen hat,
das ist ja so in die Sprache mit rein integriert.
Ins Grunde genommen sind es dringend so was ähnliches wie die primitiven Typen.
Also man kann die fast so benutzen wie die primitiven Typen.
Es gibt nur so ein paar Stellen, an denen das nicht ganz funktioniert.
Zum Beispiel die Gleichheit von Strings. Das funktioniert leider nicht
so ganz einfach. Also wenn ich zum Beispiel das mache,
was hier auf der Folie steht,
ich sage also irgendwie S ist gleich Banane,
weise ich da mal zu, dann ist das tatsächlich Banane.
Und wenn ich jetzt sage S, gleich, gleich,
New, String, Banane.
Ich kann also mir einen neuen String erzeugen,
ein neues Stringobjekt, das auch die Zeichenkette Banane hält.
Und wenn ich die miteinander vergleiche, dann kommt da was raus,
True the Falls. Falls, genau, hier kommt Falls raus.
Weil ich esse, ist eine Referenzvariable auf ein Stringobjekt.
Und das andere liefert mir auch eine Referenz auf ein neues Stringobjekt.
Es sind zwei Stringobjekte, die die gleichen Zeichen enthalten,
aber es sind verschiedene Referenzen. Und bei dem Vergleich mit Gleichgleich
werden nur die Referenzen verglichen. Wenn ich tatsächlich jetzt wissen will,
ob dieser neue String etwas anderes ist, dann kann ich tatsächlich sagen,
s.equills, und dann kann ich jetzt hier New stringen, Banane schreiben.
Dann wird also tatsächlich hier natürlich so ein Konstrukteaufruf,
eine Referent geliefert, und dann kann ich tatsächlich fragen,
hey, String, S, vergleich dich doch mal deinen Inhalt mit dem Inhalt dieses anderen Strings.
Und das, liefor true, weil er wirklich die Zeichenketten vergleicht.
Das ist so eine der unangenehmsten Designschwächen von Java,
die haben die String Spezial behandelt,
aber leider haben sie sie immer noch so als Referenzen wie außen vor gehabt.
Sie hätten das ohne weiteres machen können,
dass dieses Gleichgleich für Strings automatisch abgebildet wird auf ein Equals.
Aber das ist eine Randgeschichte. Das heißt also,
für euch gilt Strings immer mit Equals vergleichen.
Immer. Wenn ich das irgendwo im Übungsbetrieb sehe,
dass da Strings mit Gleichgleich verglichen werden, dann gibt es gleich heiße Ohren.
Ich übertreibe. Ich werde euch nie schlagen, aber okay.
Dann kommen wir jetzt zum nächsten Thema, auch mit Strings.
Und zwar anhand eines konkreten Beispiels, nämlich wie eigentlich Compiler arbeiten.
Das, was ihr ja schon gesehen habt, ist,
wenn ihr ein Java-Programm schreibt, dann muss der Compiler das
Und das geschieht üblicherweise in mehreren Schritten. Also hier ist
das so ein bisschen angedeutet, da passiert relativ viel.
Das Quellprogramm wird erst durch eine lexikanische Analyse gejagt.
Dann werden aus einzelnen Zeichen werden token. Dann gibt es
eine Syntaxanalyse und dann wird ein Syntaxbaum gebaut.
Das müsst ihr alles nicht im Detail verstehen,
aber das ist das, worauf wir uns jetzt mal eben konzentrieren.
Das heißt, der Quelltext wird zerlegt in Token,
also mehrere Zeichen werden zusammengefasst zu logischen Einheiten, die man dann Token nennt.
Also hier zum Beispiel jetzt für Java kann man sich das angucken,
in Java gültige Token sind, sowas wie Next Item.
Das heißt also hier diese ganze acht Zeichen bilden zusammen ein Token.
Eine logische, eine logische Einheit, nämlich ein Bezeichner einer Variablen.
3.1416 ist auch zusammengenommen, eine Literal, das einen bestimmten Wert darstellt.
Oder auch hier kleiner gleich oder weil, das sind echte Token.
Das heißt, der Compiler macht tatsächlich sowas hier,
wenn er so einen Quelltext von euch bekommt,
dann wird als erstes diese lexikanische Analyse gemacht.
Das heißt, es werden einfach Schnitte gesetzt an den Stellen,
wo dann diese Token sind.
Das heißt also, diese Elemente werden erstmal,
diese Zeichenfolge wird an einen Tokensstrom umgewandelt
Und hier sieht man auch, das sind also keine
gültigen Items sind zum Beispiel sowas wie Next Itemball hier,
das minus schon wieder einen Schnitt definiert.
Das wäre also next minus Item, wären also drei Token.
Auch sowas wie 3,14 ist nicht ein Token und so weiter.
Okay, dieses Token-Bezeichner ist relativ häufig verwendet in Java,
also Variablen, kann man ja mit Bezeichnern benennen.
Exemplar Variable und ähnliches. Ich will jetzt einfach mal das ein bisschen veranschaulichen,
wie ein Compiler, sogenannte reguläre Ausdrücke verwendet,
um zu definieren, an welchen Stellen geschnitten werden soll.
Also, wir sagen einfach mal einen Bezeichner,
in Java besteht aus einem Buchstaben, unter Strich wird auch zugelassen,
gefolgt von beliebig vielen Buchstaben und Ziffern. Also,
wenn wir von einer Zeichenkette S vom Typ String feststellen wollen,
ob sie ein gültiger Bezeichner ist,
dann fragen wir Apps etwas abstrakter, ob S,
ein Element der Menge aller gültigen Bezeichner ist.
Das heißt also, ich kann tatsächlich einen String fragen,
hallo, match du auf die Menge gültiger Bezeichner.
Und diese Methode Matches ist tatsächlich in der Klasse String definitiv,
die habe ich hier eben schon fast gezeigt, hier,
Matches, hier als String, als Parameter eine Rack-Ex übergeben.
Tales with are not distring matches the given regular expression.
So, wenn wir uns jetzt vorstellen,
ich habe jetzt diese Regeln, was ein gültiger Bezeichner ist.
Diese Regeln kann ich mit einer regulären Aus,
mit einem regulären Ausdruck beschreiben. Das heißt, ich kann
einen einzelnen String fragen, ob er ein gültiger Bezeichner ist.
Match du, dieser Beschreibung der Menge aller gültigen Bezeichner.
Und dieses Match, Matches liefert genau True,
wenn diese Zeichenkette ein Element dieser Menge ist, ansonsten False.
Das heißt, wenn ich jetzt Bezeichner als regulären Ausdruck in Java
definieren will, kann ich das zum Beispiel so machen.
String, menge gültiger Bezeichner, ist gleich das hier.
Das sieht jetzt relativ wild aus. Das definiert aber
praktisch genau das, was ich gerade beschrieben habe.
Anfangs ein Unterstrich oder ein Groß-Kleinbuchstabe und dann ziemlich viele beliebige Zeichen und Ziffern.
Und das ist also eine Notation, wie man Mengen von Zeichenketten beschreiben kann.
Das wird auch als eine reguläre Menge bezahlt.
Das ist ein reguläre Ausdruck, beschreibt eine Menge von Zeichenketten.
Ich kann das hier mal live zeigen. Wir haben jetzt hier mal
irgendwie Stringmenge gültiger Bezeichner ist gleich, habe ich ja gerade hier gesehen,
also irgendwie eckige Klammer auf, ups, eckige Klammer.
Aber bis Z, man sieht, also das ist irgendwie so eine,
man kann auch so Bereiche angeben, Kleinbuchstaben,
dann A bis Z und der Unterstrich ist auch mit drin.
So. Und dann das wiederum gefolgt von dem gleichen nochmal,
also A bis Z bis Z und dem Unterstrich und dann auch nach 0 bis 9.
So, und das wiederum, und das ist eine Postfix-Nutation,
beliebig oft. Ich mache es mal ein bisschen breiter,
damit sie nicht so klein daherkommt,
damit ihr auch ein bisschen was erkennen könnt.
Also ich habe jetzt hier ein String-M-Menge gültiger Bezeichner definiert,
mit diesem regulären Ausdruck. Das ist einfach eine Zeichenkette,
die weiß ich jetzt dem String zu.
Okay, muss ich mal wieder eine Mikro dahinter schreiben.
Also Menge gültiger Bezeichner sieht jetzt so aus,
genau wie auf der Folie.
Und jetzt kann ich irgendeinen String fragen,
ob er auf diesen regulären Ausdruck matcht
Dieser reguläre Ausdruck beschreibt ja,
wenn man ihn richtig interpretiert, eine Menge von Zeichenketten.
Also kann ich jetzt zum Beispiel sagen, was ist denn S gerade?
Es ist Banane. Da würde ich sagen, das ist
ein gültiger Bezeichner. Also S. Matches, mengegültiger Bezeichner,
da sagt der True. Wenn ich jetzt esse,
aber irgendwie was zuweise wie eins, zwei AB,
ihr wisst, Bezeichner dürfen nicht mit Ziffern beginnen,
dann kann ich erst das zuweisen.
Dann ist S 12 AB. Wenn ich jetzt frage wieder,
matcht denn S,
matcht das denn wirklich dieser Menge der Gültigen bezeichnet,
dann sagt er natürlich Falls
Weil das kein gültiger Bezeichner ist. Das heißt,
das ist eine relativ hübsche Sache. Man kann relativ kompakt
eine ganze Menge von Zeichenketten beschreiben mit diesen regulären Ausdrücken.
Hier ist ein anderes Beispiel, diese eckigen Klammern,
das sind immer so Mengen. Das heißt also,
sowas wie Huase hier definiert eigentlich die reguläre Menge,
nämlich eine Zeichenkettenhose und Hase. Und nur genau diese.
Das ist also ein reguläre, ein regulärer Ausdruck hier.
Das ist hier dann so mit Bereichen,
haben wir eben schon gesehen, 1 bis 3,
SE1 bis 3 zum Beispiel an dieser Stelle
Die reguläre Menge S-1, SE1, SE2,
SE3. Und das alle kleinen Buchstaben und so weiter.
Und damit haben wir schon mehr oder weniger das hier verstanden,
was hier passiert. Also ein Zeichen aus der Menge der Bürgstaben
und ein Zeichen aus der Menge der Buchstaben und Ziffern.
Und das belieb ich oft wiederholt. Das ist dieser Post-Fix-Operator
. Stattdessen kann ich auch als Post-Fix einen Plus hinschreiben,
beliebige Wiederholung, mindestens einmal.
Stern auch kein Mal. Und Fragezeichen einmal oder kein Mal.
Ein einzelner Punkt in einem regulären Ausdruck steht für ein beliebiges Zeichen.
Und jetzt kommen wir nämlich mal zu unserem schönen Beispiel mit unserem Gedicht hier.
Ja, wir haben jetzt diese ganzen Texte hier.
Jetzt kann ich mal meinen Musterfinder hernehmen
Und mir ein Exemplar erzeugen. Und wenn ich dem jetzt
sage, finde doch mal ein Muster in der Datei.
Jetzt sage ich ein bestimmtes Muster, muss ich da angeben.
Jetzt möchte ich, das habe ich mir schon was bereitgelegt,
da hätte ich dann auch direkt kopieren können, Menge gültiger Bezeichner.
Ich sehe schon, hätte ich ein bisschen schlauer machen können.
So, jetzt möchte ich alle Zeilen, die die Zeichenfolge
die enthalten, in meiner, in meinem Gedicht finden.
Das heißt also, wenn ich jetzt diesem Musterfinder sagen will,
finde doch mal Zeilen, die dem String EED enthalten,
soll er mir ausgeben.
Wer kann mir mal jetzt den regulären Ausdruck nennen,
mit dem ich das jetzt finden kann?
Ja, die, okay. So, das ist jetzt
ein regulärer Ausdruck, der definiert eine Menge von Zeichenketten,
in diesem Fall tatsächlich nur eine Zeichenkette.
Da wird er jetzt vermutlich keine einzige Zeile finden,
die nur aus der Zeichenkette die besteht,
weil in dem Gedicht steckt ja,
dass die irgendwo irgendwie mit drin.
Gibt es
einen anderen Vorschlag, ja
Ah, okay, er schlägt vor, Punkt, Sternchen.
Also der Punkt ist ein beliebiges Zeichen. Sternchen heißt beliebig oft,
dann die und Leerzeichen, ja gut,
muss man nicht machen, kann man machen, muss man nicht.
Aber dann, ich vermute, Punkt Sternchen. So,
ja, das heißt also, beliebig viele Zeichen,
dann irgendwo die und dann wieder überliebig viele Zeichen.
Wenn ich das jetzt hier aufrufe, dann gebt ihr mir tatsächlich
alle die Zahlen des Gedichtes raus, wo die drin vorkommen.
Also hier die Försterin in dieser findet ihr jetzt auch,
weil eben das Leerzeichen nicht dabei war,
ja, also ich möchte, ist ja die Frage,
wie ich das als Wort finden oder wenn ich nur
die Zeichenfolge die finden
Weil Deezer ist ja auch ein DI drin.
Das sind jetzt genau die Zahlen, die das enthalten.
Okay, was haben wir noch? Alle Zeilen, die
mit D beginnen. Wir hatten einen Vorschlag, wie wir
das aufrufen können. Ja, also das ist natürlich,
hier haben wir schon mal das erste Muster.
Und wenn es mit D beginnen soll, ist es relativ einfach.
Ich nehme einfach diesen Hinweis beliebig Fälle zeichnen am Anfang,
nehme ich raus. Okay, dann liefert er mir genau
die, wo die am Anfang steht.
Also ihr seht schon, das ist ein relativ mächtiges Konzept.
Was haben wir noch
Alle Zeilen mit einem Pokal an vierter Stelle. Das ist ja
auch mal interessant. Alle Zeilen mit einem Vokal an vierter Stelle.
Wer kann mir einen regulären Ausdruck nennen, der mir das liefert?
Ja? Ne, da habe ich gerade einen Arm gesehen.
Nein, da, da, da. Punkt,
Punkt, Punkt, Punkt. Eckige Klammer auf.
A-E-I-O. Eckige Klammer zu. Punkt, Sternchen. Ja,
also das ist ein regulärer Ausdruck. Ihr seht,
das ist relativ schnell sehr kryptisch, aber drei beliebige Zeichen,
da gerade am Anfang können irgendwelche Zeiten,
dann an der vierten Stelle soll ein Zeichen sein aus dieser Menge,
nämlich eckige Klammern eine Menge,
Und dann danach wieder beliebig viele Zeichen. Und dann liefert
Embiid zum Beispiel relativ wenige. Das sind dann,
also hier sieht man jetzt, häuft,
er legte zwei, behält, also ihr seht,
da ist immer tatsächlich ein Vokal an der entsprechenden vierten Stelle.
Da ist eine Frage. Die an der vierten Stelle,
die aus der Stadt, was gefragt wird. Richtig,
wenn die an der vierten Stelle einen Großbuchstaben Vokal hätten,
wäre das so nicht ausreichend.
Da müsste man jetzt da noch mit aufnehmen,
dass man die auch als Großbuchstaben haben will.
Haben wir jetzt nicht gemacht. Gut,
also man könnte da jetzt noch ein paar mehr Beispiele hernehmen.
Alle Zeilen, die einen Punkt enthalten,
können wir uns nochmal angucken
Die einen Punkt enthalten. Das ist ja jetzt
ein bisschen schwierig, weil wir ja gerade gesehen haben,
dass der Punkt ja ein Spezialzeichen ist.
Wie kann ich denn jetzt ihn suchen lassen nach einem Punkt?
Selbst das. Okay, also Punkt Stern,
das ist erstmal wieder beliebig viele Zeichen,
dann ein Backslash-Punkt und dann wieder Punkt Stern.
Ihr seht, das ist absolut absurd,
aber mit dieser Zeichenkette finde ich jetzt alle Zeichenketten,
die einen Punkt enthalten
Da fehlt noch was? Eine Klammer oder was? Ah,
die Ecke, Klammern musst du weg, genau.
So, so müsste es funktionieren. Ja. Okay,
ja, habe ich jetzt einen Escape-Character drin,
damit der da auch wirklich reinkommt,
muss ich jetzt hier nochmal einen Escape-Character davor zeigen.
Und siehe da, ergibt mir alle die Zahlen aus, die einen Punkt enthalten.
Das sind jetzt hier in diesem Gedicht sind das alles die,
die den Punkt am Ende haben, weil da keine Punkte mittendrin sind.
Also ihr seht, das funktioniert, das ist ein ziemlich cooles Zeug.
Weitere Beispiele für reguläre Ausprobieren könnt ihr im Skript nachlesen.
Jetzt will ich nochmal ganz kurz einen kurzen Ausflug machen.
Also hier einmal ist hier ausführliche Beschreibung der syntaxsreguläre Ausdrücke.
Das findet ihr aus der Dokumentation von String heraus.
Also wenn sich das jetzt hier anguckt,
dann sieht das eben so aus, Klaas Pattern.
Und dann sieht man hier diese verschiedenen Möglichkeiten, wie man
das beschreiben kann. Also welche Zeichenfolge, was matcht.
Also zum Beispiel hat man hier so Character-Classes wie Backslash D
ist das Gleiche wie diese Menge von 0 bis 9.
Also wenn ich jetzt einfach nur so eine Ziffer suchen will,
dann brauche ich das nicht so mit diesen eckigen Klammern schreiben.
Kann ich so machen, muss ich aber nicht.
Es gibt dann so Abkürzungen. Also diese Informationen sind extrem nützlich.
Das alles Könnt ihr eigentlich auch in Ruhe nochmal nachlesen.
Das ist jetzt hier, sagen wir, ja,
es gibt da noch weitere Operationen in der Stringklasse,
die mit regulären Ausdrücken arbeitet. Das ist schon ziemlich mächtig
. Also zum Beispiel hier eine neue Zeichenkette als Kopie,
in der das erste Vorkommen, eine der Zeichenketten,
die durch RegX beschrieben sind, durch Replacement ersetzt ist.
Also richtig mächtige so Zeichenkettenersetzungen machen. Okay,
das ist der Hintergrund, der dazu kommt,
zu diesen regulären Ausdrücken sind formale Sprachen.
Die werdet ihr in FGI1 wahrscheinlich alle
noch irgendwie kennenlernen
Letztendlich ist das so, dass diese formalen Sprachen verstanden werden können,
als eine Menge von Zeichenketten eines Alphabets.
Und das will ich jetzt hier nicht groß vertiefen,
verschiedene Klassen von Grammatiken, Trumpsky-Hierarchie, kontextfreie Grammatiken, habt ihr schon kennengelernt?
Das sind eigentlich, das ist die EBNF, mit der
man Grammatiken von, die Syntax von Programmiersprachen beschreiben kann.
Und jetzt haben wir diese regulären Ausdrücke kennengelernt.
Die sind tatsächlich sowas wie reguläre Sprachen. So,
und wenn man die jetzt miteinander vergleicht, ja,
das ist hier so ein bisschen ausführlicher, reguläre Ausdrücke.
Und ja, war fast wie eine Theorie
Was wir gesehen haben, kontextreihe Grammatiken in Java,
also in einer EBNF, beschreiben, die kontextfreie Sprache.
Das ist eine Menge von Zeichenketten. Das heißt also,
man kann fragen, ist eine bestimmte Java-Programm,
gehört das zu dieser syntaktisch definierten Sprache?
Was wir jetzt gesehen haben, sind so reglere Ausbrücke,
reguläre Ausdruck, ist eine reguläre Grammatik, beschreibt eine reguläre Sprache R,
also offen eine Menge von Zeichenketten und damit kann ich zum Beispiel fragen,
ist eine Zeichenkette ein Bezeichner?
Und so kann ich eben halt die ganzen Token einer Sprache beschreiben.
Das sieht sehr ähnlich aus und die Frage ist,
wo ist denn der Unterschied,
warum macht man das überhaupt,
diesen Unterschied
Und die Antwort ist einfach, das liegt in der Mächtigkeit.
Also kontextfreie Grammatiken können mehr, sind aber dafür auch ein bisschen aufwendiger in der Verarbeitung.
Das heißt, die regulären Ausdrücke haben den Vorteil,
dass sie sehr schnell sind. Und ihr werdet sehen,
dass man mit regulären Ausdrücken sehr,
sehr mächtige Dinge tun kann und dass eben halt ein Compiler
tatsächlich aus so einem regulären Ausdruck Code erzeugen kann,
der das sehr schnell erkennen lässt,
auf eine Zeichenkette dazugehört oder nicht.
Das ist die Zusammenfassung. Ich habe aber natürlich
noch eine abschließende Sturoresi-Frage zu diesem Thema
Ja, also jetzt haben wir noch genau so zwei,
drei Minuten, sehr schön.
Welche der folgenden Ausdrücke in der Java-Notation als Zeichenkette angegeben,
definiert nicht eine reguläre Menge, die das Wort Auto enthält.
So, ja, da könnt ihr jetzt nochmal
ein bisschen eure Stimmen abgeben.
Also vier verschiedene reguläre Ausdrücke, die Mengen von Zeichenketten beschreiben.
Und in diesen vier Mengen in einer dieser vier Mengen
ist nicht das Wort Auto enthalten
Ich habe natürlich jetzt gemeinerweise die runden Klammern nicht erläutert,
was die bedeuten, für reguläre Ausdrücke.
Die gruppieren einfach nur. Also wenn ihr jetzt heute gut aufgepasst habt,
dürftet das eigentlich relativ leicht sein, das zu beantworten.
Ich mache mal so bei 1050 Schluss Ja,
damit wir das Ergebnis heute noch sehen.
Naja, kommt, gebt mir eure Stimme.
Na gut, ich stoppe das jetzt mal.
Es sind also ungefähr 150 Stimmen. So,
wir gucken mal auf die Verteilung der Antworten
Oha. Ja, also nur ungefähr 45 Prozent sind
der Meinung, dass es der letzte Ausdruck ist.
Und dann gibt es doch einige, die der Meinung sind,
dass es die anderen Ausdrücke sind.
Richtig ist tatsächlich an dieser Stelle das. Also die Mehrheit
hat das richtig verstanden. Das heißt, wenn ich jetzt
hier nochmal umschalte, das hier ist ja eine reguläre Menge.
Also innerhalb, also dieser reguläre Ausdruck definiert ein Zeichen,
lange Zeichenketten. Entweder A oder U oder T oder O.
Das ist die eckige Klammer. Okay, wenn das alles
war, dann machen wir Schluss für heute.