Wir kehren wieder zurück zu etwas profanerem, nämlich
Datentypenzuweisungen.
Hier, das ist eine Folie, die hatten Sie beim letzten Mal schon
gesehen.
Hier geht es um die Wertebereiche und ich habe die Folie nochmal
aufgelegt, weil tatsächlich nach der Vorlesung nochmal die Frage
aufkam, ja was ist denn eigentlich der größte darstellbare Exponent
und was ja auch ein bisschen dahinter steckt ist, wie kommt man auf
diese Werte hier?
Also bei den Wertebereichen, bei den Gleitkommazahlen.
Und die Antwort ist tatsächlich die, der höchste Exponent bei einer
einfachen Präzision, also Float, Gleitkommazahl, ist 2 hoch 127 mal
dann eine Mantisse.
Und man kann sich fragen, wie kommt diese 2 hoch 127 zustande?
Naja, Sie erinnern sich, wir hatten diese Biased Representation
gehabt, das heißt man muss noch 127 draufrechnen, dann kommt man bei
254 raus.
Jetzt kann man sagen, warum ging es das?
Naja, Sie brauchen einen Wert von den 256 darstellbaren Werten, die
Sie in 2 hoch 8 darstellen können für die 0.
Den anderen brauchen Sie für die 1, 1, 1, 1, 1, 1, 1, 1, 1, diesen
besonderen Wert, der eben genutzt wird zur Codierung von Not a Number
oder Positive oder Negative Infinity.
Und wie gesagt, wenn man diese zwei Werte abzieht, kommt man in einen
Wertebereich von 254 minus 127, ist man nochmal bei 127.
Und 2 hoch 127 ist dann tatsächlich der größte Exponent, den man
darstellen kann.
Und wenn man das ins Dezimalsystem umrechnet, wie gesagt multipliziert
mit noch der Mantisse, dabei kommt man eben bei diesen 10 hoch 38
raus.
Und das war auch ja die Frage gewesen bei dem, was ist die größte
darstellbare Zahl, was ist die zweitgrößte, was ist die Differenz, wo
man merkt, 10 hoch 31.
Und klar, bei Gleitkomma, wo man im Prinzip mehr Bits für die Mantisse
hat, hat man 10 hoch 4 und 2 hoch 1024 als größten Exponent mit
derselben Logik über die Biased Representation, die ich eben hatte.
Und dann kommt, wenn man das umrechnet, auf 10 hoch 308.
Das vielleicht noch so als Ergänzung oder als Erklärung zu dieser
Folie, die ich schon das letzte Mal aufgelegt hatte.
So, jetzt springen wir dahin, wo wir schon waren.
Und zwar beim Thema Zuweisungen.
Nochmal kurz zur Wiederholung.
Eine Variable ist nichts anderes als ein Name, ein Bezeichner für eine
Speicheradresse.
Im Speicher liegt der Wert der Variable abgelegt, es wird dort
gespeichert.
Da es aber natürlich ein bisschen unhandlich ist, immer mit
Speicheradressen zu rechnen, mal abgesehen davon, dass man nicht genau
weiß, welche Speicheradresse habe ich denn heute, ist es natürlich
auch immer schlecht, nur mit so abstrakten Zahlen zu rechnen.
Und deswegen gibt es halt die Variablen Namen.
Und dort wird dann tatsächlich der Wert einer Variable gespeichert im
Speicher unter diesem Variablen Namen, der aber, wie gesagt, nichts
anderes ist wie eine Speicheradresse eigentlich.
Und damit man weiß, wie man diesen Wert interpretieren muss, haben wir
auch immer noch den Typ dazu gemerkt.
In Wirklichkeit liegt der nicht im Speicher abgelegt, der Typ.
Das muss man halt eben die Programmlogik wissen, wie man einen Wert zu
interpretieren hat aus dem Speicher.
Und dann, wenn man jetzt zum Beispiel sagt, x bekommt den Wert 4295,
dann wird eben an der Speicheradresse, die durch x bezeichnet wird,
dieser Wert hingeschrieben.
Dann hatten wir uns das hier auch mal angeschaut, dass wir Zuweisungen
machen können, dass wir auch Zuweisungen machen können mit dem alten
Wert einer Variable.
Als Gleichung gelesen macht das wenig Sinn.
Aber es soll eben heißen, hier wird der alte Wert von x genommen, eins
drauf hat den neuen Wert von x geschrieben.
Dann hatten wir noch zur Erinnerung gesagt, ja, ein bisschen was davon
hatten wir schon gesehen, nämlich als wir Attribute von Klassen
angelegt hatten.
Das sind auch Variablen, aber Variablen, wenn man so will, die zu
einer bestimmten Klasse gehören.
Das nennt man dann Attribut.
Und da konnten wir auch Zuweisungen machen.
Da waren wir stehen geblieben, das hatten wir das letzte Mal nicht
gemacht, aber das ist jetzt zum Einstieg vielleicht ganz gut, dass wir
uns mal eine kleine Aufgabe stellen, und zwar ist es so, ich lese sie
gerade vor, Sie sehen hier ein Programmfragment, wo drei
Gleitkommavariablen definiert werden, a, b und c, und die bekommen
auch gleich eine Zuweisung.
In dem Fall hier mit Konstanten, zweieinhalb, drei, c wird in der
Summe von a und b zugewiesen, auch das geht in Java.
Und dann wird das ausgegeben hier mit System.out.println, dann wird
der Wert von a manipuliert, wieder wird der Wert von c ausgegeben,
dann wird der Wert von a nochmal ausgegeben.
Es gibt also drei Ausgaben in diesem Programm, dreimal ein System.out
.println, und jetzt machen wir so ein bisschen demokratische
Informatik, wir üben also das Wählen auch schon mal.
Und zwar, was kommt bei der ersten Ausgabe raus, also bei diesem
System.out.println?
Also System.out.println c, c ist gleich a plus b.
Jetzt ist die Frage, wer ist für die Lösung a?
5,5.
Wer ist für die Lösung b?
Keiner, das traut sich jetzt keiner.
Schön, genau, also das stimmt auch, 5,5 kommt raus, es wird natürlich
als Gleitkommazahl interpretiert, es gibt keinen Grund, die Komma 5
wegzuwerfen, weil es jetzt auf einmal ganzzahlig sein müsste, sondern
System.out.println übernimmt den Typ, den c hat, das ist float, also
kann man auch die 5,5 ausgeben, kein Problem.
Richtig, so jetzt kommen wir zur zweiten Ausgabe hier, das ist diese
Zeile, System.out.println c.
Zwischendurch weisen wir a, b plus 1 zu, 1f heißt ja wieder nur 1 als
Gleitkommazahl, und jetzt ist die Frage auch wieder hier, was ist denn
hier die Ausgabe, 5,5 oder 6,5?
Wer ist für a?
5,5.
Wer ist für b?
Genau, auch einige.
Das ist insofern gut, dass ich diese Aufgabe gemacht habe, die
korrekte Antwort ist tatsächlich a, was die Mehrheit hier gesagt
hatte, hier klappt Demokratie im Hörsaal, und zwar deswegen, ich habe
zwar den Wert von a manipuliert, aber ich habe c nicht eine neue
Zuweisung gegeben.
Es ist also tatsächlich so von der Programmlogik her, dass dieser Wert
von c wird nicht dadurch manipuliert, weil ich hier a nochmal ändere
nachträglich.
Wenn ich jetzt hier nach nochmal neu c mit a plus b auswerten würde,
dann hätte sich natürlich was geändert, aber das habe ich nicht
gemacht.
Hier gebe ich c aus, c hat nach wie vor den Wert 5,5.
Das ist derselbe wie hier, also insofern ist 5,5 hier die korrekte
Antwort.
Aber wie gesagt, der Sinn der Aufgabe ist auch, das nochmal Ihnen
genau zu erklären, dass bloß weil ich einen der Parameter einer
vorherigen Ausdrucksauswertung ändere, dass das natürlich nichts
ändert an dem Wert von c an der Stelle.
So, jetzt kommen wir zur dritten Ausgabe.
System.out.println a, diesmal wird der Wert von a ausgegeben, also b
plus 1.
Und die Frage wäre also, ist es jetzt hier 5,5, 3,5 oder 4,0?
Wäre es für a?
Wäre es für b?
Und wäre es für c?
Das ist die Mehrheit und das stimmt auch.
Tatsächlich wird also hier der Wert von b genommen, das ist eine 3 als
Gleitkommazahl.
Es wird 1 draufaddiert, das ist die 4 als Gleitkommazahl an die 4,0.
Also wie man es erwartet eigentlich auch.
So, jetzt wird es ein bisschen trickreicher.
Wir gucken uns jetzt das Programm hier an.
Auf dieser Seite mit i und j.
i bekommt den Wert von 1,05.
Das ist logischerweise eine Gleitkommazahl, als Float auch.
Und j bekommt den Wert 0,05.
Und jetzt gebe ich die Differenz von i minus j, also aus.
Also hier, und jetzt ist die Frage, was kommt raus?
a 1 oder b irgendwas ungleich?
1 aber nah dran.
Wäre es für a?
Ein paar melden sich.
Die denken logisch.
Wäre es für b?
Genau, die denken in III-Gleitkomma-Arithmetik, die leider nicht ganz
logisch ist.
Insofern ist es aber, die b ist tatsächlich korrekt.
Es tritt hier schon ein Rundungsfehler auf.
Das Problem ist, dass selbst bei so kleinen harmlosen Zahlen wie 1,05,
das ist nicht präzise speicherbar im III-Gleitkomma-Format.
Das ist, hatte ich Ihnen ja gesagt, sehr häufig so, dass Zahlen, die
im Dezimalsystem klein, präzise, kompakt sind, leider in der dualen
Darstellung, also mit der Basis von 2, in der III-Gleitkomma
-Arithmetik nicht präzise darstellbar sind.
Und deswegen kommt tatsächlich hier ein Rundungsfehler zustande,
selbst schon bei dieser einfachen Rechnung, die natürlich
logischerweise 1 ergibt, aber dummerweise eben mit Rundungsfehler
nicht, sondern diesen Wert ergibt.
Leider war, und Sie können sich vorstellen, selbst hier schon bei so
einer einfachen Rechnung, haben Sie diese Rundungsfehler, und das kann
sich natürlich aufschaukeln, wenn Sie jetzt mehrfach mit dieser Zahl
weiterrechnen, dann wird der Fehler unter Umständen immer größer.
Das so ein bisschen als Warnung.
So, jetzt schauen wir uns das Ganze nicht nur mit normalen Variablen
ein, also mit grundlegenden Datentypen, wie sie in Java eingebaut
sind, InFloat oder sowas, sondern jetzt mal das Ganze, wie klappt das
eigentlich mit den Objekten.
Und Sie erinnern sich vielleicht an die erste Vorlesung, die ich Ihnen
gegeben habe, da gab es diesen new Operator.
New erzeugt ein Objekt, man sagt auch eine Instanz einer Klasse.
Und das sieht man also hier, dieser Vektor p, das ist quasi das
Ergebnis dieser new Operation, wo eine Instanz der Klasse Vektor2D
angelegt wird.
Und dann habe ich in Vektor2D, Sie erinnern sich vielleicht noch, das
x - und y-Koordinate, und die kann man tatsächlich, das hatten wir
auch schon gehabt, mit p.x setzen, auf Werte, also ganz normale
Zuweisungen, aber man schreibt jetzt hier eben p.x bzw.
p.y, wo man sagt, von dem Objekt p das Attribut x, da schreibt man p
.x.
Dass das kein super guter Stil ist, wissen die Programmiererfahrenen
von Ihnen, dass man nicht direkt auf Attribute zugreifen soll, aber
wir machen das jetzt mal hier der Einfachheit halber.
Und wenn ich das jetzt mache, hat man hier zum Beispiel dieses Objekt
p im Speicher von Vektor2D als Typ, und da hat man zwei Attribute, x
und y, und das hat den Wert 4, also 22,5.
Jetzt kann ich mir mit new eine neue Instanz von Vektor2D holen, also
ein neues Objekt, was an einer anderen Speicheradresse liegt.
Das hat den Namen q.
Sie sehen hier, das Objekt ist letzten Endes auch nichts anderes wie
eine Variable.
Das liegt hier im Speicher, und denen weise ich diese Werte zu.
Das heißt, wir haben im Speicher zwei verschiedene Speicheradressen,
einmal p und einmal q.
Sie erinnern sich, Variablennamen sind nichts anderes als eigentlich
Speicheradressen.
Und hier liegen dann an dieser Stelle eben diese Werte abgelegt.
Jetzt werden sich die Erfahrener von Ihnen sagen, ja, aber wie ist das
denn in echt?
Und ein bisschen näher an der Realität ist es hier.
Wenn man sich den Speicher in einem Augenblick mal vorstellt als
fortlaufende Zahl von Speicherzellen, wo jedes Mal ein Integer
drinsteht, stimmt nicht ganz.
Cracks wissen natürlich, dass ein Integer mehrere Bytes belegt, aber
so kompliziert würde ich es jetzt nicht machen.
Ich nenne es sich erst mal vor als Abfolge von Integers.
Und die haben alle ihre Speicheradresse.
Das ist das, was letzten Endes in diesem xyzabcdef drinsteht.
Dann haben sie ihre Werte.
Und wie gesagt, in echt speichert man den Variablentyp nicht mit.
Das machen wir jetzt nur mal hier, deswegen, weil wir diese
Komplexität, wie wir eine Gleitkommazahl oder ein Integer abbilden auf
Bytes, die tatsächlich im Speicher sind, hier weglassen wollen an der
Stelle noch.
Das ist wieder eine abstrakte Sicht auf den Speicher.
Sie wissen, es gibt immer Speicheradressen und dann Speicherzellen.
Und auch wenn in echt die Speicherzellen immer nur Bytes sind, für uns
sind jetzt mal hier Datentypen auch mit dabei.
So, und dann sieht das Ganze so aus.
Wenn ich jetzt ein Objekt habe, wie unser P eben, das ist ein Vektor
-2D -Objekt, dann werden nacheinander die Attributwerte abgelegt im
Speicher.
Das heißt, erst kommt die Speicheradresse hier 17, also x hat den Wert
17, y hat den Wert 18, wo hier immer die 2,0 und 3,0 drin liegen, in
unserer abstrakten Speichersicht.
Und dann gibt es irgendwo auch noch die Variable, das Objekt, hier mal
Position genannt, eben war es P noch im Programm, und hier steht dann
drin 17.
Und 17 ist jetzt quasi, wenn man so will, eine Referenz auf die
Speicheradresse, wo die Attribute anfangen.
In dem Fall also 17, das muss logischerweise nicht hintereinander im
Speicher liegen.
Irgendwo habe ich meine Objekte, die sind aber nichts anderes als
Referenzen auf den Speicherbereich, wo die Attribute abgelegt werden.
Und hier sieht man, dass die 17, deswegen hier so als Zeiger zeigt,
also das Post zeigt auf das 17, und da stehen dann tatsächlich die
Attribute nacheinander abgelegt im Speicher.
Wenn man sich das jetzt mal hier anschaut im Code, ich erzeuge hier
zwei Vektoren, P und Q,
und wie gesagt, new macht diesen Eintrag, ab 17 gibt es Speicher, dann
lege ich jetzt, wie gesagt, initialisiere ich die Werte, x und y von
P, dann lege ich also hier an die Stelle 17, diesen Wert 2 kommen wir
dann bei y 3,5 im Nächsten ab, und jetzt mache ich nochmal new,
bekomme hier einen weiteren freien Speicherplatz, das ist jetzt hier
in dem Fall die 20 mal, und dann mache ich hier meine Initialisierung
von Q.x und Q.y, jetzt habe ich das gemacht, die liegen jetzt hier,
und jetzt sage ich noch, ich habe ein Objekt R, und das soll
tatsächlich den Wert von P haben.
Und das heißt, R zeigt jetzt auch dahin, wo P zeigt, nämlich auch auf
die 17.
Und jetzt sehen Sie an der Stelle, dass tatsächlich zwei verschiedene
Objekte als Variable zum selben Objekt zeigen können, was im Speicher
liegt.
R und P sind in dem Fall jetzt wirklich dasselbe, also haben die
selben Attributwerte, sind aber zwei verschiedene Objekte, auch wenn
es aber, wie gesagt, auf dieselbe Speicheradresse zeigt.
De facto ist das dann in der Praxis ein Objekt.
Ich habe zwei Variablen, die aber auf dasselbe Objekt zeigen.
Wenn ich jetzt diese Zuweisung machen würde, P gleich Q, das kann ich
auch machen, dann verändert sich das Folgende, dass jetzt hier die 20,
die Q steht, auf P zugewiesen wird, das heißt, P zeigt jetzt nicht
mehr auf das Objekt, was bei der Speicheradresse 17 abgelegt ist,
sondern P zeigt jetzt auf das Objekt bei der Speicheradresse am 20,
genau wie das bei Q war.
Zum Glück zeigt R noch auf dieses Objekt.
Diejenigen von Ihnen, die schon ein bisschen erfahrener sind, wissen
vielleicht, was passiert, wenn es überhaupt keinen mehr gibt, der auf
irgendein Objekt zeigt.
Also hier könnte ja ein Objekt liegen, und wenn ich jetzt kein R
hätte, dann würde ja gar keiner mehr hierhin zeigen.
Was passiert dann?
Reinrufen.
Genau, dann kommt der Garbage Collector, oder auf Deutsch der
Speicherbereiniger, irgendwann mal vorbei, nicht sofort, aber
irgendwann mal und stellt fest, ups, das ist ein Objekt, das belegt
zwar Speicherbratz, aber es ist gar nicht mehr referenzierbar.
Es gibt gar keine Variable mehr, die sich quasi diese Speicheradresse
gemerkt hat.
Das heißt, ich kann diesen Speicher freigeben.
Ich muss das ja nicht mehr speichern.
Es kann ohnehin keiner mehr darauf zugreifen.
Und das ist, vielleicht haben einige den Begriff schon gehört, der
sogenannte Garbage Collector, der Müllsammler, wenn man das wörtlich
übersetzen würde, der halt im Prinzip periodisch nach einem bestimmten
Algorithmus durch den Speicher geht und überlegt, was sind eigentlich
Objekte, die nicht mehr referenzierbar sind, weil es gibt keinen
Zeiger mehr auf sie, es gibt keine Referenz mehr auf sie.
Das ist zum Glück aber hier nicht der Fall.
Das R speichert nach wie vor den Wert 17 und insofern ist das hier
referenzierbar und das hier auch, sogar zweimal, nämlich einmal über P
und einmal über Q.
Aber nach wie vor zwei Objekte nur.
Ich habe drei Variablen, aber zwei Objekte.
Ups.
So.
Das hatte ich gesagt.
Wichtig ist vielleicht auch noch, ist ja eine Einstein-Folie, kann man
natürlich auch die etwas komplizierteren Themen machen, die Identität
eines Objektes hängt erstmal im Default nicht davon ab, ob ein Objekt
dieselben Attributwerte hat.
Selbst wenn ich jetzt hier xy 2,0 3,5 hätte und hier auch x 2,0 y 3,5
hätte, das wären immer noch nicht dieselben Objekte, auch wenn die
Attributvariable, die Attribute dieselben Werte haben.
Es würden erst dann dieselben Objekte, wenn sie an derselben
Speicheradresse liegen.
Also erstmal standardmäßig, ein Objekt wird charakterisiert, oder die
Identität eines Objektes ergibt sich aus einer Speicheradresse, sprich
dem Wert, der hier in der Variablen steht.
Nicht über die Belegung der Attributwerte.
Diejenigen, die jetzt auch wieder fortgeschrittener sind, wir sind ja
bei der Einstein-Folie, die wissen, ich kann natürlich meinen Equals
-Operator, der feststellt, ob zwei Objekte gleich sind oder nicht,
umbauen, sodass doch wieder die Werte von den Attributen genommen
werden, aber das muss man sich halt schon selber programmieren.
Standardmäßig ist er erstmal unterschiedliche Speicheradresse,
unterschiedliche Objektidentität.
So und hier sieht man das auch nochmal dargelegt.
Ich habe drei Objektreferenzen und nur zwei Objektidentitäten.
Eben war es auch so, es ist die selbe Situation hier, nur jetzt
vielleicht nochmal grafisch ein bisschen eingängiger dargestellt.
Und wir sehen also, wenn wir jetzt eine Zuweisung machen, p gleich q,
dann ändert sich im Prinzip hier die Referenz von der 17 auf die 20.
Das ändert aber nichts daran, dass ich immer noch drei
Objektreferenzen habe und zwei Objektidentitäten.
So und wenn ich jetzt hier einen Wert ändere mit px 7,5, dann
dereferenziert man erst das Objekt und dann ändert man das hier und
dann ändert man jetzt dann hier den Wert.
Dann gibt es noch eine Besonderheit, die ich noch am Ende dieser
Vorlesungseinheit erwähnen möchte und zwar den Wert 0.
Das ist ein vorgegebenes Wort in der Java-Sprache und das soll im
Prinzip heißen, das ist ein spezieller Wert, der sagt, dieses Objekt
wurde noch nicht angelegt.
Das wurde noch nicht mit new, da wurde mit new noch kein Speicher
besorgt.
Also wenn man Vector2D p gleich 0 hinschreibt, braucht man nicht, weil
standardmäßig hätte es auch von alleine 0, aber wenn man es nochmal
hinschreibt, dann heißt es halt, es wurde hier zwar eine
Objektreferenz angelegt, p, das habe ich, aber da wurde hinterher noch
kein Objekt erzeugt.
Es wurde halt noch nicht new aufgerufen und tatsächlich Speicher
besorgt und das Objekt erzeugt.
Das passiert eben dann erst durch so eine Zeile.
Die Objektidentität wird erst durch den Konstruktoraufruf, so heißt
das auch, new erreicht.
So, dann gibt es auch noch eine kleine Besonderheit und zwar das Wort
final.
Das Wort final heißt, dass eine Variable nicht geändert werden soll,
mit anderen Worten, die Variable ist gar nicht variabel, sondern eine
Konstante.
Und davon gibt es letztendlich zwei Varianten, die Ihnen auch im
Programm durchaus begegnen können.
Das eine ist, wenn man einfach ganz normal sagt, ich habe in einer
Routine foo ein String Data definiert und der soll konstant sein, das
heißt, er soll nach dieser Initialisierung nicht mehr geändert werden,
sondern es soll nur noch gelesen werden, dann schreibe ich hier final
davor.
Dann gibt es aber auch die Variante, dass man das eben nicht vor
lokale Variablen, also Variablen innerhalb einer Methode reinschreibt,
also dieses foo ist ja hier eine Methode, das werden wir nachher noch
kennenlernen, sondern dass man es reinschreibt gleich bei den
Attributen.
Und dann schreibt man hier noch static final davor und dann heißt das,
das sind Konstanten, die sind eigentlich an die Klasse gebunden.
Das heißt, wenn Sie das jetzt Objekte instanziieren davon, Sie könnten
ja von der Klasse foo mehrere Objekte nachher instanziieren mit new
foo, dann wird aber hierfür gar kein eigener Speicherbereich
eingelegt, weil das sind Konstanten der Klasse.
In einem Objekt gibt es sie dann in dem Sinne nicht mehr als normales
Attribut, weil es ist auf der Klassenebene mal einmal festgelegt
worden als Klassenkonstante.
Und der Grund, warum ich das erwähne, ist auch, es gibt eine
Konvention, das in kompletten Großbuchstaben hinzuschreiben,
wohingegen hier behandelt man das wie eine normale Variable von der
Art, wie man es schreibt.
Das Data hier wird nicht großgeschrieben, hier schon.
Das ist eine reine Konvention.
Der Compiler erzwingt das nicht, aber diese Konventionen sind halt
hilfreich, um den Programmcode leichter verständlich zu machen und
eben Leute hier noch mal darauf hinzuweisen, das ist eine
Klassenkonstante, weil das sieht man natürlich nachher, wenn man den
Begriff verwendet, nicht dem per se an, dass das eine Klassenkonstante
ist.
Der wird ja genauso verwendet wie ein normales Attribut, aber immer
nur lesend.
Und deswegen hat man diese Konvention hier, das Großbuchschreiben, die
ist schon auch sinnvoll, um eben bei der Verwendung darauf
hinzuweisen, das ist eine Klassenkonstante, aber ansonsten ist es nur
eine Konvention, kein Sprachstandard oder so.
So, in Java ist es so, dass tatsächlich alle Variablen immer einen
Datentyp haben müssen.
Das hat unter Umständen auch Nachteile, die man ein bisschen versucht
zu beheben mit einem Thema, wo wir sehr viel später, erst nach
Weihnachten, hinkommen, das sogenannte Generics-Thema.
Aber weil man halt manchmal merkt, jetzt habe ich einen Code
geschrieben für den einen Datentyp und der Code für den anderen
Datentyp sieht eigentlich genauso aus.
Das ist ein bisschen lästig, dass man zweimal Code schreibt, obwohl es
eigentlich nur wegen unterschiedlichen Datentypen zweimal den Code
schreibt.
Aber wie gesagt, das geht dann zum Thema Generics.
Es hat aber auch eine Reihe von Vorteilen, dass jede Variable einen
Typ hat.
Zum einen macht es dem Compiler das Leben leichter, wenn er Speicher
allozieren muss.
Sie wissen ja, ein Integer wird anders interpretiert und anders
gelesen und auch anders multipliziert als eine Gleitkommazahl.
Und das müssen die Übersetzer natürlich wissen, wenn er Code
generiert.
Zum anderen ist es aber auch so, sie können gewisse Klassen von
Fehlern entdecken.
Stellen Sie sich vor, Sie wollen, weil Sie sich vertan haben, auf
einen String die Zahl 1 addieren.
Da würde der Compiler sagen, das geht nicht, die Additionsoperation
ist für Strings nicht definiert.
Und das heißt, Sie bekommen einen sogenannten Typfehler.
Und das ist sehr schön, weil den Typfehler bekommen Sie quasi umsonst,
weil das prüft der Compiler halt ab, ob eben, wie gesagt, keine
Typfehler vorliegen.
Das kann er statisch abprüfen, ohne das Programm auszuführen.
Er macht das im Prinzip nur beim Kompilieren schon in einem Rutsch,
wenn man so will, mit dieser Typprüfung.
Und das sind halt bestimmte Klassen von Fehlern, die Sie angezeigt
bekommen.
Und diesen Wert, dass Sie automatisch Fehler angezeigt bekommen haben,
verstehen vor allem die, die schon mal endlose Nächte in Debugging
zugebracht haben, also diesen Vorgang, Fehler wieder auszubauen und
mühsam sich durch Testen oder andere Sachen ranzutasten, wo könnte
denn der Fehler im Programm sein, der dazu führt, dass das Programm
leider nicht das tut, was ich will.
Und insofern, da das sonst eben ein mühsamer, manueller Prozess ist,
dieses Fehler suchen und Fehler ausbauen, ist man immer glücklich,
wenn natürlich man Fehler automatisch gemeldet bekommt,
Programmierfehler.
Und diese Typfehler sind, wie gesagt, Dinge, die man automatisch
gemeldet hat.
Und das ist natürlich eine sehr angenehme Sache.
So, um das zusammenzufassen, was wir jetzt in diesen letzten Folien
von heute hatten und aber auch in der Vorlesung letzter Woche, auf der
Typ -Ebene, Java kennt acht elementare Datentypen, die wir hatten,
Integer, Float, String und so weiter.
Klassen sind auch Datentypen, Entschuldigung, String ist kein
elementarer Datentyp, das ist eine Klasse schon.
Und mit jedem Datentyp sind bestimmte Operationen oder Methoden, die
man damit aufrufen kann, im Falle einer Klasse verbunden.
Variablen sind eigentlich nichts anderes als Namen für Speicherplätze.
Die Werte von den Variablen können verändert werden durch Zuweisungen,
wie wir das gesehen haben.
Und die Objektvariablen, also wo ein Objekt dahinter steht, das sind
eigentlich Referenzen auf die Speicheradresse, wo das eigentliche
Objekt liegt dann.
Und wenn ich habe einen speziellen Objektreferenzwert, das ist 0, das
heißt, hier ist noch kein Objekt vorhanden.
Das muss noch erst als erzeugt werden.
So, um Ihnen nochmal den Gesamtüberblick zu geben, was haben wir jetzt
gemacht?
Wir haben vor allem jetzt hier über primitive Datentypen und
Wertebereiche gesprochen.
In der späteren Folge werden wir auch über Konvertierung sprechen, das
kommt noch.
Und jetzt gehen wir gleich über zu dem Thema Verhalten, weil Sie sehen
hier, wenn ich eine Klasse habe, habe ich meine Datentypen, die
Attribute mit den Variablen.
Und das ist jeder Teil.
Und ich habe aber auch Verhaltensbeschreibungen.
Das sind meine sogenannten Methoden, also die Operationen, die ich
mache auf der Klasse.
Und dazu lernen wir jetzt in der nächsten Folge die
Kontrollstrukturen.
Wenn Sie beides haben, können Sie eben auch schon leichte, einfache
Programme schreiben, wie wir das ja auch auf den Übungsblättern
wollen.
Das ist auch der Grund, warum wir jetzt so in dieser Reihenfolge
vorgehen und jetzt nicht sofort noch vertiefen in Konvertierung oder
sowas, sondern wir wollen, dass Sie ja möglichst schnell auch ans
Programmieren kommen.
Deswegen reden wir jetzt mal über den Verhaltensteil.
Und zum Abschluss gerade, wenn Sie wissen wollen, wo kann man das
alles noch mal nachlesen, wie gesagt, hier in unserem Anfängerbuch
sind das die entsprechenden Kapitel, wie hier angegeben.
Also, kurz durchschnaufen.
Jetzt geht es weiter mit Kontrollstrukturen.
Wieder hier das Überblicksbild.
Genau an der Stelle sind wir jetzt.
Das, was ich Ihnen jetzt beibringe, hilft Ihnen, um Methoden zu
schreiben, um Verhalten zu definieren auf unseren Objekten.
Genau.
Lernziele sind, dass Sie erst mal verstehen, was der Unterschied
zwischen einem Ausdruck und einer Anweisung ist und dass Sie die
Kontrollflussanweisungen, die es in Java gibt, kennen.
Das sind if und switch und dass Sie auch die Schleifenkonstrukte
while, do while und for können und dass Sie die Wörter break und
continue kennen.
Mal schauen, wie weit wir heute kommen.
Vielleicht schaffen wir das heute nicht mehr.
So, wie so häufig wird jetzt hier in der Informatik mit dem Wort
Ausdruck etwas verwendet, was auch in der natürlichen Sprache
verwendet wird, aber halt etwas allgemeinere Bedeutung hat.
Ausdruck ist was sehr Spezielles in der Programmiersprache.
Mit einem Ausdruck meint man, das ist etwas, was man auswerten kann
und was einen Wert ergibt.
Und hier zum Beispiel, wenn man sagt, man also x ist gleich und dann
kann ein Ausdruck kommen, also etwas, was man auswerten kann und was
dann den Wert ergibt, den man hier dem x zuweist.
Ausdrücke begegnen uns auch als Argumente, als Parameter in Methoden
aufrufen.
Also wenn ich eine Funktion f von und dann kann hier ein Ausdruck
widerstehen oder auch bei diesem Wort return, was am Ende einer
Methode steht, wo man sagt, ich gebe einen Rückgabewert zurück und
dieser Rückgabewert ergibt sich durch die Auswertung dieses Ausdrucks.
So, am konkreten Beispiel, wenn ich zum Beispiel hinschreibe 3 plus y
in Klammern mal 4, ist das ein Ausdruck und zwar vom Typ Double in dem
Falle.
Durch die Komma Null wird klar, es sind Gleitkommazahlen und wenn ich
nicht das f dabei schreibe, ist Double gemeint.
Oder auch hier new Vector2D ist ein Ausdruck vom Typ Vector2D.
Warum?
Naja, das ist auch ein Konstruktoraufruf, ist ja auch evaluierbar.
Da kommt ein Ergebnis zurück, nämlich ein Objekt.
Das Objekt hat in dem Falle, wenn ich es nicht irgendwie anders drehe,
den Typ hier von dem, der auch beim Erzeugen wurde, Vector2D.
Und auch, das ist jetzt ein Spezialfall, x gleich gleich 2 ist auch
ein Ausdruck, weil das kann man evaluieren, nämlich auf einen
Wahrheitswert.
Entweder x ist x gleich 2 oder x ist halt nicht gleich 2.
Das hat wahr und falsch und dann erinnern sich Variablen, die die
Werte wahr und falsch speichern können, hatten wir Boolean genannt.
Und diese Ausdrücke, die den Typ Boolean haben, nennt man auch
Bedingungen.
Das brauchen wir nachher, um den Kontrollfluss zu steuern.
Deswegen führe ich es hier auch schon ein.
So, es gibt jetzt, ein bisschen verallgemeinert in Java, die folgende
Art, wie man zu Ausdrücken kommen kann.
Eine Variable selber wird evaluiert zu dem Wert, den sie besitzt.
Das ist also ein Ausdruck, eine Konstante genauso.
Dann ist ein Methodenaufruf auch ein Ausdruck.
Wenn ich einen Operator hier habe, plus mal durch Modulo, also einen
binären Operator, ist das auch zwei Ausdrücke mit dem binären Operator
zwischen, auch wieder ein Ausdruck.
Genauso ist ein Ausdruck mit einem unnäheren Operator, zum Beispiel
plus oder minus, auch wieder ein Ausdruck.
New hatten wir auch schon gesehen.
Das Ergebnis vom Konstruktoraufruf ist auch ein Ausdruck oder halt
eben sogar eine Zuweisung.
Wenn ich einer dieser Variable diesen Ausdruck zuweise, ist das Ganze
zusammen auch wieder ein Ausdruck.
Dann gibt es noch weitere Ausdrücke in Java, die vordefiniert sind in
der Sprache.
Einmal das Wort InstanceOf, das ist ein Boolean, was rauskommt, also
wahr oder falsch, und zwar prüft es nach, ob ein Objekt zu einer
Klasse gehört.
Das würde jetzt noch zu weit führen, aber manchmal an einigen Stellen
weiß man eben nicht mehr als Programmierer, welchen Typ hat
eigentlich, also zu welcher Klasse gehört ein Objekt, was ich habe,
und da kann man das damit prüfen und bekommt eben einen Wahrheitswert
zurück, ob zum Beispiel, trivialerweise, mein P, InstanceOfVector2D,
würde Tru rauskommen, so wie wir es definiert haben, aber wie gesagt,
müsste nicht, könnte auch mal anders kommen.
So, dann gibt es noch mal dieses komische Konstrukt mit diesem
Fragezeichen und Doppelpunkt, also die Underscores hier, also diese
Striche, die stehen immer für Dinge, wo man Ausdrücke einsetzt.
Und das ist im Prinzip sowas, wie man das in der Mathematik auch
kennt, eine Fallunterscheidung.
Ja, wenn Sie eine Formel definieren oder eine Funktion definieren und
sagen F ist gleich und dann, was weiß ich, 3, wenn A größer 0 ist und
4, wenn es gleich 0 ist oder so, also wo Sie eine Fallunterscheidung
definieren, also mit dieser großen geschweiften Klammer, das kann man
quasi auch als Ausdruck hinschreiben, indem Sie hier vor das
Fragezeichnen eine Bedingung, also einen boolischen Ausdruck machen,
der zu wahr oder falsch evaluiert.
Wenn er auf wahr evaluiert, wird der Ausdruck genommen, der hier steht
beim ersten Underscore und das ist, wenn man so will, das else der
andere.
Jetzt können Sie sagen, hey, das hat man doch schon mal irgendwie mit
if gehabt.
Ja, stimmt, aber hier kann man das quasi als Ausdruck nochmal
hinschreiben.
Und auch das wird in der späteren Vorlesung ausführlich behandelt,
wenn ich einen Typ umwandle.
Wenn ich also hier zum Beispiel eine Gleitkommazahl hinschreibe und
hier in Klammern hinschreibe int, dann soll diese Gleitkommazahl als
Ganzzahl ausgewertet werden und das ist auch wieder ein Ausdruck.
Hier sehen wir das mal als Beispiel.
P instance of Vektor 2D ist eben vom Typ Boolean, kann wahr oder
falsch sein, so wie wir P definiert haben in der letzten Vorlesung,
wer es war.
Dann mal ein Beispiel für diese Fragezeichengeschichte hier.
x gleich gleich 3, wenn x den Wert 3 hat, dann soll 5 zurückgegeben
werden oder als Ausdruck genutzt werden.
Ansonsten, wenn also x ungleich 3 ist, x plus 2.
Und das Ergebnis ist in dem Falle vom Typ Integer, weil 3 und 5 sind
eben vom Wert Integer.
Wenn sie jetzt hier 3,0 und 5,0 geschrieben haben, wäre es vom Typ
Double gewesen.
Dann in Klammern der Typ, wohin konvertiert werden soll.
Die 2,4 soll konvertiert werden in diesem Ausdruck hier in eine
Ganzzahl.
Das Ergebnis ist logischerweise eine Ganzzahl, in dem Fall also auch 2
heraus.
Und x gleich y ist selber nicht nur eine Zuweisung, es ist auch ein
Ausdruck und zwar vom selben Typ wie x.
x muss ja einen Typ haben als Variable und der Typ ist das hier.
Sieht man der Sache also nicht an, welchen es hat, aber es hat auch
einen Typ.
Gegenüber diesen Ausdrücken bestehen Java-Programme auch aus
Anweisungen.
Das sind, wenn man will, die zwei Sachen, mit denen man programmiert,
Ausdrücke und Anweisungen.
Und Anweisungen sind im Prinzip, wenn man so will, Sätze, die man in
der Programmiersprache hinschreibt, wo man sagt, bitte mach quasi das
folgende.
Veranlasst also in der Regel eine Zustandsänderung.
Es gibt eine Ausnahme, werden wir gleich sehen, aber in der Regel ist
es immer eine Zustandsänderung.
Ich kann Anweisungen hintereinander machen, dann schreibt man ein
Semikolon dazwischen und auch, dummerweise auch am Ende.
Dann ist es so, wenn ich eine Variable deklariere, also int x
schreibe, dann ist das auch eine Anweisung, könnte man jetzt ein
bisschen darüber streiten und sagen, da ändert sich aber kein Zustand.
Stimmt, da ändert sich kein Zustand, es wird einfach nur Speicherplatz
reserviert für ein x.
Das ist nicht wirklich eine Zustandsänderung Ihres Programms.
Deswegen, das ist, wenn man so will, ein bisschen die Ausnahme und man
könnte auch, wenn man super pingelig ist, Achtung, so ein Einstein
-Korinthen -Kacka- Kommentar, natürlich auch unterscheiden in
Deklarationen, Anweisungen und Ausdrücke.
Wird aber in Java nicht gemacht.
Die Deklarationen werden zu den Ausdrücken gepackt.
So, dann das Return, wo man sagt, ich möchte diesen Ausdruck
evaluieren und das Ergebnis einer Methode zurückgeben.
Das ist das Return.
Und dann haben wir hier noch ein paar weitere Beispiele von
Ausdrücken, die aber auch Anweisungen sein können.
Das sind die sogenannten Expression Statements, das sind also die, die
beides sein können.
Einen haben wir schon kennengelernt, die Zuweisung.
Ja, wenn ich x gleich y schreibe, ist das zum einen eine Anweisung.
x soll den Wert von y bekommen.
Das ist ja wohl eine Zustandsänderung, damit eine Anweisung.
Es ist aber, wie wir eben gehört haben, auch ein Ausdruck.
Nämlich das Ergebnis dieser Zuweisung ist das, was in x drin steht.
Und das kann auch weiterverwendet werden.
Zum Beispiel in so was.
x ist gleich y gleich z.
Hier nimmt man den Wert von z, setzt den y rein und diesen Wert setzt
man dann in x wieder rein.
Das geht.
Dann Methodenaufrufe.
Wenn ich eine Methode aufrufe, ist das eine Anweisung.
Aber es ist eben auch ein Ausdruck, wenn die Methode ein Ergebnis
zurückliefert und das Speicher ist.
Insofern ist das auch wieder beides.
Und auch der New Operator macht eine Zustandsänderung.
Er erzeugt nämlich ein Objekt.
Er belegt also quasi hier das p mit der Adresse.
Das ist also eine Zustandsänderung.
Damit ist es eine Anweisung.
Es ist aber eben auch ein Ausdruck, der evaluiert wird.
Insofern fällt das in die Kategorie der sogenannten Expression
Statements.
Also so Mischformen, die beides Ausdruck und Anweisung.
So und dann kommen noch zu den Anweisungen dazu.
Die Kontrollflussstatements, also die Kontrollflussanweisungen, die
wir eben heute vor allem behandelt werden.
Hier, das ist nochmal so ein Überblick mit dem Unterschied zwischen
Ausdruck und Anweisung.
Für die, denen das jetzt noch ein bisschen ungewohnt ist.
Ein Ausdruck hat immer einen Typ.
Eine Anweisung hat erst mal keine.
Es sei denn, es ist eben auch gleichzeitig ein Ausdruck.
Dann hat es natürlich wieder einen Typ.
Und im Prinzip ist der Ausdruck etwas.
Werte etwas aus.
Berechne einen Wert.
Und wohingegen eine Anweisung ist, mache etwas.
Und der Effekt von einem Ausdruck, von einer Ausdrucksauswertung ist
eben, dass ich den evaluierten Wert habe des Ausdrucks.
Sie erinnern sich, 3 plus 4 wäre ein Ausdruck.
Kann ich auswerten.
Oder ich ändere den Zustand von etwas.
Ich mache also eine Zuweisung oder belege eine Variable oder sowas.
Das ist dann eine Anweisung.
Und die Syntax ist tatsächlich, dass ein Ausdruck kein Semikolon ans
Ende richtet.
Die Anweisung hat immer ein Semikolon am Ende.
So.
Und jetzt sind Sie gefragt, um das Ganze ein bisschen zu reflektieren,
was sind also tatsächlich hier jetzt gültige Anweisungen.
Gucken Sie sich das erstmal in Ruhe an und besprechen es auch ruhig
mit Ihrem Nachbarn.
Wir gehen das dann gleich durch.
Was sind gültige Java- Anweisungen hier.
So.
Dann versuchen wir das jetzt mal gemeinsam zu lösen.
Wer denkt, dass eins, also die erste Zeile hier oben, eine gültige
Anweisung ist.
Wer sagt, das ist eine gültige Java- Anweisung?
Ein paar Unerschrockene.
In der Tat, es ist eine gültige Java- Anweisung.
Warum?
Zum einen wird mit new etwas erzeugt und zugewiesen.
Das ist eine Anweisung.
Das ist eine Zustandsänderung.
Und mit dem Aufruf shift hier wird wahrscheinlich auch noch eine
Methode ausgeführt, bei diesem Objekt, was hier erzeugt wurde.
Es ist aber kein super guter Stil, weil man hier im Prinzip mehrere
Dinge miteinander mischt.
Also die Objekterzeugung sofort mit einem Aufruf von einer Methode ist
kein guter Stil.
Das haben Sie wahrscheinlich auch deswegen aus guten Gründen noch
nicht gesehen und deswegen so etwas gezögert zu sagen, das ist eine
gültige Java- Anweisung.
Und Sie sollten es vielleicht auch nicht hinschreiben, aber es geht.
Ja bitte.
Der Datentyp, also p kann ja vorher mal deklariert werden und sein mit
null.
Das stört ja nicht an der Stelle.
Und der Datentyp ergibt sich, also der dynamische Datentyp ergibt sich
dann hier rüber.
So, beim Zweiten.
Wer denkt, das ist eine gültige Java- Anweisung?
Zwei.
Hier int, Typumwandlung, fu, x.
Das sind schon einige.
Wer denkt, dass es keine ist?
Auch ein paar.
Es ist etwas durcheinander.
Die meisten wissen es nicht so genau.
Ist in der Tat ein bisschen fies, gebe ich zu.
Also, um die Katze aus dem Sack zu lassen, fu, x alleine wäre
tatsächlich eine Anweisung.
Das ist ein Methodenaufruf, das ist eine Anweisung.
Und wenn fu etwas zurückgibt, ja, dann muss das nicht unbedingt
irgendwo zugewiesen werden.
Also man kann auch Methoden aufrufen und den Rückgabewert ignorieren.
Das geht.
Aber das, was hier steht, ist in der Tat keine gültige Java-
Anweisung.
Warum ist es keine?
Weil hier diese Typkonversion steht.
Hier möchte man das Ergebnis von fu konvertieren in einen Int, aber
man weist es nichts zu.
Das ist ja kein x ist gleich da.
Damit ist es einfach keine gültige Java- Anweisung.
Der Compiler würde hier einen Fehler melden.
So, kommen wir zum dritten.
x gleich y gleich z.
Wer sagt, das ist eine gültige Anweisung?
Wer sagt, es ist keine gültige Anweisung?
Auch ein paar.
Es ist tatsächlich eine gültige Anweisung.
Sie erinnern sich daran, dass y gleich z ja nicht nur eine Anweisung
ist.
Es ist auch ein Ausdruck.
Es hat nachher das Ergebnis.
In y steht ja nachher, wenn man das Ganze als Ausdruck betrachtet,
also stellen Sie sich mal hier für einen Augenblick eine Klammer vor,
y gleich z geklammert, dann ist hier der Wert von z das Ergebnis
dieses Ausdrucks und der wird in x zugewiesen.
Und das geht.
Zum Hintergrund stellen Sie sich das einfach vor, ausnahmsweise mal in
dem Falle, dasselbe gilt auch für die, für noch ein paar andere
Operatoren, aber in dem Falle gilt es auch, das wird im Prinzip von
rechts nach links ausgewertet.
Normalerweise würden wir den Text als normale Europäer von links nach
rechts lesen, aber die Auswertung findet hier tatsächlich von rechts
nach links statt.
Also man wertet erst y gleich z aus.
Das ist eine Anweisung, man führt sie aus, aber es ist eben auch
gleichzeitig ein Ausdruck.
Man hat den Wert von z jetzt hier und der wird dann x noch zugewiesen
und das heißt also erst wird das Gleichheitszeichen ausgewertet und
dann wird das Gleichheitszeichen ausgewertet.
Es ist also tatsächlich auch eine gültige Java-Anweisung.
Kommen wir zum letzten i++ in Klammern noch mal plus plus.
Ist das eine gültige Java- Anweisung?
Ja.
Wer sagt, das ist keine gültige Java-Anweisung?
Es ist in der Tat keine gültige Java-Anweisung.
Gebe ich zu, auch ein bisschen trickreich, weil man könnte ja meinen,
es ist doch schon klar, was damit gemeint ist, i soll um den Wert 2
erhöht werden, sieht man doch.
Geht aber nicht.
Der plus plus Operator macht ja auch eine Zuweisung.
Er erhöht ja nicht nur was, er macht ja auch eine Zuweisung.
i++ heißt ja nichts anderes wie i ist gleich i plus 1 und deswegen
braucht der plus plus Operator eine Variable, nämlich etwas, wo er was
zuweisen kann.
Hier, dieser plus plus Operator hat aber keine Variable, er hat einen
Ausdruck hier.
i++ ist ja auch ein Ausdruck und einem Ausdruck kann man nichts
zuweisen.
Jetzt können Sie sagen, das ist aber spitzfindig, es ist doch klar,
was gemeint ist, es ist doch klar, dass das i hier steht und wenn doch
hier das eigentlich dasselbe ist wie i ist gleich i plus 1 und eine
Zuweisung ist ja auch gleichzeitig wieder ein Ausdruck und so weiter,
dann müsste das doch gehen.
Aber wie gesagt, der plus plus Operator kann nicht auf Ausdrücken
arbeiten, der kann nur auf Variablen arbeiten, weil er nur einer
Variable etwas zuweisen muss.
Sie können ja auch, nur damit Sie das verstehen, auch nicht einen
Ausdruck hinschreiben und dann ist gleich 5.
Sie können nicht einem Ausdruck etwas zuweisen, das geht einfach
nicht.
Weil man einem Ausdruck nicht zuweisen kann, geht das hier nicht.
Und das ist eben auch keine gültige Java-Anweisung.
Jetzt haben wir hier noch ein anderes Quiz und zwar, wo es um
Gleichheit geht und zwar legen wir uns drei Variablen an x, y und z.
Z bekommt den Wert 42 und jetzt haben wir drei Ausgaben.
Einmal System.out.println x gleich y gleich z über die Frage, was
kommt da raus.
Dann haben wir eine Ausgabe x ist gleich gleich z, das heißt also, das
ist ein Gleichheitsabprüfung, ob x und z den selben Wert haben.
Dann wird x auf den Wert 42 gesetzt und dann mache ich wieder
dieselben Test, x gleich gleich z.
Und jetzt ist die Frage an Sie.
Wir machen das jetzt erstmal nur für das Programm, nicht für das
Programm.
Und jetzt gucken wir uns erstmal nur das eine Programm hier an.
Was kommt bei der ersten Ausgabe raus, welche der Aussagen a, b, c, d
ist wahr.
Diese Aussage sagt,
also die erste Ausgabe sagt true, die erste Ausgabe sagt 42, die
zweite Ausgabe sagt true und die dritte Ausgabe sagt true.
Und jetzt erstmal bitte nur für das linke Programm hier
Was kommt raus, was ist wahr?
a, b, c oder d.
Also wer sagt a ist wahr?
Keiner, das ist gut.
Wer sagt b?
Ja, ganz genau, das ist auch korrekt.
Ja, 42 kommt raus.
Warum nochmal?
Ich erkläre es.
z hat den Wert 42, z wird y zugewiesen, das Ganze ist ein Ausdruck,
die 42, die das Ergebnis dieses Ausdrucks y gleich z ist, wird dem x
zugewiesen und das wird auch nochmal als Ausdruck gesehen.
Ist ja auch wieder eine Zuweisung, ist ja ein Ausdruck.
Also kommt 42 raus, genau.
Jetzt fragt man sich, ist jetzt x gleich gleich z, hat es den selben
Wert?
Das ist jetzt hier die Frage c.
Ist die Aussage c korrekt, also das true rauskommt?
Wer sagt das, dass c korrekt ist?
Wer sagt, dass c inkorrekt ist?
Auch ein paar.
Es kommt aber tatsächlich true raus, weil x hat den Wert 42, y hat den
Wert 42 und auch wenn das vielleicht ein bisschen eigenartig ist,
tatsächlich druckt dann Java auch wirklich true als Ergebnis aus.
Hier als das Ergebnis dieser bool'schen Evaluation.
So und die dritte, also d, die dritte Ausgabe dieses Programms ist
true.
Wer sagt, dass es korrekt ist?
Wer sagt, dass es inkorrekt ist?
Hoffentlich keiner mehr.
Genau.
Das ist natürlich logischerweise auch wahr, weil es hat vorher auch
den Wert 42 gehabt.
Ich setze es doch mal auf 42.
Natürlich ändert sich nichts.
War zweimal 42.
So, jetzt wird es aber spannend.
Achtung, Achtung, bitte anschnallen.
Wir gehen nämlich jetzt zu dem anderen Programm über, wo wir sagen
hier, eigentlich sieht es genau so aus, nur wir sagen jetzt hier in
dem Fall Integer mit großem I.
Was heißt das?
Das heißt, jetzt habe ich kein BasicDataType mehr vor mir, Int,
sondern ich habe eine Klasse Integer.
Und ich erzeuge erst mal drei Objektreferenzen x, y und z.
Aber Sie wissen, Objektreferenz alleine hilft mir nichts.
Ich muss auch mit null tatsächlich dann ein neues Objekt erzeugen.
Also erzeuge ich jetzt hier ein Integerobjekt mit dem Wert 42.
Und das soll z sein.
Sie sehen irgendwie schon die Analogie zu dem Programm hier.
Und jetzt ist die spannende Frage, was kommt dann an der Stelle
tatsächlich raus?
x gleich y gleich z.
Kommt da 42 raus oder erst mal kommt da true raus.
Wer sagt, da kommt true raus?
Hoffentlich keiner.
Wenn keiner ist, kommt kein true raus.
Das ist überhaupt kein Vergleich.
So.
Wer sagt, da kommt 42 raus?
Ja.
Wer sagt, da kommt nicht 42 raus?
Auch ein paar, genau.
Also der Gag ist, da kommt tatsächlich 42 raus.
Das ist aber vielleicht nicht ganz logisch und man muss es erklären.
Deswegen war es durchaus nachvollziehbar, dass es nicht ist.
Also, was eigentlich hier gemacht wird, hier werden die Objektreferenz
von z, die ja hier initialisiert wurde, also hier steht irgendeine
Speicheradresse, 718 oder so, irgendeine, die wird dem y zugewiesen,
wird dem x zugewiesen.
Eigentlich würde man jetzt erwarten, dass mit Printline auch die
Speicheradresse ausgegeben wäre.
Das wäre konsequent.
Da aber in den meisten Fällen nicht das ist, was man will, wird
automatisch eine Stringkonversion aufgerufen, für die Einstein
-Experten hier.
Und die dann nämlich sagt, das Ganze als Zeichenkette ist dann doch
wieder die 42.
Also wird 42 ausgegeben.
Das liegt daran, weil man in Java die Speicheradressen eigentlich
nicht als etwas rausgeben will, weil man sagt, das ist eigentlich für
den Benutzer als Ausgabe, macht das keinen Sinn.
Die CC++ Programmierer wissen, dass ihre Welt da anders ist, aber in
Java ist das so.
Tatsächlich kommt hier 42 raus, aber wie gesagt, das ist nicht
pauschal logisch, man muss das Zusatzwissen haben, dass wenn jetzt ein
Objekt Integer in Printline ausgegeben wird, eben automatisch
konvertiert wird, was ist eigentlich der Wert davon und nicht, was ist
die Speicheradresse, was logisch wäre.
So, dann schauen wir uns die nächste Sache an.
System.out.println x== z.
Ist das true oder ist das nicht true?
Also im Prinzip die Frage c.
Stimmt die Aussage c?
Wer sagt ja?
Wer sagt nein?
Ein paar wenige.
Es ist in der Tat wahr.
Und zwar deswegen, ich habe ein Integerobjekt erzeugt, eine
Objektidentität ist da, ich habe drei Objektreferenzen x, y und z, die
zeigen aber nach dieser Zeile alle auf dieselbe Objektidentität.
Und das heißt also, wenn ich jetzt gleich-gleich mache an der Stelle,
ist es wirklich dieselbe Identität.
Es gibt nur eine Objektidentität, insofern ist es auch an beiden
Seiten die gleiche.
Ich habe nur eine.
Und deswegen kommt da true raus.
Jetzt erzeuge ich eine weitere Objektidentität.
newInteger 42 steht hier.
Ich habe auf einmal zwei Objektidentitäten.
Eine, die von y und z nach wie vor drauf zeigen, das war die von hier
oben.
Und eine x.
Und jetzt mache ich wieder x gleich-gleich z.
Und das ist jetzt die Frage nach der letzten Ausgabe.
Ist das true?
Wer sagt, dass die Aussage d stimmt?
Wenige.
Wer sagt, dass sie nicht stimmt?
Das ist die Mehrheit, das stimmt auch.
Interessanterweise im Gegensatz zu diesem Programm hier, wo natürlich
auch true rauskam, ist es jetzt nicht mehr true.
Und warum?
Gleich-gleich überprüft er hier nicht auf Werte, sondern auf
Objektidentitäten.
Und ich habe zwei verschiedene Objektidentitäten.
Zwar haben beide Objekte als Attributwert 42.
Ja, stimmt.
Aber Sie erinnern sich, was ich gesagt habe?
Objektidentitäten, das ist quasi wie Speicheradressen.
Wenn ich zwei verschiedene Speicheradressen habe, vollkommen egal, ob
an diesen beiden Speicheradressen dasselbe liegt oder nicht als Wert,
wenn es zwei Speicheradressen sind, ist es erst mal zwei verschiedene
Objektidentitäten.
Und deswegen kommt tatsächlich hier nicht true, sondern false raus.
Ich habe zwei verschiedene Objektidentitäten.
Wenn ich die auf Gleichheit überprüfe, sagt natürlich das Programm,
das sind nicht zwei selben Objekte.
Hier hat es das noch gesagt, weil auf beiden Seiten dieselbe
Objektidentität steht.
Und das ist insofern, diese Übung soll jetzt nicht nur eine Kniffelei
sein, sondern Sie sollen dabei lernen, Objektidentität ist nicht
dasselbe wie derselbe Wert in einer Variable.
Das sind zwei verschiedene Sachen.
Wenn ich derselben Wert in einer Variable habe, zweimal derselbe Wert,
alles gut.
Hier haben x und z zwei unterschiedliche Werte.
Nämlich einmal den von der einen Objektidentität und einmal den von
der anderen Objektidentität.
Das sind zwei verschiedene Speicheradressen.
Also sagt der Vergleich, nein.
Auch wenn beide Objekte 42 als Wert speichern.
Okay.
Dann machen wir jetzt einen kleinen, etwas entspannten Einschub.
Und zwar, weil Sie das nämlich auch für das Übungsblatt irgendwann mal
brauchen.
Und zwar, wie kann ich Eingaben über die Konsole machen?
Wir haben ja bisher nur System.out.printline gemacht.
Das heißt also, wir haben Dinge ausgegeben.
Und Eingaben über die Konsole sind vielleicht ein bisschen uncool,
weil man das eigentlich ein bisschen anders macht.
Eingaben über ein anständiges Userinterface.
Wir hatten in der allerersten Vorlesung von mir so ein bisschen
getrickst und das als Kommandozeilenparameter gemacht.
Aber es kann ja sein, dass Sie nicht nur beim Start eines Programmes
Werte eingeben möchten.
Dann kann man das, wie gesagt, über die Kommandozeile machen, wie ich
das beim ersten Mal gemacht habe.
Sondern jetzt kann es ja sein, dass Sie mittendrin, während das
Programm abläuft, eine Eingabe brauchen.
Und wie gesagt, normalerweise würde man das vielleicht nicht über die
Konsole machen, sondern über ein tolles fancy Graphical User
Interface.
Da das aber zu programmieren auch ein bisschen aufwendig ist, und wir
ja hier in der Anfängervorlesung sind, machen wir das jetzt mal doch
über die Konsole.
Und zwar geht das so.
Es gibt hier eine Klasse Scanner.
Die brauchen Sie nicht schreiben, die gibt es, die kann man einbinden.
Und jetzt kann ich zum Beispiel mir ein Beispielprogramm schreiben,
was erstmal sagt, gib etwas ein, Doppelpunkt, und dann wird ein
Scanner Objekt erzeugt mit New Scanner.
Und da schreiben Sie einfach System In hin.
System In ist quasi Java-ish für die Konsole.
Und dieses Scanner Objekt hat dann eine Routine, die Sie aufrufen
können.
Nextline ohne Parameter, deswegen Klammer auf, Klammer zu.
Und Nextline liest die nächste eingegebene Zeile der Konsole ein.
Zeile heißt, bis jemand Return drückt.
Und das wird dann gespeichert in der Variable Input, die vom Typ
String ist, weil klar, was wird eingegeben?
Irgendeine Zeichenkette wird eingegeben.
Zeichenkette ist für den Typ String.
Macht also Sinn, das hier in einer Variable Input vom Typ String zu
speichern.
Und dann kann man das auch tatsächlich ausgeben, indem man sagt System
Out Printline, dann als fester String.
Du hast das folgende eingegeben, Doppelpunkt, Leerzeichen,
Anführungszeichen oben, Plus.
Und jetzt der String, also den Input, was man eingegeben hat.
Wie gesagt, das ist eine vorgefertigte Klasse.
Und Sie erstellen das Objekt mit New Scanner System In.
Das können Sie einfach mal fix übernehmen.
Und wie gesagt, System In, weil Sie die Konsole meinen.
Und jetzt können Sie eine Zeile einlesen mit Scanner Nextline.
Nur damit Sie wissen, wie sowas geht.
Man kann auch statt Nextline Nextint aufrufen.
Das erwartet dann aber, dass auch der User tatsächlich ein Integer
eingibt, nicht irgendein Blödsinn.
Also nicht irgendwas mit Buchstaben.
Wie das geht, falls ich jetzt Nextint aufrufe, aber dummerweise der
Benutzer keinen Integer eingibt, also nicht 27, sondern 3abf.
Das kommt mal in der späteren Vorlesung dran.
Bei Nextline sind Sie quasi auf der sicheren Seite.
Da kann man zwar auch noch Unfug mitmachen, aber normal vergibt man
halt irgendwas ein und drückt Enter.
So, das war so das kleine Einschub.
Das brauchen Sie nämlich fürs Übungsblatt.
So, und dann kommen wir zum eigentlichen Thema dieser Vorlesung.
Und zwar Kontrollflussanweisungen.
Kontrollflussanweisungen sind dafür da, um zu steuern, wie man durch
das Programm läuft.
Sie erinnern sich ganz grob.
Erste Vorlesung, die ich gegeben hatte.
Achtung, ein bisschen leiser bitte.
Sie erinnern sich, erste Vorlesung.
Da hatte ich gesagt, erste Näherung, irgendwo gibt es ein oben in
einem Programm.
Das sind die ersten Anweisungen und die läuft man von oben nach unten
durch.
Das stimmt.
Aber es kann ja sein, dass man diesen linearen, festen Ablauf
eigentlich nicht immer haben will.
Dass man nicht immer will, dass das Programm immer dasselbe macht,
immer dieselben Anweisungen durchführen will.
Sondern das Programm muss ja reagieren auf verschiedene Eingaben,
vielleicht verschiedene Werte, die es berechnet, wie man
unterschiedlich behand haben.
Also sagt man vielleicht ist nicht immer derselbe Kontrollfluss.
Kontrollfluss ist quasi der Fluss, in welcher Reihenfolge werden die
Anweisungen durch abgearbeitet.
Das ist das Wort Kontrollfluss.
Sondern man möchte vielleicht diesen Kontrollfluss steuern, der nicht
immer automatisch von oben nach unten abläuft, sondern dass er
verzweigen kann.
Das ist dann eine If-Anweisung, eine Fallunterscheidung.
Und wenn diese Fallunterscheidung nicht nur zwei Ausgänge haben soll,
so oder so, auswärts einer Boolchen-Variable, sondern mehrere Ausgänge
hat, dann spricht man von einer Switch-Anweisung.
Oder ich möchte auch bestimmte Anweisungen wiederholt ausführen, also
iterieren.
Dann spricht man von sogenannten Schleifen, einfach weil man sich das
vorstellt, dass das halt mehrfach rumläuft, sagt man Schleife.
Davon gibt es mehrere Geschmacksrichtungen.
Vanille, du, ähm, Quatsch.
Weil, du, weil und vor.
Dann gibt es noch zwei spezielle Wörter, die auch den Kontrollfluss
steuern.
Und die machen insbesondere in Schleifen nämlich Sinn, dass man
nämlich diese Schleife entweder nochmal wiederholt, bevor man auch am
Ende war, der Schleife, das ist das Continue-Wort, oder dass man die
Schleife verlässt an einer bestimmten Stelle innerhalb des
Schleifenrumpfes, das ist die Break-Anweisung, das werden Sie dann
auch kennenlernen.
Fangen wir erstmal mit dem Einfachsten an.
Das ist das If.
Eine If ist eine Fallunterscheidung.
Wenn das folgende gilt, also ein bulscher Ausdruck, eine Bedingung
wird ausgewertet, dann mache ich etwas Besonderes und ansonsten mache
ich vielleicht etwas anderes oder gar nichts.
Das Ganze sieht also immer so aus, entweder If, eine Bedingung, und
wenn die gilt, also auf To, dieser Ausdruck auf To evaluiert, dann
führe ich diese Anweisungen aus.
Es können also durchaus auch mehrere Anweisungen sein.
Oder die andere Variante, If-Bedingung, Anweisungen, Else,
Anweisungen.
Das heißt, wenn diese Bedingung gilt, also auf To evaluieren, werden
diese Anweisungen ausgeführt.
Wenn diese Bedingung zu False evaluiert, werden diese Anweisungen,
also Anweisungen 2, ausgeführt.
Ganz kleines Beispiel.
Sie lesen jetzt hier einen Integer 1 von der Konsole.
Der wird in A gespeichert, dann wird ein zweiter nochmal eingelesen,
auch über die Konsole.
Das ist dann in B gespeichert.
Und jetzt wollen Sie wissen, was ist der Größere davon?
Und dann machen Sie einfach If, wohlsche Bedingung, A größer gleich B.
Dann sagt man, das Maximum ist wohl offenbar A, und sonst ist es B.
Und hier sehen Sie ja einfach diese Unterscheidung.
Es gibt einen logischen Ausdruck, der auf To oder False evaluiert, und
entweder wird der Teil gemacht, dann sagt man auch so im
Umgangsdeutsch, das ist der If-Teil, und das ist der Else-Teil.
Das sind die Else-Anweisungen.
Wenn man sich das grafisch vorstellen will, kann man sagen, ich habe
hier den Start, das Einlesen habe ich jetzt hier nicht angegeben, und
dann nimmt man diese Raute und sagt an der Stelle jetzt hier, da
verzweigt es.
Einmal kann dieser Ausdruck zu To evaluiert werden, dann wird der If
-Teil ausgeführt, ansonsten wird der Else-Teil ausgeführt, dann bin
ich fertig.
Wenn man sich das mal anschaut, wenn ich jetzt das quasi so synchron
habe, die Grafik und meinen Programm-Code, hier wird also das
ausgewertet, entweder wird es auf To ausgewertet, wenn ich jetzt diese
Werte hier habe, dann nehme ich jetzt diesen Teil, dann bin ich
fertig.
Und wenn ich die Werte anders gesetzt habe, dann wird der Else-Teil
ausgeführt.
So, wie ich gesagt hatte, manchmal will man aber nicht nur zwei
verschiedene Ergebnisse haben, sondern man schachtelt dann die Ifs und
sagt zum Beispiel, naja, wenn ich jetzt zum Beispiel Punkte, die ich
in einer Klausur oder einer Aufgabe umrechne, will in Notenwerte, dann
habe ich natürlich nicht nur zwei Ergebnisse, sondern ich habe ja
mehrere Noten an der Uni klassischerweise 1 bis 5, die 6 gibt es nicht
an der Uni.
Ich muss jetzt also hier meine Punkte umrechnen in die Note und dann
könnte man natürlich sich das vorstellen, dass man das mit einer
vielen If-Abfragen macht, indem man sagt, wenn die Punkte größer
gleich 87 sind, zum Beispiel, dann gibt das eine 1, wenn die Punkte
dann ansonsten, also sie sind kleiner als 87 und ansonsten, wenn sie
aber noch größer als 75 sind, gibt es eine 2.
Wenn das auch falsch war, also ich den Else-Teil mache, also sprich
ich bin kleiner 75, aber immer noch größer als 63, dann gibt es die 3.
Und wenn auch das nicht gilt, also sprich ich weniger als 63 Punkte
habe, aber noch mehr als 51 habe ich noch bestanden und ansonsten bin
ich durchgefallen.
So.
Und das sieht man in Programmtexten auch relativ häufig, diese
geschachtelten Ifs.
Sie würden jetzt vielleicht sagen, das ist aber eine komische
Einrückungsregelung, eigentlich müsste das doch, sage ich mal, diese
immer weitere Verschachtelungsebene sichtbar werden.
Stimmt.
Aber trotzdem schreibt man das manchmal auch so hin, auch wenn das,
wie gesagt, nicht die Verschachtelungsebene hat, aber weil man doch
relativ gut das Programm auf die Art und Weise lesen kann.
So.
Es ist vollkommen klar, Sie machen ja hier immer die Annahme, wenn Sie
hier größer gleich 75 sind, dass das negativ evaluiert wurde, weil Sie
hier im ELS-Teil sind.
Das heißt, die Reihenfolge, mit der Sie das hinschreiben, ist wichtig.
Sehen wir mal an, Sie sind ein sehr guter Student, Sie haben 90
Punkte.
Und ich habe jetzt dummerweise gemacht, das Ganze verdreht und habe
gesagt, 90, ja klar, das ist auch größer als 51.
Schwupps, Sie haben eine 4.
Falsch, ja.
Also das heißt, natürlich muss ich hier schon auf die Reihenfolge
achten.
Das wird manchmal nicht so deutlich durch diese Art der Formatierung.
Man sieht es dann ein bisschen besser, wenn man sich das grafisch
anschaut, dass man merkt, es verschachtelt ja eigentlich immer tiefer
logisch rein.
Weil ich immer das nächste If auswerte in der logischen Abhängigkeit
von dem If davor.
Und das sieht man hier an dieser Grafik ein bisschen deutlicher, als
ich das eben im Programmtext gesehen habe.
Und hier sagen Sie also, okay, ich starte hier, ich evaluiere das
erste If, wenn das negativ evaluiert ist, dann evaluiere ich das
zweite und so weiter.
Und da wird deutlicher, was ich eben nur so auf der Tonspur gesagt
habe, dass ich natürlich immer weiß, wenn ich hier zum Beispiel jetzt
die 2 gebe, dass das auch offenbar kein 1er-Kandidat war, weil eben
das hier negativ war, sprich, er hatte auch weniger oder sie hatte
auch weniger als 87.
Es ist die Frage an Sie, dass Sie ein leicht modifiziertes Programm
von dem, was wir eben gesehen haben, schreiben sollen.
Und zwar, dass 2-Integer-Eingaben A und B ausgibt und prüft, ob A
größer, kleiner oder gleich B ist.
Also das ist das, was Sie quasi als gegeben annehmen können hier.
Und jetzt, wie gesagt, nehmen Sie Ihren Papiercomputer und
Kommilitone, Kommilitonin und überlegen sich gemeinsam, wie sehr ein
Programm aus, was jetzt für die 2-Integer-Eingaben A und B ausgibt, ob
A größer ist, ob A kleiner ist oder ob A gleich B ist.
Können Sie ein paar Minuten machen und dann schauen wir uns gemeinsam
eine mögliche Lösung an.
So, dann wollen wir uns gemeinsam mal eine mögliche Lösung anschauen.
Ich bitte zu entschuldigen, für diejenigen von Ihnen, die das schon
programmieren können, dass das irgendwie eine ziemlich einfache
Aufgabe, aber bitte haben Sie Verständnis für die Leute, die ja eben
noch neu sind im Programmieren.
Die müssen hier vielleicht schon nochmal ein bisschen überlegen, wie
man das sinnvoll macht.
Aber ich habe auch noch ein paar Einsteine vor Ihnen hier, die ein
bisschen anspruchsvoller sind.
Also auch Sie werden noch auf Ihre Kosten kommen.
So, das ist jetzt eine Möglichkeit, wie man das macht.
Und zwar, dass man erstmal prüft, ist A größer als B?
Und dann gibt man aus System.out.println A ist größer als B.
Im else-Teil kann es ja jetzt nur noch 2 Fälle geben.
Entweder A und B sind gleich groß oder B ist echt kleiner.
Und genau das prüfe ich jetzt hier ab.
Nämlich soll ich sagen, A ist kleiner als B.
Und else, der letzte verbleibende Teil, und Sie müssen daran bedenken,
auch wenn das durch die Einrückung nicht deutlich wird, dieser else
-Teil wird nur ausgeführt, wenn auch das hier negativ ausgewertet
wurde.
Das heißt, das Einzige ist, was noch möglich ist, es ist nicht größer,
es ist nicht kleiner, also ist es gleich.
Das ist aber eine mögliche Lösung nur.
Sie können das natürlich auch anders lösen, Sie können auch die
Bedingungen in anderen Reihenfolgen hinschreiben, aber das ist eine
denkbare Lösung hier.
Achtung, ein bisschen leiser jetzt wieder.
Wir sind jetzt wieder im normalen Vorlesungsbetrieb drin.
So ein bisschen der Profi-Tipp ist natürlich, dass man idealerweise,
wenn man schlau ist, die häufigen Fälle, wenn man darüber eine Annahme
treffen kann, natürlich oben hinschreibt.
Weil dann, wenn man das einmal evaluiert hat, dieses eine if evaluiert
hat und damit die meisten Fälle schon abgehandelt hat, dann kommt man
in die Auswertung der unteren Teile gar nicht mehr rein.
Was natürlich ein bisschen Performance spart.
Manchmal kann man das nicht, dass man keine Ahnung hat, wie jetzt hier
in dem Beispiel, keine Ahnung ob A oder B größer ist oder was häufiger
ist.
Das ist ziemlich egal.
Also hier wäre es egal gewesen, aber wie gesagt, manchmal kann man
eben schon als Programmierer Annahme machen, mit welcher
Wahrscheinlichkeit wird überhaupt ein if positiv ausgewertet.
Und dann ist es natürlich sinnvoll, dass man das nach oben schreibt.
Den häufigen Fall, dass die anderen ifs gar nicht mehr angepackt
werden müssen, kostet ja alles Zeit letztendlich.
So, jetzt gibt es tatsächlich eine, wenn man so will, Sache, die sieht
so ähnlich aus, wie das, was wir eben gesehen haben.
Viele verschachtelte ifs, die man eben macht, um eine Sache
auszurechnen, die vielleicht mehrere Ausgänge hat, wie bei der
Notenvergabe, wo es ja nicht nur Bestanden, Nichtbestanden gibt,
sondern eine ganze Palette von Noten.
Und dafür gibt es auch in Java vordefiniert das sogenannte Switch
Statement.
Und in dem Switch, das kann man auf eine
Variable machen, die man auswerten kann auf Werte.
Und in dem Fall nehmen wir jetzt mal hier und haben uns vorher
festgelegt als Klassenkonstanten Befehle, die zum Beispiel über ein
User Interface eingegeben werden können und die dann in einer Variable
Command vom Typ Character stehen.
Und dann kann man sagen, wenn dieses Command z.B.
new war, dann z.B.
create new file, dann macht man das, was der Benutzer will, wenn er
auf new geht, auf neu.
Oder es gab eben open, dann geht man auf open file oder save save file
oder quit exit program.
Und wenn man all das gemacht hat und es noch nicht stattgefunden hat,
dann sagt man halt, offenbar wurde hier ein unbekanntes Kommando, was
ich nicht behandelt habe, eingegeben.
So, was man jetzt hier sieht, ist erstens mal das ist quasi eine
Evaluation von einem Ausdruck, der verschiedene Werte haben kann und
auf diese verschiedenen Werte evaluieren Sie und schauen, was kommt
hier raus.
Und das ist jetzt mal hier in diesem Beispiel diese vier new, open,
save, quit, aber das muss natürlich nicht so sein.
Sie können auch im Prinzip auf andere Werte evaluieren.
Sie können auch ein Integer auf feste Werte evaluieren.
Das andere, was man sieht, ist hier steht ein Break jedes Mal.
Das Wort Break hat man noch nicht gehabt, deswegen erkläre ich Ihnen
gerade, was das macht.
Break sorgt dafür, dass wenn dieser Case new gemacht wurde und dieses
new File aufgerufen wurde, dann kommt man als nächstes Kommando Break.
Break ist eine Anweisung und das heißt, hier verlasse dieses Switch
Statement, springe also hier hin.
Und das ist, wie Sie sehen, notwendig, weil sonst würde, und das ist
ein bisschen unlogisch, der nächste Case automatisch auf true
evaluiert und man würde danach Open File machen.
Und jetzt würden Sie sagen, hä?
Was soll das denn?
Was ist denn das für ein Quatsch?
Hier war doch eindeutig das Kommando new.
Warum wird dann das da, was eindeutig nicht der Fall ist, auch auf
true evaluiert?
Und die Antwort ist, weil es für wahrscheinlich irgendwie 0,3 Promille
der Fälle mal sinnvoll ist, sowas zu machen.
Ich erzähle Ihnen nachher auch einscheinmäßig, wo das der Fall ist.
Aber in den allermeisten Fällen können Sie sich das einfach als Petter
angewöhnen.
Sie wollen hier immer ein Break haben.
Sie müssen sich einfach angewöhnen, quasi reflexartig, wenn Sie das
Case -Ding hinschreiben, schon automatisch irgendwo mal ein Break
einzufügen, weil in der Regel wollen Sie das nicht, dass Sie hier,
wenn das new positiv evaluiert wurde, auch noch das da und das da und
das da und das da alles durchläuft.
Das nennt man manchmal auch im Umgangsdeutsch das Durchsacken durch
ein Switch-Statement.
Das wollen Sie in der Regel nicht und deswegen schreiben Sie das Break
hin.
Jetzt können Sie sagen, was ist das für ein beklopptes Sprachdesign?
Und das kommt letzten Endes von der Sprache C, wo es auch schon so
ist.
So historisch gesehen, diejenigen, die auch schon mal mit C
programmiert haben, wissen vielleicht, C ist ja deutlich älter.
C ist ja ungefähr so entstanden.
Die zwei Erfinderkönig von Ritchie saßen in der Kneipe und hatten
möglicherweise schon was getrunken.
Und dann sagte der eine, kritzelte irgendwie wirres Zeug auf den
Bierdeckel und sagte, betten daraus kannst du keinen Compiler
schreiben, der andere doch.
Und so ist C entstanden.
Und auf alle Fälle in C hat man mal diese Entscheidung getroffen, weil
das Schreiben von sogenannten Parsern, also den Programmen, die andere
Programme einlesen und zerlegen in den sogenannten abstrakten
Syntaxbaum, die brauchen manchmal dieses Durchsacken durch das Switch
-Statement.
Da macht das manchmal Sinn.
Und deswegen hatte man in C gesagt, oh, wenn man das nicht will, in
Klammern, ich schreibe jetzt gerade eben keinen Parser, dann muss man
halt Break reinschreiben.
Was natürlich eine ziemlich bescheuerte Entscheidung ist, weil, wie
gesagt, 99,999% aller Programme und Programmierer schreiben gerade
keinen Parser zufällig, sondern irgendwas anderes, wo sie das nicht
brauchen, dieses Durchsacken.
Die müssen aber jetzt alle Break reinschreiben.
Und dummerweise hat sich das halt bis in die Programmiersprache Java
über C++ vererbt, weil man halt sagt, die Profis wissen, ja, da
schreibt man ein Break hin, also schreibt man ein Break hin.
Und deswegen bringe ich Ihnen jetzt bei, schreiben Sie erst mal ein
Break hin und überlegen Sie sich zweimal, ob Sie es weglassen wollen,
wenn Sie nicht gerade einen Parser schreiben, also ein Programm, das
ein anderes Programm einliest und dann einen absagten Sonntagsbaum
umbaut, werden Sie es wahrscheinlich nicht brauchen.
Also gewöhnen Sie sich einfach das Break an und ärgern Sie sich
darüber, dass das mal 1972 in einer Kneipe schiefgelaufen ist.
Das ist jetzt zum Beispiel mal verallgemeinert, das funktioniert so,
ich habe switch und einen Ausdruck und dann habe ich Werte und
Anweisungen hier und die Cases geben den einzelnen Wert an, für den
dieser Ausdruck gelten muss, oder einer da drüber, und Default heißt,
wenn alles das nicht wahr ist, dann führe diese Anweisung aus.
Die müssen nicht da stehen, es muss keinen Default-Teil geben, aber
wenn es den gibt, wird der ausgefüllt.
So, hier kann ein beliebiger Ausdruck stehen, der aber den Typ
character byte short int haben muss und seit Java 7 auch enum oder
sogar String.
Das ist übrigens auch anders als im C.
Und der Witz ist einfach der, sie brauchen halt irgendwas, was sie
hier auswerten können, was dann nachher matchen kann auf diese Werte.
Hier, da müssen aber wirklich Werte stehen.
Sie machen also keine Unifikation von zwei Ausdrücken und überlegen
so, sind diese zwei Ausdrücke jetzt passend?
Nee.
Sie können immer nur diesen einen Ausdruck auswerten und der muss dann
passen zu dem Wert hier, aber hier steht eben kein Ausdruck.
So, dann das mit der Break-Anweisung habe ich gesagt.
Sie brauchen in der Regel immer diese Break-Anweisung nach jedem Case.
Und wenn Sie eben die mal vergessen, die Break-Anweisung, dann werden
tatsächlich alle Anweisungen der nachfolgenden Case-Anweisungen
inklusive Default ausgewertet.
So, wie gesagt, Default kommt immer automatisch dann, wenn nichts hier
zutrifft.
So, hier sieht man mal ein Ding, wo man vielleicht, wenn man ein
bisschen schlecht drauf ist, irgendwie noch so ein bisschen Hangout
hat von der letzten Nacht oder so, mal auch tatsächlich was Sinnvolles
ohne Break schreiben kann und es ist gerade kein Parser, aber es ist
von der Lesbarkeit schon nicht so schlau.
So, und zwar schauen wir uns erstmal hier das Programm an.
Was Sie hier machen, ist, Sie sagen, ich habe einen Monat, einen
beliebigen Monat, der ist jetzt mal beliebigerweise gerade 5, der 5.
Monat, und jetzt möchten Sie umrechnen vom Monat, wie viele Tage hat
der Monat.
Und was Sie jetzt machen, ist, Sie gehen im Prinzip die Monate durch,
sinngemäß Januar, Februar bis Dezember und Sie tragen einfach hier
jedes Mal ein, durch die Zuweisung, wie viele Tage hat das, und nehmen
wir mal an, es ist gerade kein Schaltjahr, hat der Februar 28 Tage und
Sie machen hier eben diese Zuweisung, Oktober 31 zum Beispiel und Sie
schreiben jedes Mal das Break hin und unter der Annahme, dass jetzt in
MANS kein 14 drinsteht, wie 13 oder 0.
Schreiben Sie auch mal keinen Default-Fall hin, sonst können Sie
natürlich mit dem Default irgendeine Fehlermeldung ausgeben.
Und jetzt haben Sie quasi umgerechnet Ihre Monatsvariable, in der der
beliebige Wert 5 stand, auf die Tage im Monat.
Gut, nachvollziehbar, lesbar, ein bisschen mehr Tipparbeit gewesen,
vielleicht als diese Variante, die genau dasselbe macht.
Diese Variante ist so Schlauberger-mäßig.
Erstmal wird der Spezialfall Februar abgefackelt, Oktober 28, dann
sagt man, ok, jetzt gibt es ja verschiedene Monate, die 30 haben.
Das sind hier der April, der Juni und der September und der November.
Also mache ich Case 4 kein Break, Case 6 auch kein Break, Case 9 kein
Break und Case 11, Days gleich 30, wohl wissend, dass wenn hier nur
eines positiv ausgewertet wurde, alle anderen danach auch ausgeführt
werden und das ist in dem Falle Days gleich 30.
Dann mache ich meinen Break und bin draußen.
Und für alles andere im Default, wo ich jetzt offenbar noch übrig bin,
das müssen ja dann logischerweise die Tage mit 31 sein.
Das ist jetzt mal ein Beispiel, wo Sie nicht einen Break
hingeschrieben haben hinter jedes Case, aber jetzt seien Sie doch mal
ehrlich, auch wenn das hier die Schlauberger-Variante ist, bis man das
verstanden hat, dass das richtig ist, muss man dreimal hingucken.
Das hier ist, obwohl es mehr Text ist, mehr Tipperei, mehr auch zum
Lesen, trotzdem aber das leichter Verständliche.
Ja bitte?
Nee, wäre in dem Fall egal.
Die Frage war, ob man dieses Break jetzt hier bräuchte, das bräuchte
man in dem Fall nicht.
So, jetzt eine ganz wichtige Botschaft, die wieder auch kein Compiler
entforst.
Sie schreiben das Programm nicht für den Compiler oder den Computer,
Sie schreiben das Programm für den nächsten Menschen, der es lesen
muss.
Das sollten Sie sich als Hauptbotschaft dieser Vorlesung einprägen,
als Softwareentwickler.
Und wenn Sie Pech haben, sind Sie nämlich der nächste Mensch, der Ihr
eigenes Programm lesen muss.
Das heißt, minimieren Sie nicht die Anzahl der Tastenanschläge,
schreiben Sie nicht ganz clevere Sachen rein, um Ihren Kollegen oder
Ihre Kollegin zu beeindrucken, wie schlau Sie programmieren können.
Alles das kommt bungerangartig auf Sie zurück.
Schreiben Sie das Programm so, dass es maximal leicht verständlich
ist.
Und ich würde sagen, das da ist leichter verständlich und leichter
nachvollziehbar, dass es richtig ist, als das da, auch wenn es
dasselbe macht und obwohl das kürzer ist.
Kürzer ist aber nicht immer besser.
Jetzt bitte wieder ein bisschen mehr Ruhe, weil jetzt kommen wir zu
einem etwas kniffligen Thema, zumindest mal für die, die anfangen mit
dem Programmieren.
Jetzt kommen wir zu Schleifen.
Und zwar, Schleifen, also im Prinzip, dass man Dinge wiederholt,
iteriert, funktionieren immer in Java nach dem gleichen Muster.
Ich habe eine Vorbereitung der Schleife, ich habe die eigentliche
Schleife, das ist dann auch der Schleifenrumpf, der iteriert
ausgebildet und ich habe eine Nachbereitung der Schleife.
Und schauen wir uns erstmal ein Beispiel an, und zwar ist das das
Beispiel, was wir schon in der allerersten Vorlesung hatten, mit
diesem Aufsummieren der ersten N-Zahlen.
Ich hoffe, Sie erinnern sich noch daran, dass ich Ihnen damals schon
beigebracht habe, erstmal über das Problem nachdenken, nicht sofort
losprogrammieren und die zumindest mal mathematisch Gebildeten von
Ihnen wissen, da braucht man in echt gar keine Schleife, weil es gibt
ja die Gauss-Summen-Formel.
Aber ich will das jetzt mal mit aller Gewalt als Beispiel für eine
Schleife nehmen.
Also machen wir hier eine Schleife und wir haben das Programm so
ähnlich, wie wir es auch schon in der ersten Vorlesung haben hier,
dass ich sag,
ich habe eine While-Schleife, die sagt, solange eine bestimmte
Bedingung gilt, führe ich etwas aus, nämlich diesen Schleifenrumpf,
nicht Reifenschlumpf.
Und hier stehen die Variablen eigentlich schon erklärt, ich möchte von
a bis b alle Zahlen aufsummieren.
Diese Zahlen sind auch hier tatsächlich angegeben, das ist das i und
das ist auch genau das i hier.
Das i bekommt also den Wert von a.
Das ist ein Beispiel für die Vorbereitung, die ich eben genannt habe.
Dann kommt die eigentliche Schleife, in der die Summation vorgenommen
wird, das Aufsummieren und auch das i um 1 erhöht wird, was man sich
ja auch vorstellen kann, wie von hier das i läuft von a nach b in
diesem großen Summenzeichen.
Und wenn man sich das jetzt mal im Diagramm anschaut, am Anfang habe
ich noch gar nichts iteriert.
Die Summe ist auf 0 gesetzt worden, i auf den Wert 8.
Dann gehe ich in die Schleife rein, werte diesen Ausdruck aus, die
Summe hat also den Wert 8.
i bekommt den Wert 9, weil ich jetzt hier 1 aufaddiere.
Dann schaue ich nach, ist i immer noch kleiner als b?
b ist in dem Fall der Wert 11, ja.
Also mache ich wieder weiter.
Ich summiere auf, das ist die 17, das ist die 10.
Dritte Iteration, i hat den Wert 11 bekommen, ich summiere auf, das
ist jetzt die 27 und jetzt summiere ich hier die 11 auf die 27,
bekomme 38.
i hat den Wert 12 und damit gilt diese Bedingung nicht mehr und ich
bin fertig und das Ergebnis steht in Summe drin, ist 38.
Das machen wir noch schnell und dann sind wir fertig.
Schreiben Sie jetzt ein kleines, einfaches Programm, was die Zahlen 1
bis p ausgibt.
Also Sie müssen in der Schleife nur diese Ausgabe machen, jetzt nichts
weiter aufsummieren, aber es soll im Prinzip für ein p gleich 5 die
Zahlen 1, 2, 3, 4, 5 ausgeben.
Also nichts summieren, einfach nur ausgeben.
Wie gesagt, für die Cracks einfach, aber für die Programmieranfänger
muss man vielleicht einmal noch nachdenken.
Das können Sie machen und dann besprechen wir gleich eine potenzielle
Lösung.
So, weil das nicht so arg kompliziert ist, schauen wir uns jetzt mal
die Lösung an.
Also Sie brauchen eine Variable, die man hochzählen kann, weil Sie
wollen ja alle Werte bis zu einem Wert ausgeben.
Das heißt, das p gibt Ihnen ja an, wohin Sie laufen, aber das ist ja
nicht der Wert, den Sie ausgeben.
Insofern brauchen Sie eine weitere Variable int gleich i und weil wir
gesagt haben, es soll bei 1 anfangen, setzen wir die schon mal
clevererweise auf den Wert 1 am Anfang.
Das ist die Vorbereitung.
Dann sagen wir, solange dieses i nicht größer geworden ist als das p,
weil wir sollen ja nur bis p ausgeben, möchten wir jetzt was
wiederholen mit der Schleife und das ist tatsächlich zum einen die
Ausgabe, dass wir sagen jetzt den Wert i ausgeben und dann das hier,
das heißt wir geben ein Leerzeichen aus.
Das ist, dass Sie nicht direkt aneinander bappen und Sie das Ganze als
12.345 lesen, sondern halt immer ein Leerzeichen dazwischen ist.
So und was wir dann auch noch natürlich machen müssen, ist das i um 1
erhöhen, weil jetzt wollen wir im nächsten Durchlauf der Schleife den
einen höheren Wert ausgeben.
So und die nächsten Sachen sind etwas aufwendiger und wichtiger und
dann kommt auch ein relativ großer Einstein-Block und deswegen will
ich das jetzt auch nicht in den letzten Minuten noch durchzwängen und
sage dann, das machen wir das nächste Mal.
Vielen Dank und dann bis in einer Woche.