Primitive Datentypen & Objekttypen
Bisher haben wir mit den uns bekannten Datentypen einfach nur gearbeitet, ohne uns groß Gedanken über die kleinen aber feinen Unterschiede zu machen.
- Warum werden manche Datentypen klein geschrieben und andere groß geschrieben?
→int, float, char, boolean, ...
→String, PVector, SoundFile, ... - Warum benötigt man für die Wertezuweisung bei manchen Datentypen das Schlüsselwort new und bei anderen nicht?
→int foo = 5;
→int[] bar = new int[10]; - Warum haben manche Datentypen einen "richtigen" Standardwert (z.B. 0 oder false) und andere den Standardwert
null?
Das liegt daran, dass es sich nicht um dieselbe Art von Datentyp handelt. Datentypen lassen sich einteilen in primitive Datentypen und Objekttypen. Dies sorgt dann unter anderem für die oben genannten Unterschiede. Schauen wir uns beide Arten einmal genauer an.
Primitive Datentypen
In Java gibt gibt es acht primitive Datentypen, alle anderen Datentypen werden Objekte bzw. Objekttypen genannt. Das sind die acht primitiven Datentypen (ein paar davon kennst du bereits):
byte, short, integer, long, float, double, char, boolean
Merkmale
Primitive Datentypen zeichnen sich dadurch aus, dass sie einfach nur Werte speichern. Deswegen haben sie auch einen "richtigen" Standardwert und benötigen nicht das Schlüsselwort new bei der Initialisierung. Außerdem werden primitive Datentypen klein geschrieben. Daran kann man schnell erkennen, dass es sich um einen primitiven Datentyp handelt.
Einteilung
Die primitiven Datentypen sind in Gruppen eingeteilt. Zahlen sind in Java so wichtig, dass sechs der acht primitiven Datentypen numerisch sind. Das bedeutet, dass jeder von diesen, bis auf den char und den boolean, Zahlenwerte speichert. Numerische primitive Datentypen lassen sich in ganzzahlige Typen und Gleitpunkttypen unterteilen. byte, short, integer und long bilden somit die ganzzahligen Typen und float und double die Gleitpunkttypen.
| Numerische Datentypen | Sonstige Datentypen | |
|---|---|---|
| Ganzzahlige Typen | Gleitkommatypen | |
| byte, short, integer, long | float, double | booelan, char |
Warum gibt es denn so viele verschiedenen numerische Datentypen? 1-3 würden doch eigentlich reichen. Der Grund hierfür ist wieviel Platz sie im Speicher einnehmen. Jeder dieser Typen besitzt eine feste Zahl an Bit, die zum Speichern des Werts im Speicher verwendet werden, der sogenannte Speicherverbrauch. Mehr Bit bedeutet mehr Platz im Speicher, was wiederum für entweder größere Werte oder eine höhere Genauigkeit sorgt.
Speicherverbrauch
long und byte
Wenn man einem numerischen Datentyp eine kleine Zahl zuweisen möchte, dann wäre es reine Speicherverschwendung den long Datentyp zu verwenden, der eine Größe von 64 Bit hat. Mithilfe von long kann man deswegen ganze Zahlen im Bereich von -263 bis 263-1 darstellen. Für kleine Zahlen, wie z.B. 34, ist der Datentyp byte vollkommen ausreichend. byte hat eine Größe von 8 Bit und einen Wertebereich von -128 bis +127.
Um Speicher zu sparen ist es wichtig zu wissen, welchen Datentyp man verwenden sollte. Bei kleinen Programmen macht das kaum einen Unterschied. Solltest du aber mal an einem größeren Projekt arbeiten, ist es sehr wichtig, wie du mit diesen Datentypen umgehst. Der geläufigste ganzzahlige primitive Datentyp ist dabei der int, da er einen angemessenen Wertebereich mit 32 Bit besitzt.
double und float
Wenn ich einem Datentyp eine Gleitkommazahl zuweisen möchte, dann muss ich den Datentyp float oder double verwenden. Der Unterschied der beiden Typen ist deren Größe. Somit ist double mit 64 Bit doppelt so groß wie float mit 32 Bit und besitzt daher einen größeren Wertebereich. So wird float oftmals als ein Gleitpunkttyp mit „einfacher Genauigkeit“ bezeichnet, während double Gleitpunkttyp mit „doppelter Genauigkeit“ genannt wird. Versucht man diese Gleitpunkttypen zu vermischen, kann es zu Datenverlust führen.
Wenn du explizit einen float-Wert anfordern möchtest, musst du am Ende der Kommazahl ein großes oder kleines „f“ schreiben. Das gleiche gilt auch für einen double-Wert indem du ein großes oder kleines „d“ am Ende setzt.
Übersicht
In dieser Übersicht kannst du sehen, welchen Wertebereich, Speicherverbrauch und Standardwerte die einzelnen Datentypen haben (Wenn du eine Variable nur deklarierst, aber nicht initialisierst, dann bekommt sie einen Standardwert).
| Typname | Größe (Speicherverbrauch) | Wertebereich | Standardwert | Beschreibung |
|---|---|---|---|---|
| boolean | 8 bit | true / false | false | Boolescher Wahrheitswert, Boolescher Typ |
| char | 16 bit | 0 ... 65.535 (z. B. 'A') |
\u0000 (= Zeichen für null ) |
Unicode-Zeichen (UTF-16) |
| byte | 8 bit | -128 ... 127 | 0 | Zweierkomplement-Wert |
| short | 16 bit | -32.768 ... 32.767 | 0 | Zweierkomplement-Wert |
| int | 32 bit | -2.147.483.648 ... 2.147.483.647 | 0 | Zweierkomplement-Wert |
| long | 64 bit | -263 bis 263-1 | 0L | Zweierkomplement-Wert |
| float | 32 bit | -3.40282347 *1038 bis 3.40282347 *1038 | 0.0f | 32-bit IEEE 754 (es wird nicht empfohlen, dies für Programme zu verwenden, die sehr genau rechnen müssen) |
| double | 64 bit | -1.79769313486231570 *10308 bis 1.79769313486231570 *10308 | 0.0d | 64-bit IEEE 754, doppelte Genauigkeit |
Objekttypen
Anders als die primitiven Datentypen, können Objekttypen ganze Objekte speichern - aber was ist so ein Objekt? Ein Objekt kannst du dir bildlich vorstellen, wie einen Gegenstand, zum Beispiel eine Mikrowelle. Eine Mirkowelle hat einen Namen, eine Höhe, Länge und Breite, ein paar Funktionen (an, aus, öffnen, schließen, ...). All das steckt in dem Gegenstand mit dem Typ Mikrowelle. Wenn ich jetzt versuchen würde diese Mikrowelle in Programmcode darzustellen, dann könnte das so aussehen:
MicroWave meineMikrowelle = new MicroWave("Heatmaster 3000"); // Erstellen einer neuen Mikrowelle mit dem Namen "Heatmaster 3000"
// Funktionen der Mikrowelle ausführen
meineMikrowelle.open();
meinMikrowelle.insertFood();
meineMikrowelle.close();
meineMikrowelle.setTimer(120);
meineMikrowelle.start();
println(meineMikrowelle.height); // Höhe der Mikrowelle in der Konsole ausgeben
Hier an diesem fiktiven Beispiel können wir ein paar wichtige Eigenschaften von Objekttypen sehen:
- Objekttypen werden groß geschrieben, damit man sie als solche erkennt. Hier lautet der Datentyp für mein Mikrowelle
MicroWave. - Objekttypen werden in der Regel mit dem Schlüsselwort
newinitialisiert. Hier gibt es nur wenige Ausnahmen, bei denen man sowohl new verwenden kann als auch eine andere, "einfache" Schreibweise. Das ist zum Beispiel bei String oder bei Arrays der Fall. - Objekttypen haben Funktionen und Felder, auf die mithilfe der Punktnotation zugegriffen werden kann. Funktionen werden (wie gewöhliche Funktionen auch) mit Klammern geschrieben, z.B.
.open().Felder hingegen sind einfach nur Variablen und werden somit ohne Klammer geschrieben, z.B.name.
Objekte sind also deutlich komplexere Konstruktionen, als Werte. Aus diesem Grund haben Objekte auch den Standardwert null, also "nichts".
Klassen
Damit du ein Objekt erstellen kannst, braucht du ein Klasse. Eine Klasse ist sowas wie ein Bauplan für ein Objekt und legt fest, welche Felder und Funktionen dein Objekt haben soll.
Das kannst du dir so vorstellen, wie die Aufbau-Anleitung für ein Billy-Regal von IKEA und ein tatsächliches Billy-Regal. Mit der Aufbau-Anleitung (Klasse) kannst du nicht viel machen. Du kannst da z.B. keine Bücher reinstellen oder Türen auf und zu machen, aber du kannst dir dein eigenes tatsächliches Billiy-Regal daraus bauen. In das fertige Billy-Regal (Objekt), kannst du dann Bücher reinstellen (also sowas wie ein Wert zuweisen) oder Türen auf und zu machen (Funktionen ausführen). Und: Du kannst ganz viele Billy-Regale erstellen, die alle nach dem gleichen Schema aufgebaut sind.

