So.
Hallo und herzlich Willkommen auch zur heutigen Vorlesung
Programmieren.
Bevor ich heute mit dem eigentlichen Inhalten anfange, wollte ich
Ihnen zunächst nochmal einen Gesamtüberblick über das ganze Modul
geben und dann auch die heutige Vorlesung und die vom letzten Mal ein
bisschen verorten.
Das sieht jetzt erst mal so aus.
Nicht erschrecken.
Das ist ein sogenannter Advanced Organizer.
Das ist ein eine Methode, um ja zum einen den Überblick über die
Themen zu geben, wie Sie das ja schon aus dem Semesterplan kennen,
aber zusätzlich auch noch den Zusammenhang zwischen den einzelnen
Themen zu verdeutlichen.
Sie müssen das jetzt nicht alles heute verstehen, aber es soll Ihnen
eben helfen, so im Laufe des Semesters die einzelnen Inhalte besser
ineinander, besser zueinander in Beziehung zu setzen.
Ich erkläre es aber trotzdem jetzt schon einmal.
Und wie gesagt, muss man doch nicht ganz verstehen.
Ich fange mal hier links an.
Ganz zentral beim Programmieren sind Datentypen und Instanzen.
Oben haben wir hier die Datentypen und Sie haben ja schon in der
vorherigen Vorlesung die Klassen als ein Beispiel von
benutzerdefinierten Datentypen kennengelernt.
Wir werden dann heute uns auch die primitiven Datentypen anschauen und
haben damit dann schon recht viel abgedeckt.
Unten sieht man Instanzen, das sind zum einen die Objekte, die wir
auch beim letzten Mal uns schon angeschaut haben, aber zum anderen
dann auch allgemeiner Werte und die Möglichkeit, Werte in Variablen
abzulegen und schließlich Objektidentität und Referenzen.
Und der Hintergrund, warum das so relevant ist, ist eben, dass ich mit
Datentypen und mit Klassen eben diese Schablone definiere, diese
Vorlage mache, was mein Programm zur Laufzeit tun soll und dass die
Instanzen dann das sind, was das Programm zur Laufzeit tatsächlich
berechnet und erzeugt, wenn ich ihm konkrete Eingaben, also ein
konkretes Problem dann auch tatsächlich gebe.
Also hier die Ebene eigentlich, auf der wir als Programmierer
unterwegs sind.
Wir schreiben Klassen und entsprechend auch Zustand, Verhalten für
diese Klassen und unten dann aber für sie das Verständnis, was was für
Instanzen werden dann zur Laufzeit erzeugt, um das dann auch richtig
anwenden zu können.
Das ist also hier links die Grundlagen.
Wie gesagt, Klassenobjekte auch insbesondere für objektorientiertes
Programmieren, Datentypen, Instanzen, Werte allgemein für jegliche Art
von Programmieren.
Wenn man diese Grundlagen dann hat, dann möchte man damit ja wirklich
komplexe Systeme bauen, um auch komplexe Probleme in der realen Welt
damit lösen zu können.
Und das ist hier so rechts dargestellt, was sie da dann noch weiteres
brauchen.
Zusätzlich zu diesen Grundlagen, um eben auch komplexere Systeme bauen
zu können.
Und was man da machen möchte, ist zum einen oftmals viele Daten
verwalten, also nicht nur einzelne Werte für X und Y, wie wir das
schon gesehen haben, sondern große Datenmengen.
Und dafür brauche ich dann Möglichkeiten, um viele gleichartige Daten
zusammen abzulegen.
Das sind Arrays, wo viele Daten hintereinander oder Listen auch viele
Daten hintereinander.
Hier sind es jetzt nur drei, aber Sie können sich vorstellen, dass
könnte auch sehr lang sein bis dahinten irgendwo.
Also auch viele Daten.
Dann möchte man beim Programmieren in seinem System auch nicht nur ein
oder zwei Klassen haben, sondern man möchte viele Arten von Objekten
in Klassen zusammenfassen.
Und um da dann die Beziehung zwischen Klassen auch herzustellen, gibt
es das sogenannte Prinzip der Vererbung, so dass man eben
Gemeinsamkeiten und Unterschiede von Klassen besser gemeinsam
verwalten kann, sage ich jetzt erst mal.
Und schließlich geht es beim Bauen von komplexen Systemen darum, auch
Systeme, die schon mal geschrieben worden sind, weiterentwickeln zu
können.
Und es geht auch darum, Arbeitsteilung machen zu können.
Man arbeitet typischerweise nicht allein an einem Programm, sondern
eben in ganzen Teams.
Und da muss man es erreichen, dass die Teile eines Programms auch
möglichst unabhängig voneinander verändert werden können.
Das Prinzip, was man erreichen möchte, ist das Prinzip der
Datenkapselung, dass man also sagt, unterschiedliche Teile meines
Programms sind irgendwie unabhängig miteinander und ich möchte sie
unabhängig voneinander weiterentwickeln, verändern können.
Aus diesem Prinzip der Datenkapselung gleiten sich dann noch ein paar
mehr.
Also diese drei Programmiersprachenmechanismen ab, nämlich zum einen
abstrakte Datentypen, Interfaces, zu deutsch Schnittstellen und
generische Datentypen, in Java heißen die Generics.
Und wenn man diese Dinge dann eben kann, dann kann man nicht nur
einzelne Klassen, einzelne Objekte schreiben, sondern große, komplexe
Systeme bauen.
Das ist also dieser gelbe Kasten.
Und hier sehen wir noch den orangenen Kasten zur Methodik, was Sie
auch in dieser Vorlesung lernen.
Das erste hatten wir schon in der letzten Woche, wirklich die
Grundlagen, die Basics, Programme ausführen zu können und eine Ausgabe
machen zu können.
Also da können Sie jetzt sagen Ja, das kann ich schon.
Weiter geht es dann im Bereich der Methodik mit der Frage, wie finde
ich eigentlich Fehler, die ich selber gemacht habe?
Das werden wir uns später noch mal anschauen.
Und es wird auch um Qualitätsstandards sowas wie Code Conventions,
Kommentare in Java, das nennt man Javadoc gehen und ein paar
Spezialitäten von Java noch mal.
Und gegen Ende des Semesters schauen wir uns dann auch noch mal Design
Prinzipien und Best Practices an, die Ihnen dann eben für die
Abschlussaufgaben helfen sollen und auch natürlich im weiteren
Informatikerleben oder was Sie studieren.
Genau, das ist also der Überblick, hier so die Grundkonzepte.
Da machen wir dann heute auch weiter mit den Typen und Variablen und
dann das Zusammenbauen und daneben darüber gelagert die Methodik.
Okay, noch mal der Überblick auch in den Folien, damit Sie das
vielleicht noch mal vergleichen können.
Das eine oder das andere, da sage ich jetzt gar nicht weiter was dazu.
Heute soll es also um die zwei Dinge Typen und Variablen gehen.
Und im Folgenden habe ich mal die Lernziele für heute hier aufgeführt.
Für den Bereich Typen wäre ein Lernziel, dass Sie die acht elementaren
Datentypen in Java kennen und dass Sie einen geeigneten elementaren
Datentyp für Ihr Problem auswählen können.
Das werden Sie dann auch auf jedem Übungszettel gleich brauchen.
Weiterhin ist ein Lernziel, dass Sie die mit den elementaren
Datentypen verbundenen Operationen kennen und diese anwenden können.
Operation ist so was Einfaches wie Addition, Substraktion oder
Zeichenketten aneinanderhängen.
Schauen wir uns dann gleich noch mal im Detail an.
Im zweiten Teil zum Teil der Variablen sind die Lernziele die
folgenden.
Zum einen sollen Sie eben wissen, was mit Variablen gemeint ist und
Sie sollen insbesondere wissen, dass Variablen Namen für
Speicherplätze sind, wo ich eben Zwischenergebnisse meiner Berechnung
ablegen kann und dann wieder darauf zugreifen kann.
Sie können entsprechend oder Sie sollen entsprechend dann Variablen
deklarieren können und auch den Wert von Variablen durch Zuweisung
verändern können.
Alles Begriffe, die dann ja gleich nochmal drankommen.
Und ein bisschen spezieller auf die objektorientierte Programmierung
und wie das in Java umgesetzt ist, ist ein Lernziel dann, dass Sie
wissen, wie Objektvariablen im Speicher abgelegt sind und dass Sie
dann Objekt Identität und Objekt Referenz voneinander unterscheiden
können.
Okay, das so als Überblick und diese Wörter werden dann entsprechend
immer wieder auftauchen.
Fangen wir an.
Datentypen habe ich jetzt schon ein paar Mal genannt.
Hier eine Definition, was das eigentlich ist.
Ein Datentyp bezeichnet eine Menge irgendwie gleichartiger Werte, mit
denen ich rechnen möchte.
Datentypen werden dafür verwendet, dass ich die möglichen Werte von
Variablen festlege oder von Attributen festlege und sogar auch die
möglichen Rückgabe Werte von Methoden festlege.
Einerseits die Werte, die ich dann verwende, und andererseits
bestimmen Datentypen auch, welche Operationen auf den Werten möglich
sind.
Ich hatte gerade schon Addition, Substraktion genannt.
Das sind zum Beispiel Operationen, die auf Datentypen, die Zahlen
sind, möglich sind.
Das erst mal allgemein.
In Java gibt es acht elementare Datentypen, sogenannte Primitive Data
Types.
Und das sind Zahlen.
Wahrheitswerte und Zeichen bei den Zahlen unterscheiden wir dann
nochmal in ganze Zahlen 1, 2, 3 und in Fließkomma Zahlen.
Das sind quasi reelle Zahlen, allerdings auch nur, weil der Computer
nur endlich viel Speicherplatz hat, nur eine Teilmenge davon.
Da kommen wir gleich dann auch nochmal dazu.
Das sind also acht elementare Datentypen, mit denen ich arbeiten kann.
Und zusätzlich bildet noch jede Klasse, die ich selber definiere oder
die ich wiederverwende, auch für sich einen Datentyp.
Sie können also in Java die elementaren Datentypen um eigene Benutzer
definierte erweitern.
Jede Klasse, die sie schreiben, ist auch ein Datentyp.
Schauen wir uns diese acht elementaren Datentypen hier nochmal in der
Übersicht an.
Hier jetzt erst mal nur der Überblick gleich auf der nächsten Folie,
dann auch der Wertebereich von diesen Datentypen.
Und dann gehen wir nochmal durch die einzelnen Datentypen im Detail
durch.
Hier ist mal nur der Überblick.
Es gibt Wahrheitswerte, die nennt man Boolean.
True, false sind auch Beispielwerte und auch schon gleich alle
möglichen Werte.
Es gibt Charakter.
Das sind Zeichen, zum Beispiel diese.
Es gibt dann verschiedene Ganzzahl Datentypen, die sich in ihrer Größe
unterscheiden.
Sehen wir auch gleich nochmal.
Wide, Short, Integer, also Int für Integer und Long.
Und dann gibt es zwei Bleitpunktzahlen oder Fließkommazahlen.
Das ist synonym zu sehen, auch mit unterschiedlicher Größe, wo ich
eben Kommazahlen mit darstellen kann.
Zu den Wertebereichen.
Was heißt das?
Unterschiedliche Größe von Datentypen.
Da ist es insbesondere interessant, bei den ganzen Zahlen habe ich ja
diese vier verschiedenen Datentypen und die unterscheiden sich eben
darin, welche Zahlen ich darstellen kann.
Mit dem Datentyp Wide habe ich nur den Wertebereich von minus 128 bis
plus 127.
Und weil das eben entsprechend nur mit acht Byte gespeichert ist.
Short, Integer und Long haben dann eben jeweils mehr Bits zur
Verfügung und entsprechend kann ich mehr zahlen in diesem oder
entsprechend habe ich mehr Zahlen in diesem Wertebereich.
Short von minus 32.000 bis plus 32.000 irgendwas.
Integer sind schon recht viele und Long sind nochmal mehr.
Sag ich jetzt einfach mal so platt.
Wir haben eben gesehen, das sind 8 Bit für Byte, 16 Bit für Short.
32 Bit für Long und 32 Bit für Integer und 64 Bit für Long.
Und diese Zahlen ergeben sich dann eben in der Potenz mit zwei.
Also dass ich sage Long, diese Zahl hier sind oder Long hat zwei hoch
64 mögliche Werte.
Und wenn ich das aufteile auf negative Zahlen und positive Zahlen,
dann komme ich eben zu diesem Intervall, was hier gezeigt ist.
Genau und bei den Floats und Doubles habe ich eben wie gesagt eine
Teilmenge der reellen Zahlen, bei Float geht die Teilmenge von dieser
sehr negativen Zahl bis zu dieser großen Zahl und bei Double habe ich
entsprechend noch größeren Zahlenraum.
Am Rande nur erwähnt, hier die Wertedarstellung.
Typischerweise stellen wir Zahlen ja im Dezimalsystem dar, also zur
Basis 10.
Die hinterste Ziffer bezeichnet 10, wie oft ich 10 hoch 0 habe.
Die Ziffer davor bezeichnet, wie oft ich 10 hoch 1 habe.
Also hier 10 plus 8 sind 18.
Man kann in Java in dem eigenen Programm die Zahlen auch im
Oktalsystem darstellen, also zur Basis 8 oder im Hexadezimalsystem,
also zur Basis 16.
Da benutzt man dann auch die Buchstaben A bis F für die Zahlen.
11 bis 15, nur dass sie das, wenn sie es mal irgendwo sehen, dann auch
wiedererkennen.
Man kann es aber auch einfach nur im Dezimalsystem oft machen.
Gut, das ist also für die Wertebereiche der elementaren Datentypen.
Wie gesagt, wir gehen gleich auf die einzelnen Datentypen nochmal ein,
schauen uns aber vorher noch mal die Operationen auf diesen
elementaren Datentypen an, die es gibt.
Das sind also die Operationen, die in Java da verfügbar sind.
Wir haben hier zum einen so eine Menge von arithmetischen und
Vergleichsoperatoren.
Die kommen Ihnen wahrscheinlich recht bekannt vor.
Man kann halt Multiplikationen ausführen, Divisionen, Modulo rechnen.
Das sind diese drei.
Man kann Addition und Substraktion machen.
Man kann auch nur ein Minus voranstellen und dadurch einen Wert
negativ machen oder eben umdrehen.
Man kann Vergleiche machen mit größer, also einfach auf echte, echt
größer prüfen oder echt kleiner oder auf größer gleich.
Und man kann auch Gleichheit und Ungleichheit prüfen.
Wenn man sich diese Operatoren anschaut, ist das Ergebnis, wenn man
diese arithmetischen Operationen, Operatoren anwendet, immer auch
wieder eine Zahl.
Das Ergebnis bei den unteren beiden hier ist an einem Wahrheitswert.
Also wenn ich X gleich gleich Y hinschreibe, wird das Ergebnis dieser
Berechnung entweder True oder False sein.
Es gibt auch noch weitere Operatoren, die bitweise vorgehen.
Ich kann also zwei Zahlen hernehmen, dann mir die Repräsentation in
Einsen und Nullen anschauen, also zur Basis zwei und dann quasi diese
Zahlen übereinanderlegen und immer die gleiche Position in einer Zahl
miteinander verrechnen.
Also Infos dazu habe ich im Anhang angehängt.
Ich will es jetzt gar nicht so im Detail beschreiben, aber das ist
dann eben ein sogenanntes bitweises Kompliment, also bitweises Nein,
bitweises Und, bitweises Entweder oder eben ein bitweises Oder.
Ziel der Vollständigkeit halber aufgeführt, sollten Sie sich auch mal
anschauen, braucht man aber nicht so häufig.
Wichtiger wieder sind dann die Operatoren auf Wahrheitswerten.
Da habe ich einmal das Nicht, also wenn ich einen Wert habe, der False
vorher war und ich setze ein Nicht davor, dann ist er dann True und
andersherum.
Fangen wir uns gleich auch nochmal an.
Ein Und-Operator und ein Oder-Operator.
Und dann schließlich gibt es auch noch wieder ein paar Operatoren auf
ganzen Zahlen, die sich auch auf diese Bit-Repräsentation beziehen und
die Zahlen in ihrer Binär-Darstellung verschieben um eine Stelle.
Auch dazu weitere Infos im Anhang.
Das sind also die verschiedenen möglichen Operatoren, die ich also
oder wie ich also mit zwei Werten rechnen kann.
Es gibt hier in der ersten Spalte noch eine Angabe der Präzedenz und
das ist einfach so ein Hilfskonstrukt, dass man quasi die Operatoren
sortiert hat, wie stark die binden und dass man Klammern weglassen
kann.
Also je kleiner dieser Wert hier in der Tabelle ist, desto stärker
bindet ein Operator.
Und wenn ich eben keine anderen Klammern angegeben hat, wird dadurch
definiert, wie was zuerst ausgewertet wird.
Es gibt auch eine sogenannte Kurzschlussauswertung.
Das bezieht sich auf diese beiden Operatoren auf Wahrheitswerten.
Wenn ich X und Y irgendwo in meinem Code stehen habe und da ist es
schlecht, genau, und X schon den Wert falsch hat, dann ist es in Java
so, dass der ganze Ausdruck gar nicht mehr ausgewertet wird, sondern
wenn ich schon weiß, dass X falsch ist bei einer Unverknüpfung, dann
brauche ich auf Y gar nicht mehr gucken.
Dann weiß ich schon, dass das Ergebnis auch falsch ist.
Dann wird also, falls das hier ein Methodenaufruf wäre, dieser hier
gar nicht mehr ausgeführt.
Das sollte man auch wissen, da passieren sonst, wenn man das nicht
weiß, vielleicht komische Dinge für einen selber.
Genau, hatte ich gerade schon kurz angedeutet, diese Präzedenzen von
Operatoren helfen dabei, unnötige Klammern zu vermeiden.
Ich kann also einen solchen Ausdruck hier hinschreiben mit diesen
ganzen verschiedenen Operatoren.
Und es ist durch die Präzedenzen, die hier nochmal dargestellt sind,
klar, in welcher Reihenfolge das ausgewertet sind, wie also die
Klammern implizit gesetzt sind.
Nochmal gesagt, je kleiner der Wert der Präzedenz ist, desto stärker
bindet der Operator.
Das heißt hier, das Plus und das Minus hat erstmal die erste
Präzedenz.
Das kommt aber hier gar nicht vor.
Das nächste sind dann Multiplikation, Division und Modulrechnen.
Das habe ich zum Beispiel hier und hier.
Wie Sie sehen, die Leerzeichen spielen keine Rolle, sondern nur die
Operatoren selber.
Das heißt, hier wird zuerst zweimal Y ausgewertet und weiter hinten.
Also bevor dann das Plus 1 gerechnet wird.
Und hier zum Beispiel in dieser Klammer wird erst Y Modul Z
ausgerechnet, bevor dann der Vergleich mit X gemacht wird.
So ergibt sich also implizit diese Klammerung hier unten.
Genau, also wohl definiert, was zuerst ausgewertet werden soll.
So viel zu den Operatoren und ihren Präzedenzen.
Wie gesagt, können wir jetzt mal durch die einzelnen Datentypen
durchgehen, was da im Prinzip dahinter steckt, was genau der
Wertebereich ist und auch, was dann auf diesen, was dann die auf
diesen Datentyp anwendbaren Operatoren sind.
Wir fangen einfach an.
Die Wahrheitswerte, Boolean haben eben nur zwei verschiedene Werte,
nämlich True oder False.
Wir haben ein paar Operatoren drauf, die in dieser Wahrheitstabelle
dargestellt sind.
Nehmen wir mal an, dass wir zwei Werte haben, die wir hier mit A und B
bezeichnen.
Dann ist das Ergebnis von einem A und B dann nur eins, wenn beide eins
gewesen sind, wie man sich das also vielleicht auch logisch erklären
würde, sonst null.
Wenn ich diesen Operator oder verwende, ist das Ergebnis von A oder B
null, wenn beide null sind, sonst eins, wenn einer von beiden oder
beide eins sind.
Hier haben wir den Operator entweder oder, wenn Sie sich auch
vorstellen, der ergibt den Wert null, wenn beide Eingaben null sind
oder ergibt den Wert null, wenn beide Eingaben eins sind und ergibt
eben den Wert eins, also True, wenn die beiden Eingaben
unterschiedlich sind.
Eine war eine falsch, also entweder oder.
Schließlich gibt es den sogenannten unnäheren Operator mit das
Ausrufezeichen, das Kompliment oder eben der Eingabewert sozusagen
umgedreht wird aus True wird Fals und aus Fals wird True.
Genau, hier in dieser Tabelle ist eins dann immer die Abkürzung für
True und null die Abkürzung für Fals.
Das werden Sie auch oftmals synonym finden.
In Java Code müssen Sie allerdings tatsächlich True und Fals
hinschreiben bei Vergleichen.
Okay,
gehen wir weiter zu den Ganztzahlen als Datentypen.
Das sind eben die Datentypen Byte, Short, Integer und Long, die es
ermöglichen, ganze Zahlen auszudrücken.
Und auf allen diesen Datentypen habe ich die folgenden Operatoren.
Ich habe Plus, Minus, Mal und ich habe Vergleichsoperatoren, die ja,
so wie Sie das aus der Mathematik eben auch kennen, fast
funktionieren.
Und ich habe die Division und Modulo rechnen.
Da muss man beachten, insbesondere bei der Division, dass das bei
Integern und anderen Ganztzahlen immer gerundet wird.
Also das Ergebnis einer Integer Division zum Beispiel ist selbst immer
auch wieder ein Integer.
Der Rest, der nicht ja, also der Rest der Division wird jetzt nicht
als Kommazahl angehängt oder hinter das Komma angehängt, sondern
verfällt einfach.
Das heißt, wenn ich bei den Integern zum Beispiel 17 durch 5 rechne,
wäre das Ergebnis 3 und der Rest 2, der ja eigentlich bleiben würde,
fällt einfach weg.
Und entsprechend umgekehrt funktioniert dieser Modulo-Apparator, der
eben gerade den Rest einer Division herausgibt.
Also 15 Modulo 5 wäre 2, weil eben 17 Modulo 5 wäre 2, weil eben der
Rest der Division von 17 durch 5 2 wäre.
3 mal 5 wäre ja 15 und dann habe ich noch 2 bis zu 17 zu kommen.
Genau, das sind also die möglichen Operatoren.
Bei ganzen Zahlen muss ich auch noch beachten, dass es ein Über- oder
ein Unterlauf geben kann.
Ich hatte ja gerade die Wertebereiche für diese einzelnen Datentypen
gezeigt und zum Beispiel Byte war von minus 128 bis plus 127.
Wenn ich jetzt also mit Byte rechne, wobei vielleicht kein besonders
gutes Beispiel, aber nehmen wir das mal an, und addiere und dann etwas
herausbekomme, was größer als 128 ist, dann wird das im Ergebnis, wenn
ich das dann wieder in eine Byte-Variable hinein speichere, werden
einfach die höchstwertigen Bits ignoriert.
Und es könnte zum Beispiel auch passieren, dass ich dann mit einer
negativen Zahl wieder herauskomme.
Also ich muss immer aufpassen, ob der Datentyp, den ich gerade
verwende, auch für die Größenordnung von Zahlen, mit denen ich rechnen
will, auch passt.
Wenn nicht, wenn also ich außerhalb dieser Wertebereiche laufe, für
die mein Datentyp definiert ist, dann passieren eben unerwünschte
Dinge, dann können also Zahlen herauskommen, die da nichts mit der
eigentlichen mathematischen Operation oder arithmetischen Operation zu
tun hat.
Ist auch wohl definiert, was dann herauskommt, aber wahrscheinlich
nicht besonders hilfreich für ihr Programm.
Beispiel hier, wenn ich im Typ Integer diese zwei Zahlen miteinander
addiere, ist das Ergebnis wieder eine negative Zahl, weil mir nach
oben hin sozusagen die Werte ausgehen und dann fängt er einfach wieder
unten an.
So kann man sich das grob vorstellen.
Oder wenn ich von der kleinsten Zahl, von der kleinsten Integerzahl
einen nochmal wieder abziehe, also von der größten negativen Zahl im
Betrag, dann komme ich zur größten möglichen Integerzahl.
Da muss man also immer aufpassen, in welchem Wertebandbereich man sich
gerade befindet.
Ich hatte gerade bei den Bytes kurz gestutzt.
Es ist tatsächlich so, dass das Ergebnis von arithmetischen
Operationen mit Operanten vom Typ Byte und Short immer sofort auch
wieder vom Typ Integer ist.
Also wenn ich jetzt tatsächlich zwei Bytes 127 plus 127 rechne, dann
bekomme ich automatisch als Ergebnis schon einen Integerwert und ich
müsste ihn dann noch wieder runterkürzen auf ein Byte.
Genau, machen wir hier weiter.
Wir haben jetzt eben diese arithmetischen Operatoren und die
Vergleichsoperatoren gesehen.
Bei den Zahlen gibt es noch so spezielle Operatoren, die nennt man
Inkrement Operator und Dekrement Operator, also Plus, Plus und Minus,
Minus.
Und das sind besondere Operatoren, weil die auch gleichzeitig den
Zustand einer Variable noch mit ändern.
Die kann ich entweder vor oder hinter eine Variable.
Das sehen wir ja später noch, was das ist, schreiben, also entweder in
Präfix Notation vor die Variable oder in Postfix Notation hinter die
Variable.
Und das bedeutet dann, wenn ich zum Beispiel so was hinschreibe wie Y
soll jetzt den Wert Plus Plus X annehmen, dann heißt das, ich
inkrementiere X erst um 1 und danach weise ich den Wert, der dabei
rauskommt, Y zu.
Also Präfix Notation heißt, es wird erst inkrementiert und dann wird
der Wert weiterverwendet.
Und Postfix Notation bedeutet hier zum Beispiel Y bekommt den Wert
zugewiesen von X plus Plus, bedeutet, ich nehme erst den Wert, den X
quasi vorher hatte und weise den Y zu und erhöhe danach X um 1.
Also dies ist auch ein unnäherer Operator, der steht nur vor einem
Wert und erhöht diesen Wert gleich um einen und legt den auch wieder
in der gleichen Variable ab.
Das Gleiche für Minus Minus, den Dekrement Operator.
Okay.
Das waren die ganzen Zahlen, also Wahrheitswerte.
Ganze Zahlen hatten wir uns jetzt angeschaut.
Gehen wir weiter zu den Fließkomma Zahlen oder wie gesagt Synonym
Gleitpunkt Zahlen und das sind in Java eben Floats und Doubles.
Diese beiden Datentypen implementieren einen Standard, den IEEE 754
Standard, wie auch immer.
Also es werden sie so auch in anderen Programmiersprachen, die diesen
Standard implementieren, wiederfinden.
Die Werte von diesen Fließkomma Zahlen sind eben eine Teilmenge der
reellen Zahlen.
Es sind nicht die reellen Zahlen insgesamt, weil das ein bisschen
viele Werte wären.
Insbesondere gibt es da unendlich viele reelle Zahlen.
Das kann ich im Speicher meines Computers dann natürlich nicht
darstellen.
Es ist also eine nur eine Teilmenge davon.
Dadurch können auch, kann ich schon mal gleich sagen, leicht Probleme
entstehen.
Aber zunächst erst mal dazu.
Es ist also eine Teilmenge der reellen Zahlen.
Man schreibt das auch im Programmcode mit einem Dezimalpunkt dann hin,
nicht mit einem Komma, wie wir es in Deutschland kennen würden,
sondern mit einem Punkt.
In meinem Code kann ich also sowas wie 3.14 schreiben und so eine
Double Variable oder einen Double Wert beschreiben.
Oder ich kann auch wissenschaftliche Darstellung verwenden mit diesem
E.
Das heißt, das steht hier für sowas wie 6,022 mal 10 hoch 23.
Also die 23 gibt dann immer den Exponenten zur Basis 10 an.
Damit kann man dann leichter große Zahlen schreiben.
Intern werden diese Fließkomma Zahlen natürlich auch irgendwie im
Speicher abgelegt.
Der Speicher kann ja nur Nullen und Einsen und da stellt sich dann
gleich die Frage Wie kriege ich denn jetzt eine Kommazahl in Nullen
und Einsen irgendwie dargestellt?
Das wird eben in diesem IEEE Standard so gemacht, dass die Zahl
aufgeteilt wird in ein Bit für das Vorzeichen, dann einige Bits für
ein Exponenten und einige Bits für eine sogenannte Mantisse, so dass
man dann insgesamt so eine Gleichung herausbekommt, die dann den
eigentlichen Wert dieser Zahl repräsentiert und zwar minus 1 hoch mein
Vorzeichen, was dann positiv ist, wenn das Vorzeichen 0 ist und
negativ, wenn das Vorzeichen 1 ist, dann ein Wert für die Mantisse.
Das ist sozusagen, kann man sich vorstellen, meine eigentliche Zahl
und ein Exponent, der dann ja als Potenz zur Basis 2 interpretiert
wird.
Schauen wir uns das am besten mal am Beispiel an.
Wenn ich also in meinem Speicher abgelegt habe, das Vorzeichen soll 1
sein.
Der Exponent ist minus 1.
Die Mantisse ist 5.
Ergibt sich der eigentliche Wert von minus 2,5, weil der Wert das wird
dann so ausgewertet sozusagen.
Ich habe das Vorzeichen 1 für das Vorzeichen.
Er gibt insgesamt ein Minus.
Ich habe hier die Mantisse als 5 stehen und ich rechne die Mantisse
dann nochmal mal 2 hoch minus 1, weil ich hier den Exponenten als
minus 1 angegeben habe.
Und das ergibt dann eben minus 2,5.
Und so kann ich dann quasi nur mit geraden Zahlen in einer gewissen
Kodierung eine, also nur mit ganzen Zahlen in einer gewissen Kodierung
dann Fließkomma Zahlen darstellen.
Bei den Fließkomma Zahlen gibt es noch ein paar besondere Werte.
Ihnen vielleicht auch mal das eine oder andere Mal begegnen werden.
Es gibt den Wert NAN, der dann zum Beispiel von Java auch so
ausgegeben wird.
Der steht für Not in Number und wird eben manchmal in Berechnungen
verwendet, wenn das Ergebnis keinen Sinn macht.
Oder Sie können es auch direkt verwenden, diesen Wert, um zu sagen
hier ist nichts Sinnvolles enthalten.
Und es gibt die besonderen Werte positive infinity und negative
infinity, die man verwenden kann, um anzuzeigen Hier dieser Wert ist
ja unendlich hoch, also aber einfach auch nur ein Wert in diesem Werte
Bereich der Fließkomma Zahlen, der eben diese Interpretation hat.
Und es gibt bei Fließkomma Zahlen lustigerweise ein positive Null und
eine negative Null.
Das hängt von der Darstellung intern eben im Speicher ab.
Werde ich jetzt auch nicht weiter darauf eingehen.
Wichtig ist aber zu wissen, dadurch dass es eben nur eine Teilmenge
der reellen Zahlen ist, werden mathematische Operationen, die ich auf
diesen Zahlen durchführe, oftmals gerundet oder eigentlich immer in
irgendeiner Weise gerundet.
Und das kann sich dann, wenn ich verschiedene Berechnungen mache, dann
auch zu Fehlern, also zu Rundungsfehlern aufsummieren, die auch
relevant für mein Programm sind.
Ich kann also ich muss viele Berechnungen, die ich mit Kommazahlen,
mit Fließkommazahlen machen möchte, auch nicht nochmal selbst
nachprogrammieren, sondern Java stellt schon eine Klasse namens Math
bereit, die so typische Fließkomma Operationen enthält wie den
Absolutbetrag von irgendwas zu nehmen oder Sinus, Cosinus oder hier
Square Root, also die Quadratwurzel.
Die Operatoren Gleichheit und Ungleichheit sind auf diesen
Fließkommazahlen auch definiert, aber nur mit Vorsicht zu genießen,
weil ich eben Rundungsfehler haben kann.
Kann das leicht sein, dass zwei Zahlen eigentlich gleich sein sollten,
aber sich dann doch durch einen Rundungsfehler im Detail unterscheiden
und dann so ein direkter Vergleich falls zurückliefern würde, obwohl
ich eigentlich lieber ein True gehabt hätte.
Deswegen sollte man je nachdem, was man braucht, natürlich im Programm
statt eines Größen oder statt eines Gleichheits Vergleichs lieber
prüfen, ob der Betrag der Differenz kleiner als ein Wert Epsilon
sozusagen ist, also kleiner als ein kleiner Wert, damit man auch
Zahlen, die nur fast gleich sind, dann auch im Programm als gleich
erkennt.
Wenn das dann im Programm so Sinn macht und anstatt ungleich dann
entsprechend im Vergleich, ob etwas größer als so eine Zahl Epsilon
ist.
Okay, wir schauen uns hier jetzt wieder unser Expertenzeichen für
jetzt mal ein bisschen tieferes technisches Detail noch mal genauer
an, wie diese Zahlen im Speicher abgelegt werden.
Diese Gleitkommazahlen.
Und hier sind dargestellt, wie eben in diesem IEEE Standard festgelegt
ist, wie sogenannte Single Precision Gleitkommazahlen abgelegt werden
im Rechner und Double Precision.
In Java wäre das dann eben Integer mit 32 Bit oder Long.
Ach Quatsch, Integer und Long, nein, Float und Double an der Stelle.
Integer und Long sind ja die ganzen Zahlen.
Bei Single Precision verwende ich eben 32 Bit, bei Float zum Beispiel.
Das hatten wir auf dieser Übersichtsfolie vorhin auch schon gesehen
und die sind im Speicher dann wie folgt aufgeteilt.
Ich verwende ein Bit für das Vorzeichen.
Das kann ja nur Minus oder eben Positiv sein.
Ich verwende dann 8 Bit für den Exponenten, also das, was nachher im
Exponent von der 2 sozusagen steht.
Und ich verwende 23 Bit für die Mantisse, also für die restliche Zahl,
sage ich jetzt mal.
Und bei Double Precision sind es eben hier ein paar mehr Bit für den
Exponenten und mehr Bit für die Mantisse.
Und damit kann ich dann eben, wenn ich hier meine Nullen und Einsen,
also meine ganzen, meine Codierung meiner ganzen Zahlen hineinlege,
kann ich dann die eigentliche Gleitkommazahl berechnen durch das
Vorzeichen und dann Mantisse mal zwei Hochexponenten.
Bei binären Zahlen ist die Basis halt immer zwei.
Das ist das Prinzip, wie diese Gleitkommazahlen im Speicher abgelegt
werden.
Die 1 vor dem Komma, wenn man sich das jetzt mal durchrechnet, wird
nicht explizit gespeichert, dass man nur am Rande gesagt und hat man
ein bisschen mehr Platz.
Und wenn das Vorzeichen 0 ist, habe ich eben eine positive Zahl.
Wenn das Vorzeichen 1 ist, habe ich eine negative Zahl.
Und dann, um das jetzt hier noch vollständig zu halten, aber auch ich
will ja da auch nicht weiter ins Detail gehen, wird der Exponent hier
noch ein bisschen verschoben, damit er immer positiv ist.
Also eigentlich hatten wir in unserem Bild gerade gesehen, dass die
Exponenten auch negativ sein können.
Wir hatten das Beispiel minus 2 hier, glaube ich, gerade gesehen.
Also 5 oder was?
Minus 1?
Egal.
Um auf diesen Wert von 2,5 zu kommen.
Das wird im Speicher aber so verschoben, sozusagen dieser Wert, dass
der Exponent immer positiv ist, damit ich besser Vergleiche machen
kann.
Aber ja, da müssen wir jetzt auch nicht im Detail drauf eingehen.
Es gibt, wie gesagt, in diesem Standard oder in Java dadurch auch
sowohl eine Plus Null als auch eine Minus Null, was natürlich erstmal
so im mathematischen Sinne keinen Sinn macht, liegt aber daran, dass
ich, wie ich es eben gezeigt habe, im Speicher zwei oder eine Null
erstmal ausdrücke durch Exponent Null und Mantisse Null.
Und dann kann das Vorzeichen ja entweder aus den Wert 1 oder den Wert
Null haben.
Und beides ist eben hinten der Wert Null, einmal mit dem Vorzeichen
Plus und einmal mit dem Vorzeichen Minus.
Ganz wichtig aber, was man sich merken muss, auch wenn man nicht in
die Details hier hineinschaut, ist eben, dass beim Rechnen mit
Gleitkommazahlen leicht Rundungsfehler passieren können, wie eben
schon angedeutet.
Aber jetzt hier auch mal im Beispiel gezeigt, wenn ich mit Floats
rechne, die ja eben einen nicht so großen Wertebereich haben, würde
mir schon das Ergebnis von 1,05 minus 0,05 ist schon nicht mehr
präzise ausrechenbar oder präzise darstellbar als Float.
Wenn ich das auch mal ausprobiere, also wenn Sie das in ein einfaches
Beispielprogramm mal so reinschreiben und ausgeben, bekommen Sie das
Ergebnis 0,999 und so weiter 4, was nicht die erwartete 1, die ich
hier erwarten würde, ist.
Also selbst da bei so, ja, doch nicht so detaillierten Zahlen, sage
ich mal, habe ich schon bei der Verwendung von Floats einen
Rundungsfehler.
Ähnlich kann es auch sein bei großen Zahlen.
Wenn ich diese beiden Zahlen miteinander addiere, also hier 1 drauf
addiere, ist das Ergebnis wieder die gleiche Zahl, weil ich eben die
Zahl 1 größer als Float nicht ausdrücken kann.
Das bedeutet, ich kann das nicht in dieser Kombination Mantisse
Exponent Vorzeichen ausdrücken.
Und wenn es nicht ausdrückbar ist, dann wird einfach implizit auf die
nächste Zahl, die wiederum ausdrückbar ist, gerundet.
Genau, da habe ich ja noch mal ein kleines Bild mitgebracht, um das
ein bisschen zu veranschaulichen.
Wie gesagt, es sind die Gleitkommazahlen eine Teilmenge der reellen
Zahlen.
Und dazu kommt jetzt also noch, dass diese Teilmenge nicht nicht
gleichmäßig dicht ist, sozusagen.
Bei kleinen Zahlen habe ich es ein bisschen dichter, wobei auch diese
Zahl bei diesen Zahlen eben auch schon an Rundungsfehler komme.
Aber insbesondere sind große Zahlen dann auch nicht so dicht,
sozusagen darstellbar.
Also ich kann insbesondere das habe ich, glaube ich, auch noch mal zum
Einblenden sowas wie Viertel, Achtel, Sechzehntel ins Kleine
hineingehen, gut darstellen.
Da das sind eben gerade diese Punkte, die hier zu sehen sind.
Aber andere Zahlen wie Zehntel sind tendenziell nicht so gut
darstellbar.
Da kann es leichter Rundungsfehler geben.
Bei den großen Zahlen komme ich eben auch irgendwann in die Bereiche,
wo ich nur noch zwei Zahlen darstellen kann, die meinetwegen 10.000
also eigentlich eine Differenz von 10.000 haben oder so.
Also da wird es relativ dünn besiedelt.
Diese dieser Zahlenstrahl, welche Zahlen darstellbar sind.
Genau weitere Hintergründe dazu habe ich mal in den Anhang von diesem
Foliensatz gehängt, denn wir könnten jetzt noch anfangen, das
tatsächlich auch mal umzurechnen, eine Dezimalzahl in so eine
Fließkommazahl.
Aber das führt, denke ich, dann auch hier jetzt etwas zu weit.
Aber wen es interessiert, kann sich das im Anhang gerne anschauen.
Machen wir mit den weiteren Datentypen weiter.
Wir haben eben noch Datentypen, um Zeichen darzustellen.
Das wäre der sogenannte Character.
Also dieses Character hier kurz für Character.
Die möglichen Werte sind eben Zeichen, sowas wie ein großes A oder
eine 3 oder ein Prozentzeichen.
Ich kann aber auch andere Zeichen ausdrücken, die ich jetzt nicht
direkt auf meiner Tastatur finde, indem ich sogenannte Escape
-Sequenzen verwende.
Also dazu gehört immer ein Backslash und dann ein gewisser Code, sage
ich mal, dahinter, wie zum Beispiel Backslash n ist, bedeutet als
Zeichen ein Zeilenwechsel.
Backslash Apostroph ist eben die Art und Weise, wie ich das Zeichen
Apostroph hinschreiben kann.
Doppel Apostroph wäre es jetzt hier.
Einfach Apostroph, eben nur eines.
Den Backslash selber muss ich dann nochmal mit Backslash schreiben,
weil der ja schon selbst das Escape-Zeichen ist.
Oder ich kann auch mit so einem Backslash dann einfach die Codierung
von einem beliebigen Zeichen angeben.
Entweder Oktal oder auch Hexadezimal, entweder mit einer 0
vorangestellt oder mit einem U vorangestellt.
Zum Beispiel kann ich mit diesem Code hier U dafür, dass es jetzt
Hexadezimal sein soll und dann dieser vierstellige Code sozusagen kann
ich ein hebräisches Aleph dann bekommen, was eben auch im Wertebereich
dieses Datentyps Charakter drin ist.
Ich zumindest aber nicht auf meiner Tastatur habe.
Operatoren auf diesen Characters sind dann die gleichen wie bei den
Ganzzahl Datentypen und ich kann einfach mit den Werten rechnen, die
eben eigentlich in den entsprechenden Tabellen drinstehen, welches
Zeichen welche Nummer intern hat, also entweder ASCII Codierung oder
Unicode Codierung heißt es da.
Da ist zum Beispiel das A unter der Nummer 97 abgelegt.
Und wenn ich dann A plus 1 rechne, zum Beispiel ist das Ergebnis 98.
Ja, aufpassen, das Ergebnis ist nicht B, wäre ja auch schön gewesen,
sondern das Ergebnis von einer solchen Operation auf Charactern ist
immer wieder ein Integer, so ähnlich wie wir das bei den ganzen Zahlen
auch gesehen hatten.
Wenn ich jetzt also A mal B rechne, würde ich zum Beispiel 9506
bekommen.
Genau, Character, also einzelne Zeichen.
Als Datentyp beschreibt also den Wertebereich aller möglichen oder
dieser möglichen Zeichen hier.
Wenn ich dann ganze Zeichenketten repräsentieren möchte, dann verwende
ich den Datentyp String, der in Java eben auch schon vordefiniert ist.
Der ist tatsächlich eigentlich auch schon als Klasse vordefiniert,
also keiner dieser primitiven Datentypen, die wir eben gesehen haben.
Aber da man das so häufig braucht, ist es hier trotzdem mal
eingeführt, dass sie sich jetzt nicht selber Zeichenketten aus
Charactern zusammenbauen müssen.
Also eine vorgefertigte Klasse von Java für den Datentyp Zeichenketten
namens String.
Beispiele sind hier zu sehen, so in doppelten Anführungszeichen kann
ich in meinem Programm hinschreiben, das ist ein Text.
Und dieses hier ist dann also ein Wert des Datentyps String, weil es
eben eine Zeichenkette ist.
Dann auch ein anderer String wäre hier Hallo oder ein entsprechender
String, auch mit Zahlen drin und auch mit anderen Charaktern drin, die
ich eben nicht direkt repräsentieren kann, wie hier das Backslash N
für einen Zeilenwechsel.
Da sind dann verschiedene Operationen drauf möglich, die ich gar nicht
alle besprechen möchte.
Einer ist vielleicht noch wichtig, nämlich die Konkatenation.
Das ist die sogenannte Verkettung von Zeichenketten.
Da kann ich also in Java einfach den Plus Operator auch für
Zeichenketten verwenden und kann also schreiben eine Zeichenkette plus
eine andere Zeichenkette.
Und das Ergebnis ist dann.
Die entsprechende lange Zeichenkette, also hier im Result würde jetzt
eine Zeichenkette drinstehen, dies ist eine Konkatenation.
Das kann ich auch zwischen Variablen so verwenden.
Hier vielleicht aufpassen, was ich tatsächlich haben muss, ist hier
eine Zeichenkette, die wieder mit einem Anführungszeichen beendet
wird.
Dahinter noch eine zweite Zeichenkette.
Dazwischen dann diesen Operator.
Wenn ich sowas hierhin schreibe, dies ist plus eine Konkatenation,
dann ist das eben gerade keine oder keine Konkatenation, sondern eine
Zeichenkette, wo ein Plus drin vorkommt.
Unterschied hier oben ist das Plus der Konkatenation Operator.
Hier unten ist das Plus einfach ein Zeichen in der Zeichenkette, weil
ich keine Anführungszeichen hier gesetzt habe.
Okay, und als letztes schauen wir uns noch den sogenannten
Aufzählungsdatentypen an.
Enum, das ist in Java eine Möglichkeit, um auch noch eigene einfache
Datentypen, sage ich mal, zu definieren.
Die Werte dieses Aufzählungsdatentyps sind also benutzerdefiniert und
ich kann explizit angeben, welche möglichen Werte dieser Datentyp, den
ich da gerade definiere, haben soll.
Am besten auch zu sehen anhand eines Beispiels.
Ich könnte jetzt also sagen, ich möchte den neuen Datentyp Compass
Direction haben.
Das soll ein Enum sein und die möglichen Werte sind einfach Norden,
Osten, Süden und Westen.
Wenn ich sowas hinschreibe in Java, habe ich dann eben meinen eigenen
neuen Datentyp definiert mit vier möglichen Werten.
Anderes Beispiel oder ganz typisch wären so die Tage einer Woche,
Montag, Dienstag, Mittwoch und so weiter.
Oder man könnte Enums auch für Farben verwenden, wenn man jetzt nur an
wenigen Grundfarben interessiert ist.
Also ein Enum, ein selbst definierter Datentyp namens Color mit den
möglichen Werten Rot, Grün und Blau zum Beispiel.
Das Schema haben Sie jetzt hier schon im Beispiel gesehen, wie man das
verwendet, ist so ähnlich wie die Klassendeklaration, die wir letztes
Mal schon uns angeschaut haben.
Man schreibt also das Schlüsselwort Enum hin, dann sucht man sich
selber einen Namen für diesen Datentypen aus und in geschweiften
Klammern führt man dann eine Liste der Werte auf.
Und typischerweise schreibt man die Werte so in Großbuchstaben, um zu
erkennen, dass das die Werte eines Enums sind.
Das ist aber wirklich nur Konvention, geht auch anders.
Operationen auf solchen Enums kann man auch angeben oder muss man als
Benutzer sogar selber angeben.
Das ist ein bisschen analog zu Klassenmethoden und werden wir uns in
einer späteren Vorlesung dann mal anschauen.
Wie gesagt, ist ja ein Datentyp immer die Verbindung aus einem
Wertebereich, den wir jetzt zum Beispiel hier definiert haben und den
möglichen Operationen auf diesem Wertebereich.
Okay, jetzt haben Sie viel gehört.
Ich habe viel gesagt.
Jetzt dürfen Sie nochmal gucken, ob Sie das soweit mitbekommen haben
oder vielleicht, wenn Sie die Folien vor sich haben, auch nochmal
zurückblättern und versuchen, diese kleinen Überprüfungsaufgaben zu
lösen.
Also drei Stück.
Ich würde Ihnen einfach ein bisschen Zeit geben, um alle drei sich
anzuschauen.
Das erste wäre, welches Ergebnis die folgenden Berechnungen liefern.
Das zweite dann, ob ich mit diesem Ausdruck hier testen kann, ob die
drei Zahlen geordnet sind.
Und das dritte, ob ich mit diesen beiden Operatoren auch Strings
vergleichen kann.
Ich würde sagen, so fünf Minuten lasse ich Ihnen mal, weil es ja auch
drei Dinge zum Überlegen sind und dann schauen wir mal, was Sie
meinen.
Okay, die Frage war, ob man beim Enum die Werte untereinander
schreiben muss oder ob man die auch nacheinander schreiben kann.
Das macht für Java keinen Unterschied.
Man kann sie hintereinander oder untereinander schreiben.
Man würde es vielleicht schöner untereinander schreiben, aber auf den
Folien ist immer nicht so viel Platz.
Also jetzt fünf Minuten Zeit.
Überlegen Sie selber mal.
Sie können auch gerne mit Ihrem Nachbarn, Ihrer Nachbarin gemeinsam
überlegen, was denn hier die richtigen Antworten wären.
Also hier Antworten A, B oder C habe ich schon mal vorgegeben.
Hier halt nur Ja oder Nein.
Wer würde jetzt noch ein bisschen Zeit gerne haben zum
Weiterdiskutieren oder können wir schon weitermachen?
Ich frage jetzt so rum, weil andersrum ist es auch schwierig zu
erkennen.
Keiner oder keiner mag es jetzt sagen.
Das kann natürlich auch sein.
Okay, ich habe mir das jetzt wie folgt gedacht.
Wir sind ja doch etwas viele, um das jetzt hier per Annehmen zu
besprechen, die Lösung.
Deswegen die Idee, dass Sie jetzt einfach die Hand heben, ob Sie A, B
oder C meinen.
Dann sehe ich auch gleich, ob die meisten von Ihnen das richtig so
sehen würden oder nicht.
Also hierfür die erste Frage.
Welches Ergebnis liefert diese folgende Berechnung?
Eins als Float, zwei plus zwei als Float geteilt durch drei als Float.
Wie gesagt, ich sage jetzt A, B, C.
Sie melden sich.
Wer meint, dass Antwort A hier die richtige ist?
Okay.
Wer meint, dass Antwort B die richtige ist?
Ich hätte es nicht machen wollen, aber ich dachte...
Naja, okay.
Und wer meint, dass Antwort C die richtige ist?
Genau.
C ist nämlich noch schöner als 42 in diesem Zusammenhang, also 1,667.
Und das ist natürlich jetzt schon eine bisschen trickreiche Aufgabe,
sage ich mal gewesen, weil man würde erst mal denken, na ja, eins plus
zwei sind drei und durch drei sind dann eins.
Also von daher kann ich das nachvollziehen.
Aber hier ist der Trick eben, dass die Operatoren ja die verschiedenen
Präzedenzen haben und dass der Divisionsoperator hier eben stärker
bindet als der Additionsoperator.
Das heißt, es wird erst zwei durch drei ausgewertet.
Das ist 0,66 und so weiter.
Und dann die eins addiert.
Und so komme ich eben zu dem Ergebnis 1,66 und so weiter.
Beziehungsweise gar nicht mal so weiter, sondern weil ich eben
Rundungsfehler habe, wird hier tatsächlich auch auf die sieben am Ende
gerundet und ich bekomme nicht das exakte Ergebnis, wo ich ja hier
Periode 6 umdrüber schreiben müsste.
Ja, 42 habe ich nur so dazwischen geschrieben.
Okay, welches Ergebnis liefert die Berechnung hier rechts?
Ich habe also ganze Zahlen, hier vier plus zwei geteilt durch drei.
Wer meint, dass A hier das richtige Ergebnis ist?
Also zwei, A natürlich nicht, zwei.
Okay, wer meint, dass B das richtige Ergebnis ist?
Das sind einige.
Und wer meint, dass C das richtige Ergebnis ist?
Ach, das kann eigentlich auch nicht sein.
Ich hatte hier vorher auch eine 1 stehen und deswegen.
Aber das macht sowieso keinen Sinn, dieses Ergebnis.
Aber nein, Sie haben alle recht.
Die Antwort ist 4, weil eben bei der Division mit ganzen Zahlen, also
hier zum Beispiel Integer, eben nur eine ganze Zahl auch wieder
rauskommen kann.
Das heißt 2 durch 3 als Integer gerechnet ist 0 mit dem Rest 2.
Aber das war ja hier nicht gefragt.
Deswegen ist das Ergebnis von 0 plus 4 dann eben auch wieder 4.
Okay, nächste Frage wäre, ob man mit a kleiner b kleiner c testen
kann, ob diese drei Zahlen geordnet sind.
Wer meint ja?
Ich frage auch gleich, wer meint, ich weiß nicht, aber wer meint nein?
Okay, das sind einige.
Ich frage vielleicht nicht, wer sagt, er weiß es nicht.
Also einige haben, glaube ich, auch einfach sich nicht gemeldet, was
natürlich auch völlig in Ordnung ist.
Genau, das ist auch richtig.
Das kann man nicht damit testen, ob diese drei Zahlen geordnet sind.
Kann man sich so erklären.
Man würde erst mal anfangen und das hier auswerten a kleiner b.
Aber das Ergebnis von diesem Vergleich wäre ja ein Wahrheitswert.
Also das wäre entweder true oder false.
Und das könnte ich da nicht nochmal mit der Zahl c sinnvoll
vergleichen.
Also es heißt da, das würde in Java so nicht gehen.
Oder würde sogar schon der Compiler gleich sagen, dass man das so
nicht hinschreiben kann.
Können Sie gerne dann auch mal ausprobieren zu Hause, was der Compiler
dann sagt.
Und die dritte Frage, kann ich die Operatoren größer und kleiner
verwenden, um Strings zu vergleichen?
Wer meint ja?
Und wer meint nein?
Richtig, von daher könnte ich jetzt auch sagen, also die meisten haben
nein gesagt und das ist auch richtig.
Gewisserweise richtig geraten war mir jetzt gerade auch noch wieder
aufgefallen, weil ich hatte auf der Folie zu den Strings ja auch
gesagt, dass es auch noch weitere Operatoren gibt, die ich in dem
Moment nicht vorgestellt habe.
Aber es ist richtig, dass es diese beiden Operatoren nicht gibt.
Es gibt also nur diesen Konkatenationsoperator von den Operatoren aus
unserer Tabelle, der auch für Strings anwendbar ist.
Und die weiteren Operationen auf Strings, die werden durch Methoden
auf der Klasse String dann dargestellt.
Also diese ganzen anderen Operatoren aus der Tabelle kann ich auf
Strings nicht verwenden.
Nur das Plus für die Konkatenation ist da eine Ausnahme.
Die Frage war, warum es beim ersten so rauskommt.
Es ist tatsächlich die Division stärker bindender als das Plus.
Müsste ich jetzt auf die Tabelle zurückgehen.
Aber der kleinere Wert ist bindender.
Das ist wahrscheinlich vielleicht der Denkfehler.
In der Tabelle steht es falsch drin.
Das wäre natürlich schlecht.
Wenn Sie es bei sich in Java eingeben, kommt es richtig so raus.
Das habe ich nämlich extra, damit mir keine Fehler passieren, auch
nochmal ausprobiert.
Je kleiner der Wert, desto stärker die Bindung.
Also die Sachen, die weiter oben stehen, binden stärker.
Das steht unten nochmal drunter unter der Tabelle.
Sie haben jetzt die Folie da wahrscheinlich ausgedruckt.
Sonst kommen Sie gleich nochmal her, dann nach der Vorlesung.
Dann können wir es auch nochmal aufklären, falls da noch
Aufklärungsbedarf ist.
Gut, soviel zu den Datentypen.
Wie gesagt, der zweite Teil der Vorlesung werden dann eben die
Variablen.
Also sage ich mal, jetzt wird es interessant, dass ich mit den Werten,
die ich mit den Datentypen beschreiben kann, auch etwas tun kann, also
auch etwas rechnen kann.
Eine Variable ist in der Programmierung ein Platzhalter für die Werte
eines Datentyps.
Und Sie sind wahrscheinlich über die Variablen, die ich eben auch
schon verwendet habe, gar nicht so sehr drüber gestolpert, weil Sie
das aus dem Matheunterricht so ein bisschen ja auch schon kennen.
Allerdings ist es ja auch ein bisschen unterschiedlich, muss man also
auch aufpassen.
Man kann also zum Beispiel die Variablen X und Y deklarieren.
Das ist jetzt hier nicht gezeigt und dann sowas wie das hier
hinschreiben.
X plus Y größer 4, sodass mein Programm, wenn ich es dann ausführe,
zur Laufzeit überprüft, ob der Wert, der hier unter der Variable X
gespeichert ist, plus der Wert, der unter der Variable Y gespeichert
ist, zusammen größer als 4 ist oder nicht.
Bevor ich Variablen verwenden kann, wie hier oben, muss ich sie
erstmal deklarieren.
Und das ist bei Variablen das Festlegen eines Namens für diese
Variable und eines Typs für diese Variable.
Sieht dann aus wie hier.
Also hier erstmal ein Beispiel einer Variablen-Deklaration.
Ich kann hinschreiben float a, Semikolon.
Das Schema dazu ist, ich gebe erst den Typ an, zum Beispiel einen
meiner primitiven Datentypen und dann einen Namen.
Diese Variable hat also den Namen a.
Damit habe ich dann deklariert, dass es in meinem Programm eine
Variable mit dem Namen a gibt.
Ich kann das auch ein bisschen platzsparender hinschreiben.
Wenn ich gleich drei Variablen deklarieren will, kann ich nur einmal
Integer hinschreiben und dann x, y, z.
Dann habe ich also drei Integer-Variablen deklariert.
Und wie funktioniert jetzt dieses Platzhalten für Werte?
Man kann sich das so vorstellen, dass eine Variable ein Name für einen
Speicherplatz ist.
Also man kann auch sagen, ein abstraktes Gefäß ist, in das Werte
hineingelegt werden können.
Deswegen hier so diese Schreibweise, das hier soll so ein Gefäß sein.
Es ist festgelegt, von welchem Typ dieses Gefäß ist.
In diesem Fall ein Integer-Gefäß.
Da passen sozusagen nur Integer rein.
Dieses Gefäß hat einen Namen, nämlich in unserem Beispiel hier x.
Das ist die Variable x.
Und in diesem Gefäß kann dann auch ein Wert stehen.
Da kann ich einen Wert reinschreiben und ich kann den Wert auch wieder
herauslesen oder verwenden.
Das ist da so das Prinzip und so ist es dann eben auch tatsächlich im
Rechner.
Im Speicher sind dann diese Werte entsprechend abgelegt.
Jetzt ist es natürlich nicht besonders spannend, einfach nur Gefäße zu
haben, wo Werte drinstehen, sondern ich will natürlich damit auch
etwas machen.
Das erste, was ich mir dazu anschauen muss, ist die Zuweisung.
Das ist also ganz allgemein, wie ich die Werte von Variablen ändern
kann.
Das kann ich gebrauchen, wenn ich zum Beispiel einen Zustand eines
Objektes habe.
Wir hatten da ja schon in der Vektor-2D-Klasse diese x-Position und y
-Position zum Beispiel gehabt.
Die kann ich jetzt also mit Zuweisung ändern, die Werte davon ändern.
Und das funktioniert nach diesem Schema.
Ich schreibe hin Name, also Variablen-Name gleich Wert.
Gleich ist also der entsprechende Zuweisungs-Operator.
Und das bedeutet, dass der Variablen mit dem Name, Name, der Wert, der
dahinter steht, zugewiesen wird.
Beispiel hier.
Wenn ich hinschreibe x, eine Variable, die nehmen wir mal an, vorher
schon deklariert war, gleich 4295.
Habe ich vielleicht vorher den Wert 0 in dieser Variable gehabt.
Und wenn ich diese Zuweisung dann ausführe, steht nachher in der
Variable der Wert 4295 drin.
Nach einer Zuweisung ist also der neue Wert, den ich rechts vom
Zuweisungs -Operator stehen hatte, dann in der Variable gespeichert.
Das ist ganz zentral zu wissen.
Und sonst, wenn jetzt Sie sagen, irgendwie nicht klar, dann schauen
Sie sich das auf jeden Fall nochmal an, wie das gemeint ist.
Aber wir haben auch noch ein Beispiel.
Einige Beispiele für Zuweisungen, an denen das vielleicht ein bisschen
deutlicher wird.
Ich kann hinschreiben x, ich lese jetzt mal x gleich y, aber das ist
ja kein Gleich, sondern tatsächlich eine Zuweisung.
Ich sage also vielleicht lieber, der Wert von y soll dem Wert von x
zugewiesen werden.
Das ändert dann den Wert der Variablen x, also immer dem des Wertes
auf der linken Seite und setzt ihn auf den Wert der Variable y.
Da wird also der Wert aus dem Gefäß von y heraus kopiert und auch in
das Gefäß von x hineingesteckt.
Ich kann auch sowas hinschreiben, ich könnte sagen x gleich x plus 1.
Das bedeutet, es wird hier erst rechts ausgewertet, was soll denn der
neue Wert sein?
Ich rechne also das, was vorher in x drin stand, plus 1.
Wenn zum Beispiel vorher in x 4 drin stand, würde hier das Ergebnis 5
sein.
Ich würde dann diesen Wert 5 wieder in die Variable x hineinschreiben.
Also diese Zuweisung nimmt den bisherigen Wert von x, addiert 1 dazu
und setzt die Variable x auf den neuen um 1 größeren Wert.
Und ein weiteres Beispiel, hier unten nochmal gezeigt, also ein ganzer
Beispielablauf.
Wenn wir jetzt mal annehmen, wir sind in einem Programmablauf, wo ich
zwei Variablen habe, x und y.
Und diese beiden Variablen haben am Anfang den Wert 10 und 6, wo auch
immer das jetzt herkommt.
Wir nehmen es einfach mal an.
Wenn ich dann diese Zuweisung hier ausführe, x gleich x plus 1, dann
habe ich ja hier oben eben den Fall gehabt, nehme ich den bisherigen
Wert von x, also 10, addiere 1 drauf, also 11 und speichere diesen
Wert dann wieder in der Variable x.
Also lege den in das Gefäß hinein.
Das heißt, wenn ich das hier ausgeführt habe, habe ich nachher die
Werte der Variablen 11.
Das x hat den Wert 11 und das y hat immer noch den Wert 6.
Damit habe ich ja nichts gemacht.
Wenn ich dann die nächste Zuweisung habe, x gleich y, dann heißt das,
ich nehme den Wert der Variablen y und lege ihn auch in das Gefäß x,
also in die Variable x hinein.
Der Wert der Variable y hier war 6.
Das heißt, wenn ich diese Zuweisung durchgeführt habe, ist danach auch
der Wert meiner Variable x dann 6.
Und wenn ich dann noch wieder sowas hier hinschreibe und das ist
jetzt, das sage ich gleich nochmal, wenn ich sowas hinschreibe, y ist
gleich 2 mal x plus 1, dann hat die Variable x ja zu diesem Zeitpunkt
den Wert 6.
Ich rechne dann also 6 mal 2 sind 12 plus 1 sind 13.
Und dieses Ergebnis dieser Berechnung wird also wieder zugewiesen der
Variable x.
Das heißt, der Wert wird in die Variable x hineingeschrieben.
Wenn ich das hier ausgeführt habe, hat y den Wert 13 und x hat immer
noch den Wert 6.
Ich habe jetzt gerade ja schon mal gesagt, man liest so leicht x
gleich y oder y gleich 2 mal irgendwas.
Das ist aber nur das Gleichheitszeichen.
Es ist eben gerade keine mathematische Gleichheit, sondern es kommt ja
hier auch auf die Reihenfolge drauf an.
Ob ich x gleich y schreibe, ist was anderes, als wenn ich y gleich x
schreibe.
Das eine bedeutet eben, der Wert von y wird x zugewiesen.
Das andere bedeutet, der Wert von x wird y zugewiesen.
Ist also nicht symmetrisch hier und hat also nichts mit der
mathematischen Gleichheit dadurch zu tun.
Das kommt auch gleich auf der nächsten Folie, sehe ich gerade.
Hat nichts mit mathematischer Gleichheit zu tun.
Man sagt eben oft gleich, weil das Zeichen das entsprechende ist.
Auf Deutsch würde mir jetzt kein schönerer Begriff an der Stelle vor
einfallen.
Aber die Zuweisung ist insbesondere nicht symmetrisch, hatte ich
gerade schon gesagt.
x gleich y ändert den Wert der Variablen x und ich habe dann den Wert,
den y auch hat, in x stehen.
Und y gleich x ändert den Wert der Variablen y.
Immer auf der linken Seite findet die Veränderung statt.
Wir hatten eben gesehen, dass man Variablen erst deklarieren muss,
bevor man sie verwendet.
Und jetzt hatten wir gesehen, wie die Zuweisung funktioniert.
Man kann das auch noch ein bisschen abkürzend schreiben.
Man kann anstatt erst eine Deklaration zu schreiben.
Es soll eine Variable vom Typ Float geben und dann zu sagen, diese
Variable soll den Wert 2,5 haben.
Kann man auch verkürzt schreiben Float a gleich 2,5 f, um quasi die
Deklaration und die Zuweisung gleich in einer Zeile zu haben.
Gut, eine Zeile war es ja auch, aber in einer Anweisung zu haben.
Und das nennt man dann Initialisierung.
Also die Kombination von Deklaration und Zuweisung nennt man
Initialisierung.
Das Schema ist einfach, ich schreibe den Typ hin, den Namen meiner
Variablen, die ich ja hier gleichzeitig auch deklariere und gebe
dieser Variablen dann auch gleich einen Wert.
Dann habe ich beides zusammen gemacht.
Noch kurz zur Verknüpfung mit den anderen Sachen, die wir schon gehört
haben.
Wo sind uns Variablen bisher begegnet?
Wir haben Variablen schon gesehen als Attribute von Klassen.
Ich hatte gerade schon diese X-Position und Y-Position in dem Vektor
2D erwähnt.
Das sind zum Beispiel auch Variablen, die als Attribute meiner Klasse
definiert sind.
Genau.
Gut.
Auch hier nochmal etwas Gelegenheit, das selber oder eben mit
demjenigen oder derjenigen, die neben Ihnen sitzt, zu vertiefen.
Jetzt sind Sie also gefragt und zwar, was die Ausgaben dieser
folgenden Programm Teile wären.
Einmal hier links.
Da habe ich drei Ausgaben, deswegen hier getrennt.
Erste Ausgabe, zweite und dritte.
Und hier rechts nochmal ein kleines Programm mit zwei, also was nur
eine Ausgabe macht und da eben die Frage, welche.
Auch da vielleicht auch nur vier Minuten Zeit reicht wahrscheinlich.
Dann genau.
Denken Sie nach oder diskutieren Sie gleich los mit Ihrem Nachbarn.
Gut, dann würde ich jetzt auch schon auflösen wollen.
Scheint mir ein ganz guter Zeitpunkt zu sein.
Oder was heißt auflösen?
Sie erst mal fragen, was Sie denn meinen.
Die Frage wäre jetzt erst mal nach der ersten Ausgabe.
Das heißt, was wird denn ausgegeben durch dieses, diesen Ausdruck
hier?
Ist es A, 5,5 oder B, 5?
Wer meint A, also 5,5?
Wer meint B?
Keiner.
Genau, 5,5 ist auch richtig.
Also Sie hätten jetzt auch auf die Idee kommen können, dass man da
irgendwie runden soll.
Aber nein, warum sollte man das tun?
Die Daten, also die Werte, die ich hier berechne und die Datentypen
hier sind eben Fließkommazahlen, mit denen ich nicht alle reellen
Zahlen, aber ein paar Kommazahlen ausdrücken kann.
Und entsprechend ist das Ergebnis hier auch wieder die in diesem Fall
sogar exakte Berechnung von 3 plus 2,5.
Dann geht das Programm hier ja weiter.
Macht nochmal eine Zuweisung an A.
Was wäre denn die Ausgabe hier in der nächsten Zeile?
Ist das 5,5?
Oder ist das 6,5?
Genau, das sehen auch die meisten richtig.
Es ist immer noch 5,5, weil hier wird ja immer noch C ausgegeben.
Ist jetzt nicht so wie beim Taschenrechner, dass man immer das
vorherige Ergebnis ausgibt, sondern hier die Anweisung ist, den Wert
der Variable C auszugeben.
Und den hatten Sie hier oben ja schon richtig bestimmt als 5,5.
Wenn ich hier mit den anderen Variablen was herumrechne, dann ändert
sich der Wert von C erstmal nicht.
Also von daher vielleicht auch zu einfach.
Was ist dann die dritte Ausgabe, wenn ich A hier ausgebe?
Da habe ich jetzt mal drei verschiedene Möglichkeiten aufgeschrieben.
Wer meint A 5,5?
Wer meint B 3,5?
Und wer meint C 4,0?
Genau, das ist auch richtig.
A hatte ich zwar vorher den Wert 2,5 gegeben, aber jetzt hier, das ist
nicht mehr relevant, weil hier führe ich jetzt ja die Berechnung durch
B, also was den Wert 3 hat, plus 1 und das Ergebnis wäre dann 4.
Und was vorher in A drin gestanden hat, ist halt nicht relevant.
Das haben Sie alle richtig so gesehen.
Auf der rechten Seite nochmal ein anderes kleines Beispielprogramm mit
weiteren Floats.
Was wird hier ausgegeben, wenn ich I minus J rechne?
Ist es A 1?
Wer meint 1?
Oder ist es B 0,999 und so weiter 4?
Einige haben die Hand jetzt auch glaube ich nicht gehoben, waren
vielleicht ein bisschen verwirrt, aber ich habe das hier nochmal
aufgenommen, um nochmal darauf hinzuweisen, dass bei Floats
Rundungsfehler stattfinden können.
Wenn man das jetzt von Hand rechnen würde, würde man natürlich sagen
1.
Aber die richtige Antwort hier bei Floats in Java ist eben 0,9994.
Können Sie direkt so auch mal in Ihr Programm eingeben, dann werden
Sie diese Ausgabe bekommen, weil eben schon diese Zahlen hier nicht
exakt dargestellt werden können in diesem Wertebereich Float.
Und wenn ich dann mit denen rechne, kommt es eben leicht zu
Rundungsfehlern.
Das heißt, das habe ich hier nochmal aufgenommen als Aufgabe, weil es
wichtig ist zu wissen, dass Floats und auch Doubles nur eine begrenzte
Genauigkeit haben.
Wenn Sie also irgendwas sicherheitskritisches programmieren, dann mal
irgendwann dürfen Sie sich nicht darauf verlassen, dass die Ergebnisse
da immer genau sind.
Okay, noch ein bisschen weiter mit der Zuweisung.
Wir hatten ja gerade schon gesagt, dass Attribute von Klassen auch so
eine Art Variablen sind.
Und entsprechend kann ich auch mit der Zuweisung die Werte für
Attribute setzen.
Hatten wir auch schon mal gesehen, aber hier jetzt mal auch wirklich
erklärt.
Ich kann Attribute dann mit Objektnahme Punkt Attributnahme
ansprechen.
Zum Beispiel wie hier gezeigt, wenn ich ein Objekt Vektor 2D neu
erzeugt habe und das unter der Variable P abgelegt habe, dann kann ich
dann so eine Zuweisung machen.
Ich kann schreiben p.x gleich 4.
Und das ist also eine Zuweisung, dass der Wert 4 in die Variable, also
in das Attribut p.x hineingeschrieben wird.
Da würde ich also im Endeffekt, wenn ich dann auch noch diese zweite
Zuweisung mache.
p.y ist 22,5.
Hätte am Ende nach der Ausführung dieser drei Zuweisungen das Objekt
die folgenden Werte hier in den Attributen stehen.
Und entsprechend, wenn ich ein zweites Objekt erzeuge, das unter der
Variable Q ablege und dann diese Zuweisung mache, habe ich danach ein
Objekt, was entsprechend hier die Werte in den Attributen stehen hat.
Also was da nur wichtig ist, Objektnahme Punkt Attributnahme ist, wie
ich auf die Werte zugreifen kann und auch Werte zuweisen kann.
Mit Objektnahme meine ich hier den variablen Namen, unter dem ich mein
Objekt vorher abgelegt habe.
Variablen Name, unter dem ich Objekte abgelegt habe, habe ich gerade
gesagt, da könnten Sie vielleicht auch uns stutzen.
Wie kann man sich das jetzt als Gefäße vorstellen?
Habe ich da jetzt ein Gefäß, wo ein ganzes Objekt irgendwie drin
liegt?
Das wollen wir uns jetzt im Folgenden mal anschauen, wie insbesondere
Objektvariablen und Objekte im Speicher abgelegt werden, weil das dann
auch noch ein paar Besonderheiten hat, die man beim Programmieren
beachten muss.
Aber vielleicht erstmal allgemein, wie sind Variablen überhaupt im
Speicher abgelegt?
Der Hauptspeicher besteht, so ein bisschen vereinfacht gesagt, aus
einer Reihe von Speicherplätzen.
Also hier zum Beispiel Platz 1, Platz 2, Platz 3, in die ich meine
Variablen hineinlegen kann.
Und diese Speicherplätze sind durchnummeriert.
Das sind sogenannte Speicheradressen, wie ich auf diese Werte dann
auch wieder zugreifen kann.
Das hier ist jetzt eine gewisse abstrakte Sicht auf den Speicher.
In Wirklichkeit sind diese Speicherzellen kleiner und können jeweils
nur ein Byte aufnehmen.
Und größere Datentypen wie Integer, wie hier gezeigt, liegen dann auf
mehrere nachfolgenden Adressen.
Aber davon haben wir jetzt mal hier abstrahiert.
Das macht man ja beim Programmieren auch so.
Und nehme jetzt mal im Folgenden an, dass ich immer einen Wert auch in
eine Speicherzelle schreiben kann, um das jetzt hier nicht noch
unnötig zu verkomplizieren.
Genau, das heißt, in diesem Beispiel hätte ich jetzt also einige
Variablen, alles Integer, hier sind auch Float-Variablen, sonst
Integer -Variablen mit entsprechenden Werten in dem Speicher abgelegt.
Bei Objekt-Variablen ist es jetzt ein bisschen spezieller.
Bei überhaupt die Objekte werden im Speicher abgelegt, indem die Werte
der Attribute an aufeinanderfolgenden Speicheradressen abgelegt
werden.
Also hier zum Beispiel, wenn ich so ein Objekt des Typs Vector2D habe,
und das hat ja die Attribute x und y.
Wir nehmen jetzt mal an, es hat die Werte der Attribute 2 und 3,5.
Dann liegen irgendwo im Speicher nebeneinander ein x und ein y mit den
entsprechenden Werten 2,0 und 3,5.
Und dieses hier zusammen, diese zwei Speicherzellen, sind dann die
Objektidentität dieses hier erzeugten Objekts, wollte ich gerade
sagen, habe ich jetzt hier nicht dargestellt.
Aber das ist also die Objektidentität eines Objektes, was gerade die
Werte 2,0 und 3,5 für x und y abgelegt hat.
Das ist sozusagen das Objekt selber, kann man sich auch merken.
Dann, wenn ich Objektvariablen habe, sind das Referenzen auf
Objektidentitäten.
Also das heißt, in einer Objektvariablen wird die Speicheradresse des
ersten Attributs eines Objekts abgelegt.
Wenn ich also eine variable Position zum Beispiel habe vom Typ
Vector2D, also wenn ich irgendwo hingeschrieben habe Vector2D Position
gleich New Vector2D, dann ist das, habe ich auch eine Variable im
Speicher vom Typ Vector2D mit dem Wert der Speicheradresse, wo mein
eigentliches Objekt liegt.
Das ist im ersten Moment ein bisschen verwirrend.
Müssen Sie vielleicht dann sich nach der Vorlesung ein bisschen Zeit
nehmen, um das auch nochmal nachzuvollziehen.
Aber ich habe auch noch einige Beispiele mitgebracht.
Beispiel hier, die folgende Situation.
Wir nehmen mal an, wir haben schon drei Variablen P, Q und R jeweils
vom Typ Vector2D deklariert.
Also da sind schon die Speicherzellen dafür reserviert, aber da sind
auch keine Werte drin.
Wenn ich jetzt die folgenden Zuweisungen ausführe.
P ist ein New Vector2D, also ein neues Objekt erzeuge.
Dann sage ich, P.X ist 2 und P.Y ist 3,5.
Dann hätte ich danach, nachdem ich diese Dinge ausgeführt habe, die
folgende Situation mit meinem Speicher.
Ich hätte hier neue Variablen, also neuen Speicher für neue Variablen
reserviert, nämlich mein X und mein Y, das sind ja die Attribute
meines Vektors.
Und da die Werte 2 und 3,5 reingelegt und die Variable P hat jetzt den
Wert 17.
Das heißt, sie zeigt auf die Speicherzelle Nummer 17.
Entsprechend ist es, wenn ich das gleiche nochmal mit einem zweiten
Objekt mache und die Zuweisung mache, würde ich dann ein zweites
Objekt erzeugen und für die Variable Q hätte ich den Wert 20, weil das
eben die Speicheradresse ist, wo das zweite Objekt liegt.
Und wo diese Objekte liegen, das wird von Java natürlich auch nicht
beliebig entschieden, aber das muss nicht unbedingt direkt dahinter
sein, sondern das kann, oder ist typischerweise woanders im Speicher,
auch da jetzt nicht weiter drauf eingehen.
Und dann, interessant, was passiert, wenn ich noch die dritte
Variable, der dritten Variable einen Wert zuweise, nämlich R gleich P
schreibe.
Da wird dann kein weiteres Objekt erzeugt, sondern das einzige, was
passiert ist, ist, dass der Wert der Variable P genommen wird und der
Variable R zugewiesen wird.
Also genau so, wie wir es vorher auch definiert haben für die
Zuweisung.
Ich nehme den Wert von P und schreibe ihn in R rein.
Und an den eigentlichen Objekten ändert sich hier nämlich gar nichts,
nur an der Referenz.
Ja.
Da würden Sie in dem Fall einen Kompilierfehler bekommen oder ein
String, müssen Sie ausprobieren, den Kompilierfehler, weil das Plus
für Vector2D nicht definiert ist.
Das können Sie hier probieren hinzuschreiben, da müsste der Compiler
Ihnen gleich sagen, dass das für diese Klasse Vector2D keine gültige
Operation ist, also weil die Datentypen geben ja an, welche
Operationen möglich sind.
Okay, jetzt noch ein bisschen interessanter und das können Sie sich
jetzt mal selber überlegen, was passiert.
Was passiert, wenn ich jetzt die Zuweisung mache, P gleich Q, also den
Wert von Q will ich P zuweisen.
Was passiert dann wohl?
Ich frage es jetzt nicht ab, ich will nur kurz Gelegenheit geben, mal
selber darüber nachzudenken.
Da muss ich hier jetzt vorhalten, dass vielleicht, dass die Computer
ja in stupider Weise das abarbeiten sollen, was vorher definiert war.
Was hier dann passiert, ist nur, dass der Wert von Q genommen wird und
in die Variable P reingeschrieben wird.
Das heißt, ich habe danach auch die 20 in P drinstehen.
Das heißt, diese Objektvariable P zeigt nicht mehr auf dieses Objekt
hier an der Stelle 17, sondern die Objektvariable P zeigt auf das
Objekt an der Stelle 20.
Und die Objekte selber haben sich wieder gar nicht verändert.
Man muss also bedenken beim Programmieren, dass mehrere Referenzen auf
dieselbe Objektidentität verweisen können, wie hier.
Und die Objektidentität selber wird nicht verändert.
So hier nochmal ein bisschen ähnlich dargestellt, aber um das nochmal
zu verdeutlichen, weil es echt wichtig ist.
Wenn wir uns das jetzt nochmal so darstellen, den Speicher, gleiche
Situation wie eben.
Links habe ich diese drei Objektvariablen und rechts hätte ich diese
beiden Objektidentitäten.
Und hier sind jetzt mal die Referenzen, also hier die 17 als Pfeile
dargestellt, um das ein bisschen einfacher zu machen.
Also die 17 hier, P hat den Wert 17, das heißt P zeigt auf diese
Objektidentität hier.
Genau, jetzt hatten wir ja gerade auch schon das Beispiel, was
passiert, wenn ich in dieser Situation jetzt P gleich Q setze.
Ich blende das mal direkt ein.
Wir haben es ja eben auch schon kurz gesehen.
Dann wird also der Wert von P auch auf 20 gesetzt.
Das war der Wert, den Q vorher hatte.
Das heißt, dass ich diese Referenz hier sozusagen umbiege, so sagt man
auch oft.
Also P, die Objektvariable P zeigt jetzt auch auf dieses Objekt an der
Stelle 20.
Bei Zuweisung werden eben nur die Objektreferenzen verändert.
Jetzt nochmal zum selber kurz überlegen.
Wenn ich jetzt so eine Zuweisung P plus X gleich P Punkt X gleich 7,5
mache, was passiert dann wohl?
Wir hatten ja eben schon gesehen, durch das P Punkt X kann ich die
Attribute meiner Objekte ansprechen.
Man nennt es auch Dereferenzierung.
Also durch das P Punkt X würde ich erstmal hier sozusagen, also würde
der Rechner jetzt erstmal zu dem P gehen und dann sagen, das
bezeichnet dieses Objekt und ich meine dann die Variable X in diesem
Objekt.
Das heißt, hier wird dieses X 7,5 verändert.
Dann vielleicht noch die Frage, was ich jetzt nicht auf der Folie
habe.
Was würde denn nun wohl passieren, wenn ich R Punkt X ausgebe?
Was würde ich als Ergebnis bekommen?
Eine Frage oder eine Antwort?
Okay, dann warte ich noch einen Moment und dann...
Bitte?
7,5, genau, weil das R zeigt auf das gleiche Objekt wie P. Das heißt,
das Objekt wurde durch diese Zuweisung eben von P verändert.
Wenn ich dann mit R Punkt X auf das gleiche Objekt zugreife, dann
kommt trotzdem 7,5 raus und nicht, wie man vielleicht meinen könnte,
2.
Aber vielleicht ist Ihnen das auch völlig klar, umso besser.
Genau, man muss aber, wie gesagt, wirklich bei der Programmierung
aufpassen, dass mehrere Referenzen auf dasselbe Objekt eben verweisen
können.
Und man spricht dann auch von Aliasing.
Das heißt, das eine Objekt, was ich eigentlich nur habe, hat mehrere
Namen.
Also wie ein Alias im Person.
Genau, das haben wir gerade gesehen.
Okay, zwei spezielle Dinge noch in diesem Zusammenhang.
Es gibt einen speziellen Wert, den Objektvariablen annehmen können.
Das ist der Wert 0.
Und das bedeutet, dass diese Objektreferenz vielleicht noch auf kein
Objekt zeigt.
Also es ist irgendwie eine leere Objektreferenz.
Und das ist jetzt eigentlich auch das, was ich eben bei dem PQR
Beispiel als Initial hätte in die Speicherzellen reinschreiben können.
Das heißt, über dieses Objekt, über diese Objektvariable ist kein
Zugriff auf Attribute oder Methoden von irgendwas möglich.
Und die Objektidentität wird eben erst dadurch erzeugt, dass ich mit
dem New Operator, den Sie ja schon kennen, ein neues Objekt dann auch
erzeuge.
Nur dann entstehen neue Objekte.
Es gibt auch Konstanten im Gegensatz zu Variablen, die eben nicht
veränderlich sind.
Ich kann Konstanten definieren mit dem Schlüsselwort Final.
Ansonsten sieht es aus wie eine Variablen-Deklaration.
Das macht Sinn für solche Dinge wie die Gravitationskraft der Erde
oder Pi.
Die ändern sich halt nicht im Laufe meines Programmablaufs.
Und da kann ich auch gleich sagen, das sind keine Variablen, sondern
konstante, also feste Werte.
Sollte ich so nur machen mit primitiven, also mit Variablen, die
primitiven Datentypen beschreiben mit Objektvariablen, dann macht das
so keinen Sinn, weil das würde nur heißen, die Speicheradresse ist
fest und man könnte damit nicht sicherstellen, dass sich die
Objektidentität nicht ändert.
Also da sollte man aufpassen.
Genau, noch zum Zusammenhang zwischen Datentypen und Variablen.
In Java ist es eben so, dass alle Variablen einen Datentyp haben und
dieser Datentyp muss auch schon zur Compile-Zeit bekannt sein.
Wir hatten eben die Rückfrage von da oben, wie das mit dem Plus und
den Strings aussieht war es, glaube ich.
Und das ist eben ein Punkt, dass Java da schon zur Compile-Zeit
überprüft, ob Operatoren und Variablen und Werte zumindest vom Typ her
zueinander passen.
Wenn ich jetzt also schreibe, ein String Adresse ist gleich 4, dann
würde der Compiler schon sagen, das passt nicht zusammen.
Die Adresse muss eine Zeichenkette sein, also mit Anführungszeichen
und 4 ist eine Zahl, das passt nicht zusammen.
Genau, die erlaubten Operatoren hängen eben auch vom Datentyp ab, von
daher gute Frage vorhin.
Das Plus wäre, oder was hatten wir eben als Frage?
Geteilt wäre kein erlaubter Operator zum Beispiel für einen String.
Gut, fassen wir heute nochmal zusammen, bevor ich dann nochmal auf
diesen Advanced Organizer zurückkomme.
Heute haben wir Typen und Variablen uns angeschaut und sie haben
gelernt, dass Java diese 8 elementaren Datentypen hat.
Auch, dass Klassen auch Datentypen sind, die sie selber definieren
können.
Und sie haben gelernt, dass mit jedem Datentypen auch Operationen
verbunden sind.
Also der Datentyp definiert den Wertebereich und die möglichen
Operationen.
Variablen sind dann Namen für Speicherplätze, wo ich
Zwischenergebnisse und Zustände ablegen kann.
Und ich kann diese Variablen durch diesen Zuweisungsoperator, das
Gleichheitszeichen, verändern.
Wir haben noch zwei Minuten.
Ich würde kurz um Ruhe und um Wiederhinsetzen bitten, dass diejenigen,
die noch bis zum Ende zuhören wollen, das auch tun können.
Ich werde auch pünktlich aufhören.
Sie bleiben ruhig und ich höre pünktlich auf.
Das ist doch ein Deal.
Ich kann Variablen durch Zuweisung verändern.
Objektvariablen, das ist wie gesagt wichtig zu wissen, sind
Referenzen.
Das heißt, in der Objektvariable selber steht nur die Speicheradresse,
wo das eigentliche Objekt liegt.
Und gut, die spezielle Objektreferenz 0 gibt es auch noch.
Die steht für kein Objekt.
Gehe dann nochmal zurück auf unseren Advanced Organizer.
Wir haben uns also heute quasi die Datentypen auf dieser Seite
vervollständigt.
Klassen kannten sie ja schon.
Primitive Datentypen haben wir jetzt kennengelernt.
Konvertierung allerdings noch nicht.
Und sie haben gesehen, wie Werte Instanzen von Datentypen sind und wie
Variablen diese Gefäße sind.
Das ist jetzt auch erklärt, wo Werte drin liegen.
Das hier unten ist also im Schaubild.
Dieses Objektvariable ist Referenz auf Objektidentität.
Okay, vielen Dank.
So viel dann für heute.
Wer noch weiter lesen will, findet hier weitere Infos.