Wir waren beim letzten Mal stehen geblieben in der Vorlesung zum Thema
Variablen und Datentypen und hatten uns eigentlich den Großteil der
Sachen zum Thema Datentypen angeschaut.
Also wir hatten gesehen, dass es diese acht elementaren Datentypen in
Java gibt, hatten dann über die verschiedenen Wertebereiche dieser
Datentypen gesprochen, hatten insbesondere dann auch etwas
ausführlicher über Gleitkommadarstellungen gesprochen, was wohl der
komplexeste Datentyp ist.
Über Boolsch oder über Wahrheitswerte braucht man eben nicht so lange
zu reden.
Wir hatten kurz festgestellt, dass Klassen auch Datentypen sind, also
da können sie sich auch immer ihre eigenen Datentypen, also die
möglichen Wertebereiche für Variablen definieren, dann auch so
zusammengesetzt, indem sie dann wieder für Klassen dann Attribute
haben, was den Wertebereich eben immer mehr vergrößert.
Und wir haben festgestellt, dass Datentypen auch mit Operationen
beziehungsweise Methoden, die dann auf diesen Variablen möglich sind,
verbunden sind.
Wir haben ein paar von den acht noch nicht angeschaut, da machen wir
jetzt an der Stelle weiter.
Uli22, wir hatten ja geendet, genau mit dem Quiz zu den
Gleitkommazahlen und hatten eben uns noch nicht angeschaut, oder ich
glaube zumindest noch gar nicht angeschaut, die Characters als
weiteren elementaren Datentyp.
Hier also der letzte.
Characters ist der Datentyp, um Zeichen ausdrücken zu können.
Und das ist eben vordefiniert in Java, was dann da die möglichen
Zeichen sind.
Das sind sogenannte ASCII- und Unicode-Zeichen, also verschiedene
Codierungen für verschiedene Zeichen, die ich ausdrücken möchte.
Zeichen, also sowohl Buchstaben, Zahlen, als auch irgendwelche
Sonderzeichen.
Und wenn man sich da den Unicode-Standard mal anschaut, gibt es eine
ganze Menge von Sonderzeichen.
Also die Zahlen und Buchstaben sind da in der Minderheit.
Das kann ich zum einen ausdrücken.
Und wichtig dabei ist noch zu erwähnen, dass man, wenn man Characters
angibt, also Character eben als Datentyp ein einzelnes Zeichen, kann
ich nicht alle Zeichen immer direkt in meinem Programm-Code gut
darstellen, sondern wenn der selber wieder in Unicode codiert ist,
geht's sogar doch.
Aber oftmals pragmatischer oder auch portabler über verschiedene
Plattformen ist es, dann Sonderzeichen auch durch ihre sogenannten
Escape -Sequenzen zu codieren, weil ich vielleicht nicht auf jeder
Plattform, wo ich meinen Programm-Code anschauen möchte, dann auch
tatsächlich alle Zeichen verfügbar habe, um mir das anzuschauen.
Und bei einigen Zeichen, wie zum Beispiel bei diesem ersten hier, ist
es auch gar nicht so leicht, sich anzugucken.
Das erste Zeichen, dieses Slash-N, ist nämlich das Zeichen, was einen
Zeilenwechsel beschreibt.
Und das sehe ich ja typischerweise eben nicht.
Also das sehe ich im Editor dann nur als Zeilenwechsel.
Das würde an der Stelle sogar gar nicht funktionieren.
Das muss ich escapen, wie man dann auf Englisch sagt.
Das heißt, was ist da der Wertebereich?
Es gibt zum einen die verschiedenen Buchstaben, die verschiedenen
Zahlen.
Und dann nimmt die Möglichkeit, auch weitere Zeichen, die so
vielleicht nicht direkt auf der Tastatur verfügbar sind, auszudrücken.
Zum einen diese Sonderzeichen wie ein Zeilenwechsel, in dem man
Backslash -N schreibt.
Das N steht für den Zeilenwechsel, der Backslash ist hier dieses
Escape -Zeichen, was immer einläutet.
Das ist jetzt nicht genau das Zeichen, was ich meine, also nicht der
Backslash, sondern das, was dahinter kommt, codiert mein Zeichen, was
ich eigentlich meine, sozusagen.
Ein bisschen speziell beim Programmieren noch die Behandlung vom
Apostroph.
Weil Sie, wie Sie jetzt hier auch sehen, die Zeichenliterale selber
schon immer in Anführungszeichen setzen, kommt einem auch einigermaßen
bekannt vor, weil man wirklich sagt, man meint jetzt den Buchstaben A.
Wenn man dann allerdings wiederum ein Apostroph als Zeichen meint,
muss man auch hier wieder ein Escape davor setzen und schreibt dann
also Anführungszeichen für, hier beginnt jetzt ein Charakter,
Backslash, Apostroph, und wieder eben ein Apostroph für, hier endet
jetzt ein Charakter.
Wenn Sie also einen einzelnen Charakter, einer Variablen, einen
einzelnen Zeichenwert eines Apostrophs zuweisen möchten, können Sie
schreiben Apostroph, Backslash, Apostroph, Apostroph.
Backslash selber muss man auch wieder escapen, weil der ist ja jetzt
gerade das Symbol dafür, dass ich eine Escape-Sequenz starte.
Das heißt, wenn ich wirklich einen Backslash meine, muss ich sogar
zweimal Backslash schreiben an dieser Stelle.
Klar, weil, kann ich den Buchstaben doppelt belegen oder mein Programm
kann ich wissen, meine ich denn jetzt tatsächlich den Backslash oder
meine ich jetzt den Anfang einer Escape-Sequenz?
Genau, das sind also so die typischsten Escape-Sequenzen, die Sie
brauchen.
Zeilenwechsel braucht man halt relativ häufig.
Die Apostrophe, dass man die eben auch ausdrücken kann und den
Backslash selber.
Und dann können Sie eben auch, und da haben Sie weiterhin die
Möglichkeit, weitere Zeichen aus diesen ASCII- und Unicode-Standards
zu kodieren.
Da gibt es also große Tabellen, wo immer steht, was ist der Code für
mein einzelnes Zeichen und was ist dann das entsprechende Zeichen.
Entweder können Sie sich da auf diese ASCII-Tabellen beziehen.
Das wird dann, ja, oktalkodiert.
Das heißt, Sie haben als Escape-Zeichen erstmal Backslash und Null und
können dahinter den ASCII-Code schreiben, den Sie meinen.
Zum Beispiel hier, wenn man 07 dann als ASCII-Code beschreibt, wäre
das das sogenannte Bell-Zeichen.
Das ist eigentlich auch ein Steuerbefehl und führt dazu, dass ein
Rechner einen Ton ausgibt.
Soll an der Stelle das sein.
Also es sind tatsächlich nicht nur Zeichen, die dargestellt werden
können, sondern kann auch sowas sein.
Also auch ein Steuerzeichen an der Stelle, sagt man auch.
Oder hier Unicode-kodiert mit Slash-U.
Einfach als Beispiel, so ein hebräisches Aleph kann man damit auch
ausdrücken.
Also dieser Unicode-Standard ist wirklich sehr ausführlich, hat für
die verschiedensten Sprachen und auch alle möglichen Sonderzeichen
dann Symbole, die Sie dann eben über den entsprechenden Unicode-Code
zugreifen können.
Man kann, wir haben ja gesagt, Datentypen sind nicht nur die möglichen
Werte, sondern auch die darauf möglichen Operatoren.
Und auf Characters habe ich, oder auf Zeichen habe ich die Operatoren,
die ich auch bei den Ganzzahltypen habe, wo dann einfach die ASCII-
bzw.
Unicode -Werte verrechnet werden.
Die werden dann als Zahl behandelt und dann kann ich damit rechnen.
Ich kann also zum Beispiel sagen, A plus 1 wäre 98, weil der Unicode
-Code oder der ASCII-Code für ein kleines A eben die 97 wäre, plus 1
wäre dann 98.
So kann ich auch A mal B zum Beispiel rechnen.
Ob das jetzt so viel Sinn macht, A mal B insbesondere, sei mal
dahingestellt.
A plus 1 kann ja vielleicht noch Sinn machen, wenn man ein Programm
hat, was irgendwie das Alphabet ausgeben soll oder so, warum nicht?
A mal B, einfach nur um darzustellen, man kann damit auch
multiplizieren, wenn man dann möchte.
Das waren einzelne Zeichen, Characters.
Interessanter wird es, wenn ich einzelne Zeichen auch zusammenfüge zu
ganzen Strings, also zu ganzen Zeichenketten.
Das ist in Java möglich schon über die mitgelieferte Klasse String.
Sie sehen jetzt hier, dass diese Klasse großgeschrieben ist, der
String großgeschrieben ist.
Das ist also kein elementarer Datentyp mehr, sondern das ist eine von
Java vorgegebene Klasse, wo ich eben Objekte vom Typ String dann
instanziieren kann.
Trotzdem Zeichenkette sowas dann doch wieder irgendwie Elementares,
dass wir das hier auch an dieser Stelle oder ich das hier auch an
dieser Stelle in den Folien habe.
Beispiele, wie man sowas verwendet.
Man kann also sagen, ich habe jetzt eine Variable vom Typ String und
ich weise ihr den Wert zu, das ist ein Text.
Hier wieder einleitend, so ein doppeltes Anführungszeichen.
Beim Charakter eben hatten wir gesehen ein einfaches.
Ausleitend am Ende ein doppeltes, auch wieder so ein doppeltes
Anführungszeichen.
Dann habe ich also dieser Variablen jetzt diese Zeichenkette
zugewiesen.
D -A-S, Leerzeichen, I-S-T, Leerzeichen, E-I-N, Leerzeichen und so
weiter.
Können Sie sich vorstellen.
Das ist also nicht nur ein einzelner Charakter mit diesem
Wertebereich, sondern aneinander gehängte Zeichen als sogenannte
Zeichenkette.
Anderes Beispiel wäre dieser String Greeting hier.
Man kann also auch Sonderzeichen benutzen.
Sie könnten jetzt auch, das ist im nächsten Beispiel der Fall,
Steuerzeichen wie dieses Backslash-N, was wir eben gesehen haben, für
einen Zellenumbruch verwenden oder eben Unicode-Zeichen verwenden.
Wenn Sie jetzt hier also wieder schreiben würden, was hatten wir
gerade gesehen für das Aleph, ich weiß es nicht auswendig, Slash-U und
dann den Code für das Aleph, dann würde hier also in dem String
sozusagen ein Aleph-Zeichen stehen in dieser Zeichenkette.
Dies hier wäre aber, wie gesagt, der Zeichenumbruch.
Also wenn man sich das jetzt auf dem Bildschirm ausgeben lassen würde,
hätte man am Fasanengarten 5 und dann in der neuen Zeile 76131
Karlsruhe stehen.
Operationen auf den Strings, der wichtigste, gibt es eine ganze Menge.
Sie können, wenn Sie eine schlaue oder einigermaßen schlaue IDE wie
Eclipse zum Beispiel benutzen, um zu programmieren, sich das jeweils
auch immer anzeigen lassen, was es für Operationen auf so einem
Datentyp gibt, welche Methode die Klasse String also hier zur
Verfügung stellt.
Wahrscheinlich wichtigste für den Anfang ist erstmal die Konkatenation
von Strings, die Verkettung, wie man das auf Deutsch dann auch nennt,
dass man auch hier mit dem Plus-Operator mehrere Strings
aneinanderhängen kann einfach.
Ich könnte jetzt also sagen, meiner Variablen result weise ich zu,
einmal den String dies ist mit Leerzeichen beendet, das ist also hier
ein String vom Anführungszeichen bis zum Anführungszeichen, plus, was
die Konkatenation angibt, dann einen weiteren String, eine
Konkatenation bis zum zweiten Anführungszeichen.
Was ich dann also nachher in dieser Variable result drinstehen habe,
ist ein String, dies ist eine Konkatenation mit jeweils zehn
Leerzeichen zwischen den Worten, aber eben ein langer String.
Da werden also einfach diese beiden Strings aneinandergeklebt
sozusagen.
Das kann man direkt machen, indem man hier die Strings im Programmcode
so hinschreibt, was sie denn enthalten sollen, oder man könnte, man
kann das auch machen, wenn die Strings schon in Variablen gespeichert
sind, Greeting und Address hatten wir ja oben schon definiert, gleich
dann auch mehr zu diesem Thema Variablen und Speichern, wenn sie jetzt
irgendwie sagen, Moment, das hatten wir doch noch gar nicht.
Da kann ich aber auch diese beiden Strings miteinander verketten und
bekomme dann ein Ergebnis, ein langes String, der eben die Inhalte
dieser beiden Variablen einfach hintereinander hängt.
Erst das Greeting und dann die Adresse.
Hallo am Fasangarten.
Macht jetzt vielleicht nicht so viel Sinn, aber einfach als Beispiel.
Wo Sie dann natürlich aufpassen müssen, wäre beim dritten Fall, dieses
Beispiel hier ist keine Konkatenation, wenn man jetzt einfach
schreibt, Anführungszeichen dies ist plus keine Konkatenation, dann
ist das keine Konkatenation, keine Verkettung, sondern eben einfach
nur ein String, der zufällig ein Plus enthält.
Also es ist natürlich ein Unterschied, ob Sie jetzt das Plus in Ihrem
Programmcode selber stehen haben und das als Operator ausgewertet
wird, vielleicht auch, wenn Sie es an Stellen geschrieben haben, wo
Sie es nicht dürfen, zu einem Kompilierfehler führt, oder wenn Sie das
String als ein Zeichen in Ihrer Zeichenkette verwenden, also
Entschuldigung, das Plus hier innerhalb der Anführungszeichen stehen
haben, dann ist es halt einfach nur irgendein Zeichen in dieser
Zeichenkette und wird nicht weiter interpretiert vom Compiler und dann
auch nachher vom Programm nicht.
Genau, das kann man mit den Strings machen.
Da können wir jetzt also Ausgabentext mit verwalten.
Und schließlich zum Abschluss von den Datentypen noch ein weiterer
Datentyp, der sogenannte Aufzählungsdatentyp, Enum, für Enumeration
auf Englisch.
Der bietet jetzt die Möglichkeit, benutzerdefiniert eine nicht zu
große Menge, eine sollte einigermaßen kleine Menge von möglichen
Werten zu definieren, die ich explizit angebe, mit denen ich dann
rechnen kann oder mit denen ich dann arbeiten kann und mit denen ich
dann mein Programm auch teilweise verständlicher machen kann.
Beispiele wären, ich gebe die verschiedenen Richtungen, die ich auf
einem Kompass habe, als Enum an.
Also ich sage, die möglichen Werte, die ein Kompass zeigen kann, sagen
wir mal die Grundwerte, weil da gibt es natürlich dann auch noch
wieder zusammengesetzte Werte daraus, sind Norden, Osten, Süden,
Westen.
Einfach, dass ich eine andere Möglichkeit sowas zu machen wäre, das
irgendwie in Zahlen zu kodieren, dass ich jetzt sage, naja, ich nehme
jetzt mal eine 0 für Norden, eine 1 für Osten und so weiter und nehme
da die Gradzahl oder sowas.
Das wäre dann allerdings schlechter lesbar später in meinem Code, als
wenn ich das explizit mache und so ein Enum benutze und hier dann auch
klar ist, es gibt wirklich nur genau diese vier Werte.
Wenn ich zum Beispiel Zahlen benutze, um Kompassrichtungen zu
kodieren, dann wäre ja hier die Frage, was, wenn ich mal auf eine 5
komme, also eine 5 in meinem Programm habe, was bedeutet das denn als
Kompassrichtung?
Das würde gar keinen Sinn machen.
Deswegen besser solches, ja, ist auch wieder das Stichwort
Modellierung eigentlich, solche Eigenschaften meiner Domäne, für die
ich mein Programm schreibe, dadurch explizit zu machen und also hier
genau zu sagen, ich definiere einen neuen Datentyp namens Compass
Direction, das ist ein Enum, das ist eine Enumeration von möglichen
Werten und der hat genau diese vier Werte, Norden, Osten, Süden,
Westen.
Anderes Beispiel wären Tage, da gibt es eben auch genau diese sieben
Tage, könnte ich schön als Enum beschreiben oder vielleicht auch
Farben, wenn ich da so eine abgeschlossene Menge von Farben habe, die
für mein Programm relevant sind.
Vielleicht ist Gemurmel im Hintergrund, wieder die Bitte, das doch
bitte einzustellen.
Hier vorne in der, was ist das, dritten Reihe, roter Haarschopf zum
Beispiel.
Dritte Reihe, auf der Emporo meine ich, er hat es noch nicht gemerkt.
Genau, sie meine ich.
Spätestens wenn es so ruhig wird und ich nichts mehr sage, dann
sollten Sie eben gucken, ob ich Sie gerade angucke, weil Sie gerade
reden, das wäre, glaube ich, ganz sinnvoll.
Sonst merke ich mir nämlich das Gesicht irgendwann.
Das ist jetzt negativ gesagt, das ist natürlich auch positiv, wenn ich
mir ein Gesicht merke, aber mit einer negativen Erinnerung daran, das
möchten wir, glaube ich, nicht.
Okay, das sind also diese Enumsfeste Werte, Menge von festen Werten,
die ich beschreiben möchte.
Schema, das zu deklarieren ist, ich schreibe dieses Schlüsselwort Enum
hin, jetzt funktioniert mein Stift nicht mehr, naja, egal, gebe diesem
Datentyp dann einen Namen, den ich im Weiteren verwenden kann und in
geschweiften Klammern die Liste der Werte, wie Sie es hier oben dann
auch sehen.
Auch hier wieder, ein Datentyp ist ja nicht nur eine Menge von Werten,
sondern auch mögliche Operationen auf diesen Werten.
Das ist bei Enums natürlich so erstmal nicht gegeben, sie sind ja
benutzerdefiniert, also kann Java nicht automatisch wissen, was da für
die möglichen Operationen drauf sind.
Ich kann aber als Programmierer eben auch Operationen für diese
Enumerations definieren und zum Beispiel definieren, dass eine
Addition darauf möglich ist oder vielleicht, dass so ein Mittelwert
der Compass Direction möglich wäre.
Da könnte ich also so eine Operation definieren, die mir, wenn ich
Norden und Süden reingebe, vielleicht Osten rausgibt, das macht nicht
so viel Sinn, weil das in der Mitte liegt oder so.
Oder ein Mehrheitswert, also ich kann da verschiedene Operationen
drauf definieren, wie ich es denn gerne hätte.
Okay, so viel zu den Datentypen auch schon.
Abschließend dazu dann auch nochmal eine kleine Anregung an Sie,
darüber nachzudenken, was wir jetzt heute und letztes Mal gehört
haben.
Wir hatten ja schon das Quiz gehabt zu den verschiedenen
Gleichkommazahlen, das ist die jetzt also hier nicht mehr dabei,
sondern eher nochmal ein bisschen allgemeinere Fragen.
Sie können sich also, wie Sie es kennen, für diejenigen, die
vielleicht beim letzten Mal nicht dabei waren, einloggen hier über
inter.fzi.de.
Und da dann, wenn Sie mitmachen wollen bei diesem Quiz, die
entsprechenden Quiz-IDs hier eingeben, fünf verschiedene IDs für diese
fünf verschiedenen Fragen.
Wenn Sie zum Beispiel mit die Frage beantworten wollen, warum dann der
Wert von 10-6 nicht eine Million ist, sondern zwölf.
In Java sollte ich vielleicht dazu sagen, denn ich glaube in LaTeX
würde das ganz gut funktionieren, oder?
Oder im Taschenrechner auch.
Hier das Ergebnis, also einfach mögliche Antworten, die wir hier
vorgeschlagen haben.
Oh, Sie sehen es gar nicht.
Wir können nicht ganz so lange Fragen stellen.
Also die möglichen Antworten, die wir hier formuliert hatten, war zum
einen 10 hoch wird als Multiplikation, also 10 Dach, sollte ich jetzt
an der Stelle sagen, wird als Multiplikation Dach mit 10 in Basis 2
aufgefasst.
Nee.
Das zweite hier, die zweite Antwortmöglichkeit, muss ich eben gucken,
welche das ist.
Genau, aber das hat irgendwas mit Überlauf zu tun.
Vielleicht ist eine Million größer als die maximal darstellbare Zahl.
Auch nicht der Fall.
Wir haben jetzt keine Annahme, was 10 hoch oder 10 Dach 6 für ein
Datentyp sein könnte, sodass der größte Wert gerade, was hätten wir
da, 900.000, irgendwas, auf jeden Fall 12 vorher wäre.
Weil da müsste es ja auch gerade hinkommen, dass es hier tatsächlich
eine 12 dabei rauskommt.
Andere Antwortmöglichkeit, der Dachoperator wandelt die Zahl in
Characters, also Zeichen, wie wir gerade gesehen haben, um und rechnet
mit diesen.
Stimmt auch nicht, auch eine kreative Antwort vielleicht, aber stimmt
nicht.
Was richtig ist, was Sie jetzt hier unten nicht sehen, ach doch, wenn
ich drübergehe, ist, der Dachoperator, wenn Sie sich erinnern, ist gar
nicht dafür da, um Exponenten anzugeben für eine Zahl, sondern der
Dachoperator ist das bitweise Entweder-Oder auf einer Zahl, also auf
den Binärdarstellungen von 10 und 6 in diesem Fall.
Und 10, wenn Sie sich das umrechnen würden, oder das kann man jetzt
vielleicht an der Stelle auch geraten haben, wäre 1 0 1 0.
Eine 6 in Binärdarstellung wäre 0 1 1 0.
Und wenn ich das eben Entweder-Oder setze, bekomme ich 1 1 0 0.
Könnten Sie sich jetzt aufschreiben, könnte ich auch dazu schreiben,
was eben im Dezimal dieses Themen wieder eine 12 wäre.
Also Antwort ist, weil das Dach hier kein Hoch-Irgendwas ist, sondern
weil das Dach hier den Operator bitweise Entweder-Oder beschreibt.
Nächste Frage.
Könnte ich mit a b c testen in meinem Programm, ob diese drei Zahlen
geordnet sind?
Haben Sie jetzt vielleicht auch schon ausgefüllt?
36% sagen ja.
Die meisten sagen nein.
Und nein ist auch richtig.
Das geht nicht.
Wir können nicht einfach mit a, hier oben steht es ja auch nochmal, a
kleiner b kleiner c sagen, ob diese drei Zahlen geordnet sind, weil
diese Operatoren ja immer nur für zwei Eingaben definiert sind und
Ausdrücke sind.
Also wenn ich jetzt hier sage, a kleiner b ist eben ein
Vergleichsoperator, hier links würde ich auch an der Stelle mit der
Auswertung anfangen oder würde quasi implizit die Klammer gesetzt
werden, a kleiner b, dann würde dieser Ausdruck ausgewertet werden und
würde zu einem Ergebnis führen, also entweder wahr oder falsch, ich
weiß es jetzt ja gar nicht, die Werte haben wir nicht dazu gegeben.
Hier würde dann also ausgewertet werden, a kleiner b, entweder true
oder false, auf jeden Fall ein bool'scher Wert, ein Wahrheitswert.
Und dann hätte ich noch stehen, Wahrheitswert, true oder false, ist
kleiner als c.
Und das ist dann nicht definiert, weil dieser Vergleichsoperator eben
auf Zahlen definiert ist, aber nicht auf einem Vergleich von einem
Wahrheitswert und einer Zahl.
Das heißt, hier würde sogar der Compiler dann meckern und sagen, da
sind die Operatoren nicht richtig verwendet.
Nächste Frage wäre, kann man die Operatoren kleiner und größer
verwenden, um Stringvariablen zu vergleichen?
Was meinen Sie?
Genau, die meisten sagen nein und das ist auch richtig.
Tatsächlich kann man es aus der vorherigen Folie ja nicht ganz
hervorgehen, weil wir hatten gesagt, auf Strings sind viele Operatoren
definiert, unter anderem die Konkatenation.
Wir hatten das Größer und Kleiner für den String nicht gesehen und
tatsächlich gibt es Größer und Kleiner für Strings auch nicht.
Und man könnte sich jetzt auch überlegen, was würde das eigentlich
heißen, weil Strings ja auch unterschiedlich lang sein können zum
Beispiel.
Man könnte sich jetzt, das ist natürlich auch an der Stelle wieder
alles Konvention, man hätte sich auch überlegen können, naja, Größer
und Kleiner-Operator definieren wir so, dass irgendwie die Länge der
Strings verglichen wird oder so.
Das wurde in Java aber nicht so gemacht.
Dafür gibt es eine andere Möglichkeit, beim String auf seine Länge
zuzugreifen.
Variablen -Name Punkt Länge und darauf dann explizit zu vergleichen,
um dann auch wieder ein Programm verständlicher zu machen.
Hier will ich jetzt nicht einfach den Werte selber vergleichen,
sondern ich will irgendeine Eigenschaft dieser Werte vergleichen, in
dem Fall zum Beispiel die Länge.
Also das geht nicht, man kann nicht Größer, Kleiner auf String
-Variablen benutzen.
Hier noch zwei weitere Fragen, die was mit Nullen, sage ich jetzt mal,
so platt zu tun haben.
Was passiert denn wohl, wenn ich schreibe 1, jetzt sage ich sogar mal
wirklich ein Zeichen, Slash 0 in Java, was passiert da?
104?
Nein?
Ach genau, die verschiedenen Antwortmöglichkeiten, die haben Sie
sicherlich auf Ihrem Bildschirm genauer gesehen.
Aber wir sehen sie jetzt hier auch.
Was könnte es alles sein?
Vielleicht kommt 0 raus, vielleicht bekomme ich eine sogenannte
Exception, eine Ausnahme, weil ich durch 0 geteilt habe.
Vielleicht kommt dieser Maximalwert eines Doubles raus oder Minus
-Infinity oder Plus-Infinity oder auch der größte Wert vom Integer.
Das sind alles so weitere Antworten, um noch ein paar mehr Antworten
zu haben.
Aber tatsächlich liegen sie auch alle in größer Mehrheit richtig.
Die Division durch 0 ist ja nicht definiert.
Was heißt das, durch 0 zu teilen?
Mathematisch macht das nicht so viel Sinn und so ist es in Java also
auch wiederum in gewisser Weise per Konvention, dass man sagt, das
geht nicht.
Also ein Integerwert durch den Integerwert 0 zu teilen, macht keinen
Sinn, würde dann die virtuelle Maschine zur Laufzeit sagen und eine
sogenannte Ausnahme werfen.
Das sehen wir dann auch noch in späteren Vorlesungen, was das genau
bedeutet mit der Ausnahme, mit dem Fehlertext.
Hier wurde durch 0 geteilt, das macht keinen Sinn, sozusagen.
Vielleicht haben Sie beim ersten Probieren jetzt mit den ersten
Übungszetteln auch schon andere Exceptions gesehen.
Das sind eben immer irgendwelche Fehlerfälle, die das Programm Ihnen
dann wirft, wenn irgendwas nicht so funktioniert, wie es sollte.
Oder wie Sie sich das wahrscheinlich gedacht haben.
Jetzt denken Sie sich wahrscheinlich, naja, dann ist das nächste
wieder das gleiche.
105, was ist also das Ergebnis von 1,0 oder 1.0, wenn wir es jetzt mal
als Gleitpunktzahl bezeichnen, geteilt durch 0.0.
Ich habe mir jetzt hier leider die verschiedenen Antwortmöglichkeiten
nicht aufgeschrieben, deswegen muss ich es mal wieder hier aufmachen.
Dann sehen Sie schon gleichzeitig, was Ihre Kommilitonen denken, aber
ich glaube, Sie sind sowieso gerade ein bisschen schneller als ich.
Von daher passt das.
Ah,
hier haben wir jetzt mal zwei Lösungen, die ähnlich sind.
Also auch hier wieder so andere Sachen.
Es könnte vielleicht Double, Negative Infinity, Integer Max Value
rauskommen, wobei das sagt wahrscheinlich keiner.
Doch, das sagt einer.
Wir sind hier ja auf jeden Fall, das kann ich vielleicht schon mal
ausschließen, mit dem Datentyp Double jetzt unterwegs.
Warum da plötzlich was mit dem Integer rauskommen sollte, das ist ein
bisschen weiter weg.
Also irgendwie plausible Antworten wären diese drei, wie Sie jetzt
auch an der Menge der Kommilitonen sehen, die das hier gesagt haben.
Die schon mal was mit Double zu tun haben, das ist schon mal näher
dran.
Man könnte jetzt erst mal meinen, naja, Division durch 0 sollte bei
Gleitkommazahlen genauso wenig Sinn machen wie bei Integern.
Und wenn Sie das denken, dann denken Sie eigentlich auch völlig
logisch und plausibel.
Es ist jetzt tatsächlich so, dass im IEEE-Standard definiert ist, dass
eine Division durch 0, außer 0 wird durch 0 geteilt, positiv Infinity
gibt, also Plus und Endlich.
Das ist an der Stelle ja auch wieder eine Konvention.
Das heißt, die Macher des Standards haben sich das überlegt, dass es
so sein soll, hatten auch ihre Gründe dafür.
Dass sie nämlich sagen, naja, wenn mal eine Division durch 0 auftaucht
in meinem Programm, ohne dass ich das gewollt habe, ist wahrscheinlich
Plus Infinity.
Und bei Double habe ich ja die Möglichkeit, durch dieses
Schlüsselwort, durch eine bestimmte Bit-Kombination Plus Infinity auch
anzugeben, womöglich noch irgendwie sinnvoll weiterzurechnen.
Das heißt, wenn jetzt in der Situation, was ein bisschen was anderes
war, aber trotzdem als Beispiel in der Situation, wo eine Rakete
gestartet wird und dann ein Softwarefehler während des Starts
auftaucht, hilft es auch nur bedingt viel zu sagen, oh, Arithmetic
Exception, Division durch 0, ich höre auf.
Sondern da war eben die Idee, was könnte da noch einigermaßen
plausibel sein und dieses Ergebnis Infinity zu geben, könnte doch
plausibel sein und kann sogar sein, dass sich das in späteren
Berechnungen dann wieder ausnegiert oder gar keine Konsequenz auf die
eigentliche Berechnung hat, sodass dieser Wert dann auch ignoriert
werden kann und weitergerechnet werden kann.
Also da war ein Beispiel, das hatte ich jetzt nur gerade zufällig
gesehen bei Wikipedia, wenn ich zum Beispiel parallele Widerstände bei
einem Kurzschluss berechnen will, könnte es eben sein, dass ich da den
einen Widerstand durch eine Division durch 0 berechne, wo dann aber
der Wert plus Infinity physikalisch schon Sinn macht und insbesondere,
was ich dann insgesamt für diesen Widerstand rausbekomme, trotzdem
auch Sinn macht.
Ich dann also ein bisschen Fehlertoleranz sozusagen habe.
Das ist der Grund, warum in dem Standard einfach festgelegt wurde, das
Ergebnis von 1,0 durch 0,0 bei Gleitkommazahlen, die diesem Standard
entsprechen, soll plus Infinity sein.
Genau.
Wie gesagt, einfach festgelegt als Standard.
Und vielleicht nochmal, Sie können sich jetzt fragen, naja, wieso hat
man das für die ganzen Zahlen, für die Integers nicht genauso gemacht?
Der Unterschied hier bei den Fließkommazahlen und den Integers ist ja,
dass ich bei den Fließkommazahlen eben diese bestimmten Bitmuster
habe, um solche Besonderheiten auszudrücken.
Und zusätzlich im IEEE-Standard habe ich auch noch die Möglichkeit,
Flags auszudrücken.
Also ich kann zum Beispiel auch in meiner Zahl, zu meiner Zahl noch
zusätzlich speichern, ob die jetzt einen Überlauf hatte oder nicht.
So ist es in dem Standard selbst zumindest vorgesehen.
Das heißt, ich kann sowas wie Infinity überhaupt erst ausdrücken.
Wenn ich jetzt hier an dieser Stelle das Ganze auf Max Int setzen
würde, würde ich im Nachhinein nicht unterscheiden, kam da jetzt
wirklich der maximale Integerwert raus oder soll damit Infinity
ausgedrückt werden.
Deswegen ist es an der oberen Stelle bei den ganzen Zahlen sicherer
sozusagen auch tatsächlich eine Exception zu werfen.
Das ist da der Hintergrund.
Okay.
Gut.
Gehen wir mal weiter.
So viel also jetzt wirklich zu den Datentypen, was ich ja eben schon
angekündigt hatte, wo wir schon fast mit fertig sind.
Wir hatten ja ganz einleitend bei der letzten Vorlesung gesagt,
Datentypen schauen wir uns an, weil sie eben die Wertebereiche für
Variablen formulieren.
Und Variablen eben die Möglichkeit sind oder eine Möglichkeit sind, in
meinem Programm Abstraktionen zu erreichen, indem ich also sage, das
ist ein abstrakter Behälter für irgendwie Werte, mit denen ich dann
während meines Programms oder während der Ausführung meines Programms
rechnen kann.
Eine Variable ist also, ich nehme mal wieder meinen Stift her, ein
Platzhalter für Werte eines Datentyps, hier oben zum Beispiel.
Das heißt, ich rechne nicht immer mit festen Zahlen 4 plus 8
irgendwas, sondern ich habe Platzhalter so wie x, y, werden jetzt an
dieser Stelle zwei Variablen, mit denen ich rechnen kann.
Später zur Laufzeit könnten irgendwelche Werte hier drin stehen, ein
Wert für x, ein Wert für y.
Und ich kann in meinem Programm vergleichen, ob die Summe dieser
beiden Werte größer als 4 ist.
Ich weiß aber an dieser Stelle jetzt noch nicht, wenn ich das Programm
schreibe, welche Werte denn genau in x und y gespeichert sein werden.
Wenn wir ganz zurückdenken an unser Beispiel mit dem Sortieren von
Personen und Scheiben, die ich hin und her reiche, kann ich hiermit
also gerade nicht nur eine mögliche Berechnung ausdrücken.
Das wäre jetzt zum Beispiel 6 plus 7 größer 4.
Das wäre ja genau nur eine Kombination von Werten, die ich abfrage,
sondern hiermit kann ich eine ganze Menge von möglichen Fällen,
nämlich dass x 5 ist und y 4 oder was auch immer, verschiedene Fälle
abdecken, bin also hier etwas abstrakter.
Wie bekomme ich jetzt so eine Variable hin?
Ich muss Variablen als erstes mal deklarieren, bevor ich sie verwende
in Java.
Das heißt, ich muss für die Variable einen Namen und eben den Typ,
Datentyp, wie wir es ja gerade kennengelernt haben, festlegen.
Das Schema sieht so aus, ich schreibe einfach Typ, Leerzeichen und
dann den Namen einer Variable dahinter.
Ich kann auch mehrere Variablen vom gleichen Typ hintereinander
definieren.
Beispiele wären so, ich definiere eine neue Variable vom Typ Float,
dann schreibe ich Float a, beende das Ganze mit einem Semikolon.
Oder ich definiere drei neue Variablen vom Typ Integer, nehme ich x,
y, z, dann schreibe ich es entsprechend so.
Deklarieren bringt einem natürlich noch gar nichts, denn wir wollen ja
auch Werte in diese abstrakten Behälter hineinbekommen.
Das mache ich mit Zuweisungen, das ist das Setzen eines Wertes.
Das Schema ist ganz einfach, ich schreibe den Namen meiner Variable
hin, das Gleichheitszeichen und dann den Wert, den diese Variable
annehmen soll.
Der Wert kann erstmal ein Wert sein wie 4, 8, also einfach die
konstant eingegebene Zahl.
Oder kann auch ein allgemeiner Ausdruck sein, kann sich wiederum aus
etwas anderem ergeben, wie wir es dann später auch noch sehen werden,
sonst wäre es auch ein bisschen langweilig.
Ich kann auch beides miteinander kombinieren, Deklaration und
Zuweisung, das nennt man dann Initialisierung.
Ich muss also nicht schreiben float a, Semikolon und dann a soll 2,5
sein, sondern kann das gleich in 1 schreiben, float a gleich 2,5 zum
Beispiel.
Das ist jetzt sowieso nicht das Äquivalente, sehen Sie gerade, weil
hier habe ich ein float a hier, würde das wahrscheinlich implizit
casten.
Genau, also hier auch Schema, Typ, Name, gleich Wert, Semikolon,
kombiniert also Variablen, Deklaration und Zuweisung.
Wir haben Variablen auch schon auf einigen vorherigen Folien gesehen,
einfach nur hier als Kommentar, Notiz am Rande, das war jetzt nicht
das erste Mal, Sie haben das auch in verschiedenen Ausdrücken und auch
als Attribute in Klassen auf den vorherigen Folien schon mal gesehen,
aber jetzt wissen wir auch, was das ist, nämlich aha, eine Variable.
Damit ich mit Variablen jetzt rechnen kann, mein Programm ausführen
kann, Berechnungen machen kann, brauche ich Zuweisungen, hatten wir
jetzt ja gerade auch hier schon gesehen, Setzen eines Wertes.
Diese Werte kann ich nicht nur einmal am Anfang setzen, sondern gerade
insbesondere auch jederzeit, bei den meisten Variablen, den Wert
wieder neu setzen.
Variablen können also ihren Wert ändern, zum Beispiel, wenn ich jetzt
an Objekte denke und dieses Vector2D-Objekt, was wir in der ersten,
zweiten Vorlesung hatten, denke, kann ich da den Zustand dieses
Objektes ändern.
Vector2D gab ja zum Beispiel Richtung und Geschwindigkeit unserer
Billardkugeln an, in unserem Beispiel.
Wenn ich jetzt also in meinem Programm einen Punkt habe, wo eine Kugel
angestoßen wird, dann möchte ich ausdrücken, dass sich der Wert der
Geschwindigkeit, zum Beispiel, meines Objektes ändert und kann eben
durch Zuweisung diese Variable, die die Geschwindigkeit ausdrückt,
dann ändern.
Zuweisung also Setzen eines Wertes und der Zuweisungsoperator ist das
Gleichheitszeichen, was manchmal ein bisschen verwirrend ist, wenn man
noch nicht in der Sprache so drin ist.
Das Gleichheitszeichen bedeutet also Zuweisung, das Gleichheitszeichen
bedeutet nicht gleich, ist gleich im mathematischen Sinne.
Beispiele, wenn ich schreibe x gleich y, Semikolon, dann wird der Wert
der Variable x geändert, also immer auf der linken Seite, auch das
wieder Konvention, und bekommt den neuen Wert, und zwar den Wert, den
die Variable y vorher hatte.
Wenn also, bevor ich in diese Programmzeile komme, der Wert der
Variable in y 5 war, dann würde man mit dieser Zuweisung dann auch x
auf 5 setzen, also als konkretes Beispiel.
Anderes Beispiel wäre dieses hier, ich schreibe x gleich x plus 1, das
bedeutet, ich nehme den bisherigen Wert von x, addiere 1 drauf und
setze die Variable x dann auf den neuen, nun 1 größeren Wert.
Beispiel, wie sowas ausgewertet werden könnte, hier unten in der
Tabelle.
Wenn ich jetzt also mal, einfach als Beispiel, das hat jetzt keinen
bestimmten Hintergrund, von der Situation ausgehe, dass ich zwei
Variablen habe, x und y.
Initial haben diese Variablen, wenn ich in mein Stück Programmcode,
was ich jetzt hier im Folgenden zeige, reinlaufe, haben die zum
Beispiel die Werte x, x hat den Wert 10 und y hat den Wert 6.
Wenn ich dann diese Ausführung hier ausführe, diese Anweisung hier
ausführe, was passiert dann mit meinen Variablen, um das einfach mal
hier darzustellen.
Wenn ich ausführe x gleich x plus 1, würde sich der Wert von y nicht
ändern, sondern nur der Wert von x, das wird um 1 höher gezählt.
Wenn ich dann ausführe x gleich y, wird, wie wir es hier eben gerade
oben gesehen haben, der Wert von x gesetzt auf den Wert von y.
Also ich nehme mir die 6 sozusagen von links rüber und gebe auch dem x
den Wert 6.
Und wenn ich jetzt diese letzte Anweisung hier ausführe, y gleich 2
mal x plus 1, würde also jetzt das y einen neuen Wert bekommen, weil
das links auf der Zuweisung steht.
x hat also weiter den Wert 6, den ich gerade eben produziert hatte und
y würde jetzt den Wert 13 bekommen, 6 mal 2, 12 plus 1, 13.
Also Sie sehen, der Zuweisungsoperator sagt immer, dass die Variable,
die links von dem Zuweisungsoperator steht, ihren Zustand, ihren Wert
ändert.
Oder im Ausnahmefall auch nicht ändert, wenn es gerade zufällig den
gleichen Wert schon hatte.
Das gibt es auch natürlich, aber prinzipiell der auf der linken Seite
könnte sich dadurch ändern.
Wichtig dabei zu bedenken, diese Notation, und vielleicht hat Sie
dieses hier auch gerade schon gewundert, x gleich x plus 1 hat eben
nichts mit mathematischer Gleichheit zu tun.
Das würde ja auch gar keinen Sinn machen, das könnte nie erfüllt sein
an dieser Stelle.
Und es ist auch nicht symmetrisch, wie es vielleicht aussieht, wenn
ich also x gleich y schreibe, ist das nicht das gleiche wie y gleich
x, weil eben im ersten Fall der Wert der Variable in x geändert wird
auf den Wert der Variable y und im zweiten Fall der Wert der Variable
y geändert wird auf den Wert, den x zu dem Zeitpunkt gerade hat.
Also da einmal aufpassen, wenn man jetzt tatsächlich Gleichheit im
mathematischen Sinne abfragen möchte, erinnern Sie sich an die
Operatoren aus der vorherigen Vorlesung, da gab es dann dieses
doppelte Gleichheitszeichen, was dann tatsächlich prüft, ob zwei Werte
gleich sind und was dann auch wieder symmetrisch ist.
Okay, wir haben jetzt gerade Zuweisungen von ganz normalen Variablen,
so lokalen Variablen, die ich einzeln da liegen habe, gesehen.
Wir können auch Attribute von Objekten setzen, also ihnen Werte
zuweisen.
Das macht man dann, indem man die durch Objektname Punkt Attributname
anspricht, sofern die Attribute sichtbar sind, kann ich jetzt schon
mal einschränkend sagen, aber Sichtbarkeit kommt auch erst ein
bisschen später in der Vorlesung.
Im Moment in unserem Beispiel sind die das noch alle.
Wenn ich jetzt also zum Beispiel ein neues Objekt vom Typ Vector2D,
also zweidimensionaler Vektor, erzeuge und das in der Variable p
ablege, referenziere, kann ich danach dann schreiben, p Punkt x ist 4
und weise also dem Attribut x dieses Objekts den Wert 4 zu und kann
danach auch schreiben, p Punkt y gleich 22,5 würde also dem Attribut y
meines Objekts, was ich mit p referenziere, den Wert 22,5 zuweisen.
Was ich dann also am Ende rausbekomme, wäre sowas.
Dieses Objekt, was wir mit p referenzieren, hätte diese beiden Werte
als Attribute.
Ähnlich, wenn ich ein zweites Objekt erzeuge und das über den
Variablennamen q referenziere und dann q Punkt x entsprechend q Punkt
y hier die Werte zuweise, würde ich also ein zweites Objekt am Ende
dieser Ausführung haben mit den entsprechenden Werten als Attributen.
Hoffentlich einsichtig.
Ein bisschen mehr zu Variablen, was dahinter steht.
Wir hatten ja gesagt, abstrakte Behälter sozusagen für Werte, mit
denen ich innerhalb meines Programms rechnen möchte.
Das ist also ein, wir können auch sagen, Name für einen Speicherplatz.
Hauptspeicher ist dann das, wo die Werte dann auch tatsächlich zur
Laufzeit meines Programms drin liegen.
Und dieser abstrakte Behälter hat entsprechend diese drei Teile.
Ich habe einmal den Namen meiner Variablen.
Ich habe den Typ meiner Variablen.
Bei Java ist immer klar, was auch der Typ einer Variablen ist.
Und ich habe einen Wert, das können Sie sich dann eben vorstellen, als
Gefäßinhalt dieses abstrakten Behälters.
Wenn ich jetzt also eine Zuweisung ausführe, heißt das, dass ich jetzt
an dieser Speicherstelle in diesem abstrakten Behälter, der durch
meine Variable bezeichnet wird und der einen bestimmten Typ hat, einen
neuen Wert ablege.
Also wenn ich vorher sowas hatte wie hier, Variable x mit Wert 0 ist
ein Integer und ich schreibe dann hin, x ist 4295, dann wird nachher,
nachdem diese Zuweisung ausgeführt wurde, eben der Wert 4295 in meinem
abstrakten Behälter als Wert stehen.
Ein bisschen weiter ins Detail.
Wie sieht das im Speicher genau aus?
Wir können uns das für diese Diskussion jetzt mal vorstellen, dass der
Hauptspeicher einfach aus einer Reihung von Speicherplätzen, wie wir
es hier dargestellt haben, einer pro Variable besteht.
Das ist, oder vielleicht erst mal dazu gesagt, diese Speicherplätze
sind dann durchnummeriert, das nennt man Speicheradressen, dass man
immer sagen kann, aus welchem Speicher, aus welchem Feld möchte ich
denn jetzt was rauslesen.
Ist hier eine etwas abstrakte Sicht, das wäre hier die nächste
Anmerkung, dass wir jetzt sagen, es ist immer eine Speicheradresse für
genau eine Variable da, weil in Wirklichkeit diese Speicheradressen
quasi kleinere Bereiche bezeichnen und ich zum Beispiel einen
Integerwert nicht in eine Speicheradresse schreibe, sondern in vier,
weil der einfach ein bisschen größer ist als ein Byte, wie Speicher
typischerweise groß sind.
Auch nicht bei allen Rechnern, aber typischerweise.
Also von daher nur der Hinweis, das ist jetzt hier nicht ganz akkurat
dieses Bild, aber für unsere Zwecke an dieser Stelle ausreichend.
So würde das dann also aussehen, dass ich halt sage, ich habe die
verschiedenen Variablen da im Speicher liegen.
Und interessant wird es sich dann gerade anzuschauen, wie sieht das
dann bei Objektvariablen und Referenzen aus, also mit unserem Beispiel
gerade p.x und p.y, wo liegen die dann eigentlich, das sind ja dann
mehrere Gefäße oder vielleicht auch doch nur ein Gefäß, schauen wir
uns im Folgenden an.
Bei Objekten ist es so, dass die Attribute an aufeinanderfolgenden
Stellen im Speicher abgelegt werden, also wenn wir jetzt unser
Vector2D -Beispiel haben, was nur die Attribute x und y hat, würden
wir irgendwo im Speicher hintereinander diese beiden Speicheradressen,
die x und y bezeichnen, liegen haben und die Werte, die diese
Attribute gerade aktuell haben, würden in diesen Speicherstellen
liegen.
Das wäre sozusagen das Objekt selber, was dann also hier im Speicher
liegt.
Wenn ich jetzt eine Variable habe, die auf ein Objekt zeigt, also wie
in unserem Beispiel, wir hatten eben die Beispiele p und q als
Referenzen auf Objekte.
Hier kann ich auch genauso gut sagen, ich habe eine Variable p, o, s
für Position.
Dann ist das tatsächlich eine Referenz auf ein Objekt, was diese
Variable speichert.
Das heißt, in der Objektvariable, das wäre dann diese Variable hier,
p, o, s, wird die Speicheradresse abgelegt, wo dann das eigentliche
Objekt liegt.
Genauer gesagt, wo das erste Attribut dieses Objektes liegt.
Wenn ich jetzt also die Situation habe, dass ich ein neues Objekt
erzeugt habe vom Vektor 2D und dann geschrieben habe p, o, s gleich
dieses neue Objekt, womöglich habe ich geschrieben Vektor 2D p, o, s
gleich new Vektor 2D, dann habe ich die Werte von x und y zugewiesen.
Da würde danach mein Speicher so aussehen.
Ich hätte also an irgendeiner Stelle diese Variable p, o, s liegen,
die vom Wert Vektor 2D ist und das bedeutet, der Wert, der hier
eigentlich drinsteht, zeigt auf die Speicheradresse, die hier als Wert
eben drinsteht, also hier zum Beispiel die 17 und an dieser
Speicheradresse liegt dann das eigentliche Objekt, was gemeint ist.
Das ist recht wichtig zu wissen, also von daher vielleicht der
Einstein hier gar nicht so angebracht, nicht nur für Experten wichtig
zu wissen, weil da nämlich sonst komische Dinge passieren können, wenn
man sich das nicht bewusst ist, dass das so ist.
Einfach, um das erstmal noch zu verdeutlichen, folgende Situation,
wenn wir jetzt also anfangen und sagen, p ist gleich new Vektor 2D mit
bestimmten Werten, das ist jetzt hier das Beispiel, ich glaube, das
haben wir in der Klasse selber noch gar nicht gesehen, dass wir die
Initialwerte von diesen x- und y-Attributen schon gleich bei der
Erstellung des Objektes mit übergeben, Stichwort Konstruktor, aber das
kommt dann eigentlich auch erst später noch.
Können Sie sich aber, glaube ich, an dieser Stelle vorstellen.
Q ist ein neues Vektor 2D-Objekt mit den x- und y-Werten 1,2 und 9,4.
Wenn ich dann noch sage, r, auch eine Variable vom Typ Vektor 2D, ist
gleich p, dann würde ich danach die folgende Situation hier im
Speicher haben.
Ob das jetzt genau an diesen Stellen liegt, ist natürlich nicht
gesagt, aber so eine oder so eine ähnliche Situation im Speicher,
nämlich, dass ich diese drei Variablen habe, p, q und r, dass ich dann
hier das erste erzeugte Objekt habe, wenn ich das jetzt richtig sehe.
Genau.
Und hier das zweite erzeugte Objekt mit den entsprechenden
Attributwerten und entsprechend diese Variablen, die die Objekte
anscheinend bezeichnen, tatsächlich nur Referenzen auf diese Objekte
sind.
In p würde jetzt also drinstehen 17, weil das Objekt, was gemeint ist,
an Speicherstelle 17 liegt.
In q würde drinstehen 20 jetzt in diesem Beispiel, weil das Objekt,
was eigentlich gemeint ist, an Speicherstelle 20 liegt.
Und interessant auch noch, was passiert, wenn ich r gleich p setze,
wollte ich gerade sagen, nein, das ist natürlich gerade verkehrt, wenn
ich dem r den Wert der Variablen p zuweise, so wäre es in lang
gesprochen, dann bekommt nämlich r diese Variable vom Typ Vector2D
auch den Wert 17, weil diese Variable jetzt also auf das gleiche
Objekt zeigt, auf das auch p gezeigt hat.
Also hier wird gar nichts kopiert oder so, sondern es wird einfach die
Speicheradresse nur übernommen.
Der Wert der Variablen, wenn ich eine Objektreferenz habe, ist einfach
die Speicheradresse, wo das Objekt drinsteht.
Wenn ich dann also zum Beispiel die Zuweisung p gleich q machen würde,
ändert sich auch gar nicht so viel.
Ich würde einfach hier dieser Variablen p den neuen Wert 20 zuweisen,
das heißt p ist jetzt keine Referenz mehr auf die Speicheradresse 17,
sondern p ist jetzt eine Referenz auf die Speicheradresse 20.
Und die Objekte, da wundert man sich vielleicht, bleiben einfach
unberührt.
Die bleiben so.
Objekte mit ihrer Identität bleiben einfach so im Speicher liegen und
werden gar nicht geändert, sondern nur die Referenz auf das Objekt
wird geändert.
Und daher habe ich die Beobachtung hier, mehrere Referenzen, mehrere
Variablen können auf dasselbe Objekt, das heißt dieselbe
Objektidentität verweisen, weil sie eben einfach die Objektidentität,
die Speicheradresse als Wert in ihrem Abstraktbehälter, sage ich jetzt
wieder, liegen habe.
Und dieses Objekt selber, was an der Speicherstelle 17 liegt, wird
also von dieser Zuweisung p, bekommt den neuen Wert von q nicht
verändert.
Was vielleicht auf den ersten Blick jetzt nicht das ist, was man
erwartet hätte, wenn man das sieht, weswegen man eben dieses
Speichermodell im Hintergrund ein bisschen kennen muss, um
nachvollziehen zu können, was denn passiert, wenn man mit Objekten
umgeht.
Hier das Ganze nochmal ähnlich dargestellt eigentlich.
Wenn wir jetzt mal vor diesem bandartigen Speicher weggehen und uns
die Objekte einfach hier so vorstellen oder die Referenzen, ich habe
hier eine Speicheradresse und dann habe ich da den Namen meiner
Variablen und den Wert meiner Variablen drinstehen.
Dann habe ich also eine Menge von Speicheradressen, die
Objektreferenzen darstellen, die also Variablen vom Typ des jeweiligen
Objektes, also der Klasse sind.
Und ich habe andere Speicherstellen, wo die Objekte selber drin
liegen, die Objektidentitäten an den Speicherstellen.
Wenn ich jetzt also diese Situation hier vorher hatte, eben wie wir es
gerade hatten, P und R zeigen auf das erste Objekt, Q zeigt auf das
zweite Objekt an Speicherstelle 20 und dann diese Zuweisung mache, der
Wert von Q wird P zugewiesen.
Dann hätte ich also nachher diese Situation, das Einzige, was sich
verändert hat, ist hier der Wert von P, also die Information, auf
welches Objekt der Zeiger P oder die Objektreferenz P hier zeigt.
Das ist dann nicht mehr die 17 wie vorher, sondern dann jetzt die 20,
weil Q eben auch den Wert 20 hat.
Das wird also wirklich einfach nur der Wert von hier nach hier
übernommen, wie das auch bei den Integerzuweisungen der Fall war.
Und es werden eben nur die Referenzen verändert, das kann man gar
nicht genug betonen.
Was passiert jetzt aber, wenn ich diese Objektreferenz.Attributnamen
-Notation benutze in der Zuweisung?
Dann wird es wieder interessant, gehen wir also wieder davon aus, ich
bin in dieser Startsituation, P und R sind Referenzen auf das Objekt
17, Q ist eine Referenz auf das Objekt 20.
Ich nenne es jetzt mal einfach 17 und 20, wir haben ja keinen anderen
Namen definiert.
Wenn ich dann sage, der Wert 7,5 soll P.x zugewiesen werden, dann
führt diese Punktnotation hier, der Punktoperator dazu, dass ich halt
über die Referenz P zu dem Objekt gehe, mir da das Attribut x hernehme
und da diesen Wert, der hier auf der rechten Seite steht,
reinschreibe.
Es ändert sich also in diesem Fall tatsächlich dann das Objekt, also
die Attribute des Objektes, der Zustand des Objektes ändert sich und
zwar des Objektes, was von der Variablen P referenziert wird.
Also wie gesagt, ich gehe hier hin, P, durch den Punktoperator gehe
ich zu dem Objekt, was referenziert wird, finde dann da mein x und
kann dem x den neuen Wert zuweisen.
Das kann auch wieder verwirrend sein, deswegen sollte man das hier im
Hinterkopf haben, wenn man mit Objekten arbeitet, denn wenn ich jetzt
nur das R anschaue oder wenn ich jetzt in der nächsten Zeile mir auf
der Konsole ausgeben lassen würde, was denn jetzt der Wert von R.x
ist, würde ich auch hier natürlich wieder die 7,5 bekommen, weil es
nur ein Objekt gibt und zwei Referenzen auf dieses Objekt in diesem
Fall.
Das nennen sich dann Aliasing, weil quasi dieses Objekt, dieses eine
Objekt, was nur tatsächlich vorhanden ist, eigentlich zwei Bezeichner
hat, wie man das ja im Personalausweis auch stehen hat.
Wenn jemand einen Alias hat, dann hat er zwei Namen, die aber die
gleiche Person bezeichnen, also auch hier zwei Referenzen, zwei
Variablen, die das gleiche Objekt bezeichnen.
Nach dieser Zuweisung, wie ich gerade schon gesagt habe, gilt dann
also auch, dass R.x den Wert 7,5 hat.
Hier jetzt also gleich gleich im Sinne von, das wird zu wahr
ausgewertet.
Okay, wie gesagt, den Burschen hier hätte ich vielleicht auch besser
entfernen können, weil das ist jetzt auch nicht nur für die Experten
unter Ihnen relevant, diese Überlegung, wie das dann mit den
Objektidentitäten und den Zuweisungen aussieht, sondern auch für alle
anderen zumindest am Ende dieser Vorlesung, also am Ende des
Semesters.
Was gibt es noch zu Objektreferenzen zu sagen und Variablen?
Einfach noch, dass Objektvariablen, Objektreferenzen auch den
speziellen Wert 0 annehmen können.
Ich kann also schreiben, Vector2DP bekommt zugewiesen den Wert 0.
Das soll einfach nur heißen, ich habe schon diese Referenz, aber ich
sage jetzt noch nicht, auf welches Objekt das zeigen soll.
Das ist noch eine leere Referenz sozusagen.
Steht für kein Objekt und ich kann dann auch im Folgenden nicht p.x
schreiben und dem neuen Wert zuweisen, weil es einfach noch kein
Objekt gibt, was dahinter steht.
Das Objekt selber wird dann erst erzeugt, wird erst instanziiert, sagt
man, wenn man diesen new Operator benutzt.
Ich kann also zum Beispiel dann in der ersten Seite dieses hier
schreiben, in der zweiten Seite dann schreiben p gleich new Vector2D.
Und mit dieser unteren Anweisung kann ich jetzt also ein neues Objekt
vom Typ Vector2D erzeugen, also in meinem Speicherplatz sowas hier neu
erzeugen.
Das hätte jetzt noch die Werte 0, x und y und kann dann sagen, dass p
auf dieses neue Objekt zeigen soll.
Also auch die Möglichkeit, 0 hier zu haben.
Was anderes, was da eigentlich weniger mit zu tun hat, aber einfach
noch unter diesem Bereich Variablen, wie gehe ich damit um, fällt,
wäre die Konstantendeklaration.
Ich kann ähnlich zu Variablen, wo ich ja gerade sage, das sind Werte,
das sind Behälter, wo ich auch mal zwischendurch was Neues
reinschreiben will und die ich auslesen möchte sowieso, aber in die
ich auch zwischendurch was Neues reinschreiben will, kann ich auch
Konstanten deklarieren.
Das sind dann also spezielle Arten von Variablen, wobei das Wort
Variable eben da nicht so viel Sinn macht, die sich dann nicht mehr
ändern können, wo ich also initial einmal den Wert vorgebe und dann im
Folgenden diesen Wert aber nicht mehr ändere.
Also Variablen, aus denen ich nur lese, könnte man vielleicht sagen.
Das ist in Java durch diesen Zusatz Final markiert.
Wenn ich also schreibe Final und dann ein Datentyp und einen Namen
meiner Variable gleich Wert, dann heißt das, dass ich jetzt eine
Konstante deklariert und gleich initialisiert habe auf einen
bestimmten Wert.
Das bietet sich an für so Dinge, die sich einfach nicht ändern in
meinem Programmablauf, wie zum Beispiel, was denn jetzt die
Schwerkraft der Erde ist oder was der Wert von Pi ist zum Beispiel.
Das wird sich in meinem Programmablauf nicht ändern.
Ich muss nicht während meines Programmablaufs da neue Sachen
reinschreiben in diese Variable, sondern ich kann dann gleich angeben,
dass sich dieser Wert im Laufe meines Programms nicht mehr ändern
wird, dass es eben eine Konstante ist.
Ist dann hilfreich, hilft auch wieder fürs Verständnis und macht es
auch dann bei der Ausführung ein bisschen effizienter, wenn man hier
weiß, dass sich das sowieso nicht mehr ändern kann.
Okay.
Hatte ich schon mal gesagt, noch abschließend zu Datentypen.
In Java müssen alle Variablen einen Datentyp haben.
Ich kann nicht eine Variable irgendwie offen lassen und erst später
mal definieren, was der Datentyp sein wird oder ich würde auch nicht
sagen, dass sich der Datentyp erst irgendwie zur Laufzeit ergibt oder
so, sondern in Java haben alle Variablen, wenn ich sie deklariere,
auch gleich einen Typ und der muss auch zur Compile-Zeit bekannt sein.
Und der Compiler und auch die virtuelle Maschine überprüfen dann auch
immer wieder, ob der Typ zusammenpasst.
Und wie wir ja einleitend schon gesagt haben, die erlaubten
Operatoren, was ich dann also mit meiner Variable machen kann, hängt
vom Datentyp ab.
Das macht es eben so schön, weil ich zum Beispiel schon der Compiler
dann feststellen kann, wenn ich A plus B schreibe und A und B, also
Strings ist ein böses Beispiel, aber sind irgendwelche Datentypen, auf
denen das Plus nicht definiert ist, dann kann schon der Compiler
gleich sagen, nee, das geht aber nicht und ich werde diesen Fehler
nicht erst zur Laufzeit bemerken.
Ich habe also hier so eine sogenannte Typsicherheit.
Okay, zusammenfassend für diesen Teil der Vorlesung.
Wir haben eben die Typen letzte Woche uns angeschaut, elementare
Datentypen, Klassen sind auch Datentypen und an Datentypen hängen eben
Operationen dran, die möglich sind.
Und wir haben uns dann heute den Themenbereich Variablen angeschaut.
Variablen, Namen für Speicherplätze oder abstrakte Behälter, so können
Sie sich das so oder so vorstellen.
Variablen kann ich durch Zuweisungen ändern.
Zuweisungen sind nicht Gleichheitsabfragen, sondern eben Zuweisungen.
Bisschen speziell sind noch die Objektvariablen, weil die tatsächlich
Referenzen auf eine andere Speicherstelle sind, wo dann die Werte,
wiederum die Variablen, die Attribute meines Objekts drin liegen.
Und der spezielle Wert 0, den gibt es auch noch für kein Objekt.
Gut, soviel zu diesem Themenbereich, was aber nicht alles ist für
heute.
Sie würden sich sonst auch wundern wahrscheinlich, sondern wir können
dann gleich weitermachen mit dem nächsten Thema, nämlich den
Kontrollstrukturen.
Wir haben jetzt also gesehen Variablen und Datentypen und Sie haben
immer so einzelne Zuweisungen gesehen, aber das ist natürlich noch
relativ wenig, was ich damit machen kann.
Also ich kann damit kein Programm schreiben, ich könnte damit noch
keine Personen sortieren, wie wir das in unserem einleitenden
motivierenden Beispiel gesehen haben.
Kontrollstrukturen, auch da kurz der Hinweis, wo Sie da mehr im
Fließtext auch nochmal zu lesen können.
Das ist ein Abschnitt in dem Buch von Dietmar Ratz und anderen.
Und bevor wir uns jetzt die Kontrollstrukturen wie Bedingungsabfragen
und Schleifen tatsächlich angucken, erstmal ein bisschen grundlegend
zwei Begriffe, nämlich Ausdrücke und Anweisungen in Java.
Es gibt in Java eben Ausdrücke auf Englisch, Expressions, aus denen
ich mein Programm zusammensetzen kann, sozusagen.
Die kommen vor zum Beispiel auf der rechten Seite von Zuweisungen.
Wir hatten eben immer das Beispiel sowas wie x gleich y oder x gleich
5.
Allgemein kann man bei Zuweisungen x gleich, also Variabelnahme
gleich, auf die rechte Seite einen sogenannten Ausdruck schreiben.
Ich kann Ausdrücke auch schreiben als Argumente von Methoden.
Wenn ich also eine Methode f habe, ich rufe die auf mit verschiedenen
Parametern, dann kann an jeder Stelle für jeden Parameter auch ein
Ausdruck wiederum stehen.
Oder ich kann Ausdrücke verwenden als Rückgabewerte von Funktionen.
Wenn also eine Funktion irgendein Ergebnis zurückgeben will, gibt es
das Schlüsselwort return, zurückgeben und dahinter steht dann auch ein
Ausdruck.
Ein Ausdruck ist einfach etwas, eine Berechnung, die einen bestimmten
Wert hat.
Und Ausdrücke können eben unterschiedlich aussehen.
Zum Beispiel hier gezeigt, sowas hier, ohne die Anführungszeichen, die
müssen Sie sich jetzt wegdenken, sonst wäre es ein String, wäre ein
Ausdruck.
Wenn ich also schreibe, Klammer auf, 3.0 plus Y, Klammer zu, mal 4.0,
angenommen, dass Y schon eine definierte, zum Beispiel Double-Variable
habe, ist dann wiederum dieses Ganze hier ein Ausdruck vom Typ Double.
Denn ich habe hier verschiedene Doubles, die ich mit den erlaubten
Operatoren miteinander verrechne.
Das heißt, das Ergebnis dieses Ausdrucks ist auch wieder ein Double
oder es ist ein Ausdruck vom Typ Double.
Ein anderes Beispiel für ein Ausdruck wäre sowas, was wir auch schon
gesehen haben, New Vector 2D mit einer bestimmten Initialisierung.
Das wäre dann ein Ausdruck vom Typ Vector 2D.
Oder wenn ich sowas schreibe, X gleich gleich, ich sage jetzt mal
wieder die einzelnen Zeichen, 2, also ein Vergleich auf Gleichheit
zwischen X und 2, ist ein Ausdruck vom Typ Boolean, also ein Ausdruck
vom Datentyp Wahrheitswert.
Solche Ausdrücke nennt man dann auch Bedingung.
Also ein Ausdruck immer etwas, im Prinzip eine Berechnungsvorschrift,
wie dieses hier oder auch dieses hier, irgendetwas, was hingeschrieben
wird, ausgedrückt wird und was sich dann zu einem Wert, der einen
bestimmten Typ hat, ausgewertet werden kann.
Was gibt es sonst noch für Ausdrücke in Java?
Variablen selber, also eine einzelne Variable ist auch ein Ausdruck,
vielleicht mal hier zurückgegangen, wenn wir also hier das Beispiel
aus dem vorherigen Foliensatz hätten, X gleich Y wäre Y, dieser
zugegebenerweise sehr einfache Ausdruck, der einfach nur den Wert von
Y und den Typ von Y hat.
Attribut kann ich genauso nehmen, auch eine Konstante kann auf dieser
Seite einer Zuweisung zum Beispiel stehen.
Ein Ausdruck kann ein Methodenaufruf sein, das soll an dieser nächsten
Stelle hier gezeigt werden, also wenn ich eine Methode namens f
irgendwo definiert habe, ist, wenn ich f mit seinen Parametern, diesen
Aufruf an f irgendwo hinschreibe, ist das wiederum auch ein Ausdruck.
Ich kann zwei Dinge, die Ausdrücke sind, mit den binären Operatoren,
die wir letzte Woche kennengelernt haben, zusammennehmen, also diesen
Operator ausrechnen, dann ist das Ergebnis wieder ein Ausdruck.
Wenn ich also Ausdruck, Operator, Ausdruck schreibe, ist das Ergebnis
hier wieder ein Ausdruck.
Wenn ich unnähere Operatoren habe, wie zum Beispiel Minus, dass ich
einfach die entsprechende negative Zahl haben will, ist also Minus,
Ausdruck, auch wieder ein Ausdruck.
Und wie wir gerade eigentlich auch schon gesehen hatten, der New
Operator mit einer Klassenbezeichnung dahinter ist auch ein Ausdruck,
er hat als Ergebnis dann einen Wert, also ein Objekt dieser hier
definierten Klasse.
Es gibt noch ein paar Spezialausdrücke in Java, einer ist InstanceOf,
mit dem ich überprüfen kann, ob eine bestimmte Objektreferenz oder
beziehungsweise das Objekt, was von dieser Referenz referenziert wird,
eine Instanz einer bestimmten Klasse ist.
Beispiel hier unten, ich kann also schreiben P InstanceOf Vector2D,
das Ganze, dieser Ausdruck insgesamt ist dann vom Typ Wahrheitswert,
weil das Ergebnis kann entweder wahr oder falsch sein, entweder ist P
vom Typ Vector2D oder nicht.
Genau, das ist also auch ein Ausdruck, diese Kombination dann, hier
Variablename, hier Klassenname und InstanceOf dazwischen geschrieben.
Ein weiterer spezieller Ausdruck ist diese Kombination aus
Fragezeichen und Doppelpunkt, sogenannte bedingter Ausdruck, da kann
ich also sowas schreiben wie hier, ich kann sagen, wenn x gleich 3
ist, dann soll das Ergebnis meines Ausdrucks 5 sein, sonst soll das
Ergebnis meines Ausdrucks x plus 2 sein.
Das ist also eine Kurzschreibweise für das, was wir gleich noch sehen
werden, für eine Bedingung, für eine Fallunterscheidung.
Kurzschreibweise, dass man einfach sagt, wenn dieser Teil hier, was
immer zu dem Wahrheitswert sich auswenden muss, gegeben ist, dann ist
das Ergebnis meines Ausdrucks 5 und sonst x plus 2.
Ich könnte jetzt also hier davor schreiben, y gleich und würde diesen
Ausdruck dann zum Beispiel der Variablen y zugreifen.
Und schließlich eine weitere Art von Ausdruck ist die sogenannte
Typumwandlung in Java, wo ich einfach eine Variable hier hinschreibe
oder auch einen anderen Ausdruck und dann mit Klammer auf, Typ,
Klammer zu, den Typ ändere von diesem Wert.
Wenn ich jetzt also zum Beispiel hinschreibe 2,4 und das umwandle zu
einem Integer, würde ich als Wert dieses Ergebnisausdrucks eine 2
bekommen.
Da würde dann einfach der Gleitkommateil abgeschnitten werden, die
nächste Ganzzahl oder die niedrigere ganze Zahl gesucht werden und
dieser Ausdruck hätte dann den Wert bei.
Okay.
Ausdrücke also, Dinge, Berechnungen, die einen Wert ergeben.
Damit kann ich dann auch noch nicht so richtig was machen.
Wir hatten gerade schon die Zuweisungen kennengelernt, aber das sind
gerade keine Ausdrücke, sondern damit kann ich erstmal nur
Berechnungen machen und habe dann irgendwie ein Ergebnis in der Hand,
wo ich wiederum etwas typischerweise machen möchte, zum Beispiel das
einer Variable zuweisen.
Deswegen gibt es noch eine weitere Klasse von Elementen meines
Programmcodes, nämlich Anweisungen auf Englisch, Statements.
Anweisungen sind etwas anderes.
Anweisungen veranlassen eine Zustandsänderung meines Programms.
Also ein Beispiel haben wir schon gesehen, eine Zuweisung.
Ich weise einer Variable einen Wert zu.
Anweisungen haben gegenüber zu Ausdrücken eben keinen Typ, sondern sie
ändern im Gegensatz dazu den Zustand meines Programms.
Da gibt es in Java auch wieder ganz viele Anweisungen.
Hier so die wichtigsten.
Eine Anweisung wäre zum Beispiel, eine lokale Variable zu deklarieren,
also int x, Semikolon.
Das heißt, es soll jetzt irgendwo dieser abstrakte Behälter angelegt
werden vom Typ Integer mit dem Namen x.
Es ändert sich also was in meinem Speicher, deswegen eine Anweisung.
Eine Zuweisung, was wir eben schon gesehen haben, wäre eine Anweisung,
dass x jetzt den Wert von y bekommen soll.
Ein Methodenaufruf ist eine Anweisung, einfach nur eine normale
Anweisung, wenn sie keinen Rückgabewert hat.
Das kann man jetzt an dieser Methode nicht erkennen, deswegen dahinter
geschrieben.
Wir nehmen jetzt mal an, diese Methode hat keinen Rückgabewert.
Dann ist einfach dieser Aufruf der Methode auch eine Anweisung,
gefolgt von Semikolon.
Hier wieder so eine zusammengesetzte Definition, wenn ich einfach eine
Menge von Anweisungen schon habe, die irgendwelche Anweisungen sein
können und da Klammern drumherum setze, bekomme ich eine sogenannte
Blockanweisung.
Ich habe also mehrere Anweisungen zusammengenommen in einen Block und
dieser Block selber ist auch wieder eine Anweisung.
Und Rückgabewerte, haben wir auch schon kurz gesehen, sind auch
insgesamt eine Anweisung und hier schon ein gewisser Bezug zu den
Ausdrücken.
Wenn ich also sage return und dahinter irgendein Ausdruck schreibe,
dann ist das insgesamt eine Anweisung, weil sich eben was an dem
Zustand meines Programms ändern soll.
Die Methode, aus der Methode wird rausgegangen, das hat dann auch
Einfluss auf den Speicher.
Ich nehme die Variablen, die sich innerhalb der Methode, die ich als
lokale Variablen verwendet hatte, dann wieder weg.
Ich lösche sie, entferne sie, deswegen das auch eine Anweisung.
Genau, wir werden auch, heute wahrscheinlich nicht mehr, aber dann
beim nächsten Mal, verschiedene Control-Flow-Statements, also
verschiedene weitere Anweisungen, die es mir ermöglichen, den Control
-Fluss meiner Anwendung zu definieren, dann auch noch kennenlernen.
Was ist jetzt also hier der Unterschied zwischen Ausdrücken und
Anweisungen, was Sie eben so als elementare Bausteine eines Programms
kennen sollten?
Ein Ausdruck hat einen Typ, eine Anweisung hat keinen Typ.
Ein Ausdruck ist also so eine Art Berechnung, ich sage, hier soll aus
verschiedenen Variablen, durch Methoden, Aufrufe, wie auch immer, ein
Wert berechnet werden.
Es liefert also einen Wert.
Bei der Anweisung ist es eher so zu verstehen als Mache, deswegen
heißt es ja auch Anweisung auf Deutsch.
Ich weise meinen Computer in meinem Programm an, etwas zu tun und das
heißt, ich ändere den Zustand meines Weichers im Endeffekt.
Von der Syntax kann ich die beiden noch unterscheiden.
Ein Ausdruck hat keinen Semikolon, erstmal für sich.
Eine Anweisung beende ich immer mit Semikolon und kann ebenso
verschiedene einzelne Anweisungen voneinander trennen.
Ausdrücke sind dann im Endeffekt, oder wie ich mein Programm
zusammensetze, immer Teil einer Anweisung.
Und wenn ich mir Methoden anschaue, was wir ja auch schon mal vorher
gesehen hatten, ist der Rumpf jeder Methode ebenso eine Folge von
Anweisungen.
Auch da habe ich wieder die geschweiften Klammern, ähnlich wie bei der
Blockanweisung.
So schon vielleicht kompliziert genug.
In Java gibt es dann noch die Situation, dass es auch so eine Mischung
gibt im Prinzip aus Anweisungen und Ausdrücken.
Genauer gesagt Anweisungen, die ich auch als Ausdruck verwenden kann,
weil sie auch einen Wert zurückliefern.
Das sind eigentlich Anweisungen, also das heißt, sie ändern eigentlich
den Zustand.
Wie zum Beispiel dieses i++ hier.
Das Inkrementieren von i gleichzeitig liefert das i++ aber auch einen
Wert zurück.
Es liefert nämlich den neuen Wert von i auch gleich zurück, sodass ich
diese eigentlich Anweisung i++ auch als Teil eines Ausdrucks verwenden
kann oder selber schon ein Ausdruck ist.
Ich kann also zum Beispiel so etwas schreiben wie x soll den neuen
Wert zugewiesen bekommen y++.
Dann wird zum einen die Variable i verändert, das ist also wie gesagt
eine Anweisung.
Es ist aber gleichzeitig eine sogenannte Expression Statement, eine
Mischung aus Anweisungen und Ausdruck.
Und ich kann auch gleich mit diesem Ausdruck weiterrechnen an dieser
Stelle.
Ähnlich auch mit der Zuweisung selber.
Zuweisung haben wir ja gesehen, ist eigentlich eine Anweisung.
Aber auch bei der Zuweisung, wenn ich sie einfach so verwende als
Ausdruck, würde hier jetzt der Wert von y auch zurückgeliefert werden.
Also ich kann, anstatt jetzt zu schreiben y gleich z und in der neuen
Zeile zu schreiben x gleich y, kann ich das auch in eine Zeile
schreiben, weil eben dieser Teil hier, die eigentliche Anweisung, auch
wie ein Ausdruck verwendet werden kann.
Das testen wir gleich mal.
Mit ein paar Ja-Beispielen hier, Java-Beispielen.
Und der Frage, bei welchen dieser Schnipsel hier handelt es sich denn
um eine, ich sollte dazu sagen, um eine legale Anweisung.
Also eine Anweisung, die der Java-Compiler auch akzeptieren würde.
Das wäre dann die Umfrage Nummer 108 und da sollte ich wahrscheinlich
Ihnen einen Moment Zeit lassen, darüber nachzudenken.
Aber ich kann es auch währenddessen vorlesen.
Also das erste Fragment wäre p, Objektreferenz, ist ein neuer Vektor
2D und da rufe ich dann die Methode shift mit den Werten 1 und 2 auf.
Das zweite Beispiel wäre so ein Typcast des Methodenaufrufs der
Methode namens foo und den Parameter x.
Das dritte Beispiel wäre x, wird zugewiesen y, wird zugewiesen z.
Und das vierte Beispiel wäre i++, also Inkrement von plus in Klammern
und dann wieder plus plus dahinter.
Es gibt also zwei bis drei Favoriten.
Sie sagen, ah jetzt kann ich das wieder nicht sehen, b und d, oder
eher gesagt am ehesten d, zweiter Favorit b, wären hier die
Möglichkeiten.
Jetzt gehen wir nochmal wieder zurück auf die eigentliche Folie.
Ups, da muss ich jetzt aufpassen, dass ich hier nicht durcheinander
komme mit meinen Fenstern.
D und b.
Die beiden haben ja zum einen erstmal was gemeinsam, b und d, nämlich
dass 1 und 3 Anweisungen sind.
Da waren sich also relativ viele von Ihnen einig und das stimmt auch.
P ist eben eine Anweisung, die eine Zuweisung hier ist und der
Ausdruck auf der rechten Seite der Zuweisung ist hier jetzt einfach
ein bisschen komplizierter, ein bisschen länger.
Ich mache jetzt ein neues Objekt vom Typ Vector2D und rufe dann da
gleich auch noch eine neue Methode auf.
Diese Methode, wie wir jetzt hier sehen, hat einen Rückgabewert, der
dem Typ von P auch entspricht und dann wäre das eine richtige
Anweisung.
Achso, ich kann ja auch so drauf tippen.
Und das dritte haben Sie auch genau richtig gesehen, sieht ja im
ersten Moment vielleicht doch irgendwie verkehrt aus, aber nein, wie
wir gerade gesehen haben mit den Expression Statements, kann ich das
also gerade so schreiben, dass also hier zuerst ausgewertet wird, der
Wert von Z wird Y zugewiesen.
Das Ganze ist aber auch eben ein Expression Statement, das heißt, ich
kann es wieder als Ausdruck benutzen und der Wert, der da rauskommt,
nämlich der ursprüngliche Wert von Z, wird dann auch dem X zugewiesen,
einfach ein bisschen kürzer als zu schreiben Y gleich Z und in der
nächsten Zeile zu schreiben X gleich Y.
Dann gab es ein bisschen Hin und Her bei der Lösung Nummer 4, das geht
auch tatsächlich nicht, das heißt, das ist keine Anweisung, auch wenn
man das vielleicht im ersten Moment gedacht hätte, kann man selber
ausprobieren, wenn man das mal hinschreibt in dem Programm und dann
den Java Compiler drauf aufruft oder das gleiche in so etwas wie in
der Entwicklungsumgebung wie Eclipse macht, dann wird man einen Fehler
bekommen, weil das Plusplus nämlich ein Operator ist, der nur auf
Variablen angewendet werden kann.
Und den kann ich eben nicht auf so einem Expression Statement wie X++
anwenden, das geht an der Stelle nicht.
Sie waren sich alle einig, dass das Zweite hier keine Anweisung ist.
Wir hatten zwar gesehen, dass Methodenaufrufe, wo kein Rückgabewert
zurückkommt, auch solche Expression Statements oder solche Anweisungen
sein können, Anweisungen sein können sogar genauer, aber wenn ich da
eine Typumwandlung auf dem Ergebnis mache, dann ist das Ergebnis, was
wir hier sehen, erstmal tatsächlich wieder...
Moment, dann kann ich, so rum war es, gar keine Typumwandlung machen,
also ich würde an dieser Stelle sowas hier schon gar nicht schreiben
können, weil durch diese Typumwandlung das Ergebnis des
Methodenaufrufs zu einem Ausdruck werden könnte und ich diesen
Ausdruck so alleine nicht als Anweisung verwenden könnte.
Ich kann zwar schon Methodenaufrufe alleine schreiben als Ausdruck,
der dann auch als Anweisung beschrieben wird, insbesondere wenn sie
keinen Rückgabewert haben, aber auch sonst, dann wird der Rückgabewert
einfach ignoriert, aber dadurch, dass ich noch einen Typecast davor
mache, eine Typumwandlung davor mache, habe ich dann wirklich nur noch
einen Ausdruck und den kann ich nicht alleine als Anweisung
beschreiben.
Da würde aber auch schon, und das ist wieder das Schöne an Java,
gleich schon der Compiler sagen, nee, das geht so nicht, sodass man
solche Fehler, das sind ja wirklich auch Fehler im Detail, gleich auch
bemerken würde und der Compiler da gleich darauf hinweisen würde, dass
das in Java so nicht erlaubt ist, zu schreiben.
Okay, ein weiteres Quiz habe ich noch.
Ja,
das machen wir auch noch, auch wenn es von der Zeit her schon ein
bisschen knapp ist, aber ich finde es ganz nett.
Außerdem kommt die 42 drin vor, die mögen Sie ja auch sehr gerne, also
machen wir auch dieses Quiz.
Die kam auch schon vorher drin vor, kann ich dazu sagen, aber gut,
egal.
Also, wir haben zwei Programme, Programmschnipsel, die sich nicht
stark unterscheiden.
Also ich habe hier Integer x, y und z.
z kriegt den Wert 42, ich habe dann zwei Ausgaben, x wird der Wert 42
ausgegeben und dann eine weitere Ausgabe und die Frage ist eben, was
sind die Ausgaben, was würde ich sehen nacheinander, wenn ich da sowas
auf der Kommandozeile dann ausführe zum Beispiel.
Zweite Version des Programms, eigentlich fast gleich, außer dass ich
hier nicht Int schreibe, sondern Integer schreibe.
Integer ist ähnlich wie String, ein Objekt, also eine Klasse, die von
Java vorgegeben wird, Klasse Integer, die einen Integerwert nochmal
kapselt als Objekt.
Das ist also kein elementarer Datentyp, der direkt in diesen
abstrakten Behälter reingelegt wird, sondern ich erzeuge ein Objekt,
in dem Objekt liegt dann mein Integerwert und ich habe halt
Objektreferenzen auf diesen Integerwert.
Und, das macht es womöglich oder auch nicht, das ist hier die Frage,
vielleicht ein bisschen unterschiedlich hier, oder auch nicht.
Also die Frage, beide Programme geben drei Zeilen aus und welche
dieser Aussagen sind richtig?
Da müssten Sie jetzt, wenn ich mich jetzt recht an die Abstimmung
erinnere, Sie können pro Abstimmung quasi einen Wert auswählen hier an
der Stelle, das heißt, wenn Sie jetzt meinen, dass mehrere davon
richtig sind, dann können Sie einfach auch mehrfach abstimmen.
Dann verrate ich vielleicht schon, dass nicht nur eine von diesen
Aussagen richtig ist, sondern mehrere.
200 von Ihnen sagen B ist richtig, natürlich, das Programm muss 42
ausgeben, das wäre auch anderes denkbar und das ist die erste Ausgabe
auch richtig.
Genau, jetzt muss ich das hier wieder zurücksetzen auf das Quiz.
Ich könnte es ja gleich so auch auflassen.
Können Sie das so noch lesen oder ist das zu klein?
Zu klein höre ich.
Ich sehe es gut und ich höre zu klein, also mache ich es mal doch so.
Genau, was ist die erste Ausgabe des Programms?
Das ist dieses x gleich y gleich z.
Und es ist richtig, dass hier 42 rauskommt, da hatten wir eben gehört,
die Zuweisung ist ein Expression Statement, die kann ich also sowohl
als Ausdruck als auch als Anweisung benutzen.
Und es ist gerade kein Vergleichsoperator, es ist nicht das gleich
gleich, sondern es ist der Zuweisungsoperator, hier das gleich.
Das heißt, was hier passiert ist, z hat ja schon den Wert 42, y
bekommt durch diese Zuweisung auch den Wert 42.
Und das hier ist eben die Zuweisung, es wird übrigens von rechts nach
links ausgewertet, die Zuweisung, die ich auch als Expression
Statement benutzen kann.
Das heißt, diese Zuweisung hat sozusagen auch wieder einen Wert, mit
dem ich weiterrechnen kann, nämlich 42.
So, dass ich dem x hier auch gleich den Wert 42 zuweisen kann und das
Ergebnis ist auch wieder ein Expression Statement.
Das heißt, ich kann das auch wieder als Ausdruck, als Parameter dieser
Methode übergeben und quasi die 42, die dabei rauskommt, dieser
Methode zum auf die Konsole ausgeben, übergeben.
Und das Ergebnis ist dann, dass ich als erstes eine 42 als Ausgabe
bekomme.
Genau.
Also a ist nicht richtig, das hat sich ja auch schon ausgeschlossen.
Einige von Ihnen sagen, dass d auch noch richtig ist.
Andere, ein paar weniger sagen, dass c auch noch richtig ist.
Moment.
Also die zweite Aussage ist tatsächlich auch, die zweite Ausgabe, es
ist richtig, dass beide Programme an dieser Stelle true ausgeben.
Achso, wir haben jetzt im Moment nur auf das rechte Programm geschaut.
Ah, okay, egal.
Also hier geht es jetzt gerade um den Unterschied der beiden
verschiedenen Programme.
Hier hatte ich ja gerade schon erklärt, hier wird der Wert 42 einfach
diesen verschiedenen Integer-Variablen zugewiesen.
Hier habe ich ja ein neues Objekt vom Typ Integer erzeugt.
Hier wird also diese Objektreferenz, die Referenz auf das neue Objekt,
dem y zugewiesen und dann auch dem x zugewiesen.
Wenn ich jetzt an diese PrintLine-Methode ein Objekt übergebe, dann
wird im Hintergrund die sogenannte ToString-Methode aufgerufen, weil
es ist ja nicht klar, wie so ein Objekt dann aussieht, wenn ich es
ausgebe auf der Konsole.
Aber diese ToString-Methode macht dann eben auch wieder 42 draus.
Deswegen passt das jetzt an der Stelle noch.
Hier passt es auch noch, weil hier sage ich einfach nur x gleich
gleich z.
Also ich vergleiche hier auf der linken Seite, ob x und y den gleichen
Integer -Wert haben.
Das haben sie.
Da habe ich ja gerade eben erst gleich gesetzt.
Das heißt, hier kommt true tatsächlich raus bei dem ersten Programm.
Bei dem zweiten Programm habe ich hier einen Vergleich, ob die beiden
Objektreferenzen gleich sind, also ob die Speicheradressen, die Werte
dieser Variablen sind ja die Speicheradressen meiner Objekte, ob die
gleich sind.
Und da ich die aber sowieso gerade auf das gleiche Objekt gesetzt
habe, sind die auch gleich, diese Speicheradressen.
Und auch hier wird true ausgegeben.
Jetzt kommt aber ein Unterschied, wenn ich jetzt nochmal sage x gleich
42.
Auf der rechten Seite, wenn ich dann wieder x Gleichheit auf
Gleichheit prüfen z ausgebe, würde auf der linken Seite auch wieder
true rauskommen und es wird langsam langweilig an dieser Stelle.
Auf der rechten Seite wird es aber interessant, denn wenn ich jetzt
ein neues Objekt vom Typ Integer anlege, mit dem Initialwert 42 und
das der Variablen x zuweise und danach diese Überprüfung mache mit der
Gleichheit, wo ich dann also überprüfe, sind die beiden
Speicheradressen, die in den Variablen x und z drinstehen, gleich,
bekomme ich als Ergebnis ein false, weil x und y an dieser Stelle auf
zwei verschiedene Objekte zeigen.
Und hier wird also nicht geprüft, ob diese Objekte die gleichen Werte
in ihren Attributen haben, sondern mit dem Gleich-Gleich überprüfe ich
nur, ob diese beiden Variablen auf das gleiche Objekt zeigen.
Das wird also zu false ausgewertet.
Das heißt, d hier unten ist verkehrt.
Gut.
So viel dann auch zu den verschiedenen Ausdrücken und Anweisungen.
Dann haben wir hier an dieser Stelle geendet.
Nächstes Mal schauen wir uns dann die Kontrollflussanweisung an, aber
in vier Minuten ist das nichts.
Also schließe ich mal an dieser Stelle.