In unserem Beispiel von oben würde die Klassen Microwave festlegen, dass jede Mikrowelle die Felder (Variablen) name und height hat und die Funktionen open(), instertFood(), close(), setTimer() und start().
Kurz gesagt: eine Variable deren Datentyp ein Objekttyp ist, speichert ein Objekt. Dieses Objekt wird erstellt mithilfe einer Klasse, die festlegt, wie dieses Objekt aufgebaut ist.
In diesem Modul werden wir das Thema Objekte und Klassen nur streifen, da wir Objekte verwenden. Tiefer einsteigen in diesem Thema werdet ihr im Modul Objektorientierte Programmierung.
Referenztypen & Wertetypen
Nun beschäftigen wir uns ein wenig damit, wie die CPU Daten speichert, abruft und verwendet. Wir haben das Thema ganz am Anfang einmal kurz gestreift und dann angefangen mit Variablen zu arbeiten, aber haben wir uns wirklich Gedanken gemacht, was da eigentlich in dieser "Blackbox" namens "Computer" vor sich geht?
Variablen
Denken wir nochmal zurück: Was genau ist eine Variable eigentlich? An Anfang haben wir das der Einfachheit halber einen "Container" für Daten genannt. Gehen wir aber mal einen Schritt weiter:
- Eine Variable besteht aus Datentyp, Bezeichner und Literal.
- Der Literal ist ein irgendwo im Speicher abgespeicherter Wert für eine Variable.
Eine Variable ist sozussagen eine Referenz, ein Verweis auf diese Stelle im Speicher. Du kannst dir das vorstellen wie eine URL. Eine URL ist eine Adresse, ein Verweis auf eine Webseite, die irgendwo im Internet gespeichert ist. Um dir die Webseite ansehen zu können brauchst du nicht den Code der Webseite selbst, es reicht einfach den Link zur Webseite zu haben.
Null
Eine Referenz kann auch auf "nichts" zeigen. Bei Webseiten kennen wir alle den 404-Error bzw. den Page-not-found-Error. Wenn in Java eine Referenz auf "nichts" zeigt, dann zeigt sie auf null. Das bedeutet in diesem Fall "nichts". Dieser Begriff ist dir aber schon begegnet. Zum Beispiel beim Debugging, wenn du eine NullPointerException hattest, weil du eine Variable verwendet hast, der du keinen Wert zugewiesen hast.
Kopie oder Referenz?
Wenn wir mit Variablen arbeiten stellt sich die Frage, womit der Computer eigentlich arbeitet. Arbeitet der Computer mit einer Kopie unseres Wertes oder mit der Referenz? Lass dir mal folgendes durch den Kopf gehen:
Wir erstellen eine Variable varA und weisen ihr den Wert 10 zu. Stell dir das vor wie ein Koffer mit dem Namen varA und dem Inhalt 10. Dann erstellen wir eine Variable varB und weisen ihr den Wert von varA zu:
int varA = 10;
int varB = varA;
Nun könnten folgende zwei Dinge wahr sein:
varBist ein zweiter Koffer mit den NamenvarBund dem Inhalt 10 (= Kopie).varBist ein zweiter Name für den vorhandenen KoffervarA(=Referenz).
Was stimmt? Und wie können wir das herausfinden?
→ Ganz einfach: Indem wir varB verändern. Wenn dann auch varA verändert ist, handelte es sich um einen Koffer (also eine Referenz). Wenn varA unverändert bleibt, handelt es sich um zwei Koffer (also eine Kopie).
Werte und Referenzen
In Java gibt es verschiedene Arten von Datentypen: Wertetypen und Referenztypen.
- Bei den Wertetypen wird in dem obigen Beispiel der Wert verwendet, sprich eine Kopie erstellt.
- Bei den Referenztypen wird in dem obigen Beispiel eine Referenz erstellt.
Welcher Datentyp in welche Kategorie gehört, haben wir bereits im Abschnitt Arten von Datentypen gesehen. Hier haben wir allerdings von primitiven Datentypen und Objekttypen geredet. Die Einteilung ist aber dieselbe. Dabei sind die primitiven Datentypen Wertetypen und die Objekttypen sind Referenztpyen.
Somit gibt es acht Wertetypen:
- byte, short, integer, long, float, double, char, boolean
Alle anderen Datentypen sind Referenztypen (inklusive Arrays).
Call-By-Value & Call-By-Reference
Diese beiden Begriffe sollten dir schonmal begegnet sein. Wir haben sie in Parameter ganz kurz thematisiert. Sie beschreiben, ob bei einem Funktionsaufruf die Parameter als Wert (also eine Kopie) oder Referenz übergeben werden.
- Call-By-Value: Wenn nur der Wert übergeben wird, dann hat das, was innerhalb der Funktion passiert keine Auswirkung auf die "Außenwelt", da ja nur die Kopie verändert wird, nicht das Original. Es gilt das Las-Vegas-Prinzip: What happens inside the function, stays inside the function.
- Call-By-Reference: Wenn die Referenz übergeben wird, die sozusagen auf das Original zeigt, dann hat alles, was innerhalb der Funktion mit der Parameter-Variablen passiert, Auswirkung auf die Außenwelt.
Im Abschnitt zu den Parametern einer Funktion haben wir uns gefragt, was "in" einem Parameter gespeichert ist. Schau dir das Beispiel dort nochmal an. Wir haben eine Funktion geschrieben, die zwei Integer-Variablen übergeben bekommt und diese vertauschen soll. Dabei haben wir festgestellt, dass sie nicht vertauscht wurden. Die Funktion hat nur mit einer Kopie der Werte gearbeitet, aber nicht mit der tatsächlichen Variablen bzw. Referenz.
In diesem Fall ist das auch klar, da es sich bei den Variablen ja um Integer-Werte handelte, sprich um einen Wertetyp.
Was würde passieren, wenn wir dieselbe Funktion umschreiben, so dass sie zwei Strings miteinander vertauscht? Also so:
String wert1 = "1";
String wert2 = "2";
void setup() {
println("wert1: " + wert1 + " wert2: " + wert2);
swap(wert1, wert2);
println("wert1: " + wert1 + " wert2: " + wert2);
}
void swap (String x, String y) {
String temp = x;
x = y;
y = temp;
}
Auch dann werden die Werte nicht vertausch. Warum ist das so? Auch wenn es sich bei einem String um ein Referenztyp handelt, gilt in Java:
Bei Funktionsaufrufen werden die Parameter immer kopiert und an die Funktionen weitergegeben. Java macht also immer Call-By-Value. Java unterscheidet hier nicht zwischen Wertetyp und Referenztyp.
Zusammenfassung
In Java gibt es primitive Datentypen und Objekttypen.
- Die acht primitiven Datentypen sind byte, short, integer, long, float, double, char, boolean
- Sie speichern einfache Werte
- Die numerischen primitiven Datentypen werden unterteilt ist ganzzahlige Typen und Gleitkommatypen
- Die numerischen primitiven Datentypen haben unterschiedliche Größen und Wertebereiche
- Primitive Datentypen werden klein geschrieben
- Objekttypen speichern Objekte
- Objekte bestehen aus Funktionen und Feldern, auf die mittels Punktnotation zugegriffen werden kann.
- Objekttypen werden groß geschrieben und in der Regel mit dem Schlüsselwort new initialisiert.
- Objekttypen haben den Standardwert null.
- Uns bekannte Objekttypen sind: Arrays, String, SoundFile und PVector.
Für Referenz- und Wertetypen gilt:
- Bei Java werden Datentypen unterschieden zwischen Wertetypen und Referenztypen
- Die primitiven Datentypen sind allesamt Wertetypen
- Objekttypen sind Referenztypen
- Variablen sind Referenzen auf Stellen im Speicher, wo Werte gespeichert sind.
- Je nach Typ ist in der Variable entweder der tatsächliche Wert oder die Referenz auf die Stelle im Speicher gespeichert.
- Call-By-Value bedeutet, dass bei einem Funktionsaufruf die Parameter als Wert übergeben werden.
- Veränderungen der Parameter innerhalb der Funktion haben keine Auswirkung auf die übergebenen Variablen außerhalb der Funktion.
- Call-By-Reference bedeutet, dass bei einem Funktionsaufruf die Parameter als Referenz übergeben werden.
- Veränderungen der Parameter innerhalb der Funktion verändern auch die Variablen außerhalb
- Obwohl es in Java Werte- und Referenztypen gibt, verwendet Java immer Call-By-Value.