In der Programmiersprache Java sind zwei Arten von Datentypen definiert: primitive Datentypen und Referenzen auf Objekte.
Eine Referenz repräsentiert einen Verweis auf ein Objekt, welches selbst frei definiert werden kann.
Die insgesamt 8 primitiven Datentypen können in die folgenden 4 Kategorien unterteilt werden:
Jeder dieser 8 primitiven Datentypen verfügt über eine fest definierte Größe, welche auf allen Plattformen gleich ist.
Da Java eine objektorientierte Programmiersprache ist, existiert für jeden primitiven Datentyp eine eigene Wrapper-Klasse.
So besitzt der Datentyp boolean die Wrapper-Klasse java.lang.Boolean und kann dadurch auch wie ein echtes Objekt behandelt werden.
Die Daten von Java-Anwendungen werden im Hauptspeicher in Variablen gespeichert und können während der Laufzeit gelesen und verändert werden. Jede Variable bekommt bei ihrer Deklaration einen der primitiven Datentypen fest zugewiesen. Durch die sinnvolle Wahl der verwendeten Datentypen kann der Quellcode optimiert werden.
Die Variablen besitzen immer einen fest definierten Wert. Entweder ist dies der zugewiesene Standardwert oder der durch die Initialisierung vorgegebene Wert. Bei lokalen Variablen wird durch eine Datenflussanalyse vom Java-Compiler deren Initialisierung sichergestellt.
Die 8 primitiven Datentypen in Java
In der unteren Tabelle sind die 8 primitiven Datentypen und die zugehörige Wrapper-Klasse aufgelistet. Neben dem Datentyp-Namen sind in der Tabelle die Größe des Datentyps, der Initialisierungs-Standardwert und der jeweilige Wertebereich angegeben.
Datentyp | Größe [Bit] | Standardwert | Wertebereich | Wrapper-Klasse |
---|---|---|---|---|
boolean | 1 | false | true, false | java.lang.Boolean |
byte | 8 | 0 | -128 … +127 | java.lang.Byte |
short | 16 | 0 | -215 … 215-1 | java.lang.Short |
int | 32 | 0 | -231 … 231-1 | java.lang.Integer |
long | 64 | 0 | -263 … 263-1 | java.lang.Long |
float | 32 | 0.0 | ±1.2×10−38 … ±3.4×1038 | java.lang.Float |
double | 64 | 0.0 | ±2,2×10−308 … ±1.8×10308 | java.lang.Double |
char | 16 | \u0000 | ‚\u0000‘ … ‚\uFFFF‘ | java.lang.Character |
Die meisten Einträge in der Tabelle sind selbsterklärend. Die beiden Gleitkomma-Datentypen float und double sind etwas komplizierter und sollten von jedem Programmierer gut verstanden werden. Daher ist es sinnvoll sich mit der Norm IEEE 754: Standard for Binary Floating-Point Arithmetic auseinanderzusetzen.
Einen hervorragenden Einstieg in das Thema Gleitkommazahlen ermöglichen der Wikipedia-Eintrag http://de.wikipedia.org/wiki/IEEE_754 und die Seite der IEEE Normungsgruppe 754 mit hilfreichen Beiträgen: http://grouper.ieee.org/groups/754/.
In dem folgenden Beispielprogramm werden die 8 primitiven Java-Datentypen verwendet.
In dem ersten Teil des Quelltextes werden die Variablen deklariert, dabei wird der Datentyp und Variablenname festgelegt. Bei der Deklaration wird in diesem Beispiel auch gleichzeitig die Initialisierung der Variable vorgenommen, indem sie einen Wert zugewiesen bekommt.
Im zweiten Teil des Quelltextes werden die, in den Variablen, gespeicherten Werte auf der Kommandozeile ausgegeben.
Beispielanwendung mit den 8 primitiven Java-Datentypen.
/* * Beispielanwendung mit den 8 primitiven Java-Datentypen. */ public class DatentypenVorstellen{ public static void main(String[] args) { System.out.println("\n1: Deklarieren der Variablen"); boolean myFirstBoolean; myFirstBoolean = true; char myFirstCharacter = '\u0051'; byte myFirstByte = 123; short myFirstShort = -12321; int myFirstInteger = 1_234_567_890; long myFirstLong = -9_223_372_036_854_775_808L; float myFirstFloat = 3.01f; double myFirstDouble = 16_394_000_000_000.987654321; System.out.println("2: Ausgabe der Werte \n"); System.out.println("myFirstBoolean: " + myFirstBoolean); System.out.println("myFirstCharacter: " + myFirstCharacter); System.out.println("myFirstByte: " + myFirstByte); System.out.println("myFirstShort: " + myFirstShort); System.out.println("myFirstInteger: " + myFirstInteger); System.out.println("myFirstLong: " + myFirstLong); System.out.println("myFirstFloat: " + myFirstFloat); System.out.println("myFirstDouble: " + myFirstDouble + "\n"); } }
Die nachfolgende Abbildung stellt die Ausgabe der Java-Anwendung „DatentypenVorstellen“ dar. Es fällt auf, dass die double Variable myFirstDouble nicht den exakten Wert gespeichert hat. Es ist durch Rundung ein Verlust an Präzision (loss of precision) eingetreten.
Die in dem oben angegebenen Quelltext verwendeten primitiven Java-Datentypen wurde bisher nur kurz erklärt. Wir wollen jetzt auf jeden der 8 Datentypen einen genaueren Blick werfen und die jeweiligen Merkmale kennenlernen.
Boolean – Der logische Datentyp in Java
Der logische Datentyp boolean besitzt den Wertebereich (true, false) und muss zwangsweise verwendet werden, wenn ein logischer Operand erforderlich ist. Somit ist es nicht zulässig ganzzahlige Datentypen mit z.B. 0 oder 1 anstelle des logischen Datentyps zu verwenden.
In dem oberen Quelltext haben wir in Zeile 8 die Variable myFirstBoolean deklariert und anschließend in Zeile 9 der Variable myFirstBoolean den Wert true zugewiesen.
Diese beiden Schritte können zu einem Schritt zusammengefasst werden, wie es bei den restlichen Variablen Zeile 10 bis 16 der Fall ist.
Byte, short, int, long – Die integralen Datentypen in Java
Die 4 integralen Datentypen repräsentieren die Ganzen Zahlen in Java. Um ressourcen-optimiert zu programmieren, sollte für jeden Anforderung der optimale integrale Datentyp gewählt werden. Dabei benötigt der byte-Datentyp mit seiner Länge von 1 Byte (8 Bit) den geringsten Speicherplatz, hat dabei aber auch den kleinsten Wertebereich (-128, 127).
Die anderen 3 integralen Datentypen short, int und long besitzen die Länge von jeweils 2, 4 bzw. 8 Byte. Über den größten ganzzahligen Wertebereich verfügt der long-Datentyp (−9.223.372.036.854.775.808, 9.223.372.036.854.775.807).
Jeder der 4 integralen Datentypen ist vorzeichenbehaftet. Bei negative Zahlen besitzt das höchstwertige Bit den Wert 1, somit ist sofort erkennbar ob es sich um eine negative oder positive Zahl handelt.
Die Werte können auf 4 verschiedene Arten eingegeben werden, in jedem der 4 Beispiele enthält die Variable value den Wert 25:
- Oktalform – mit dem Präfix 0
- Dezimalform – ohne Präfix
- Hexadezimalform – mit dem Präfix 0x
- Binärform – mit dem Präfix 0b
Schreibweise integrale Java-Datentypen.
wertOktal = 031; // entspricht 25 in Oktalform wertDezimal = 25; // Dezimalform wertHexadezimal = 0x19; // entspricht 25 in Hexadezimalform wertBinär = 0b11001; // entspricht 25 in Binärform
Hinweis: Die binäre Schreibweise wurde mit Java 7 eingeführt. Weiterhin ist es seit dieser Version möglich Ziffernfolgen mittels Unterstriche besser lesbar darzustellen. Dazu können große Zahlen folgendermaßen angegeben werden: 160_000_000.
Hinweis: Um ein Literal als long festzulegen, muss der Suffix L an die Zahl angehängt werden. Siehe Zeile 14: long myFirstLong = -9_223_372_036_854_775_808L;
. Wird dies nicht angegeben, dann betrachtet der Java-Compiler das Literal automatisch als int-Datentyp.
Float, double – Die Gleitkomma-Datentypen in Java
Im Gegensatz zu den integralen Datentypen müssen Zahlenwerte für die beiden Gleitkomma-Datentypen float und double immer in der Dezimalform angegeben werden. Die beiden Gleitkomma-Datentypen sind nach der Norm IEEE 754 in Java umgesetzt worden. Dabei steht float für einfache Genauigkeit und double für doppelte Genauigkeit.
Eine float-Zahl belegt 4 Byte Speicherplatz, bei double sind es 8 Byte.
Bei einer float-Zahl wird folgende Darstellung verwendet: 1 Vorzeichen-Bit, 8 Bit für den Exponenten und 23 Bit für die Mantisse. Bei einer double-Zahl: 1 Vorzeichen-Bit, 11 Bit für den Exponenten und 52 Bit für die Mantisse.
Im Normalfall besteht eine Gleitkomma-Zahl aus einem Vorkommateil, dem Dezimalpunkt, einem Nachkommateil, einem Exponenten und dem Suffix F oder D. So repräsentiert das Literal L = -.5e-1D
den Zahlenwert L = -0,05 = -0,5 * 10−1 = -0,5/10
.
Auch kürzere Schreibweisen sind zulässig, bspw. L = 0.5
oder L = 5e3
oder L = 1984D
.
An den Beispielen ist erkennen, dass mindestens der Dezimalpunkt, der Exponent oder der Suffix angegeben werden muss, um ein Gleitkomma-Literal von einem integralen Literal unterscheiden zu können.
Hinweis: Um ein Literal als float festzulegen, muss der Suffix F an die Zahl angehängt werden. Siehe Zeile 15: float myFirstFloat = 3.01F;
. Wird dies nicht angegeben, dann betrachtet der Java-Compiler das Literal automatisch als double-Datentyp.
Char – Der Zeichen-Datentyp in Java
Der char-Datentyp in Java speichert Zeichen auf der Grundlage des Unicode-Zeichensatzes. Dies ist ein wichtiges Merkmal der Programmiersprache Java, da es die Portabilität auf möglichst vielen Plattformen sicherstellt.
Jedes Zeichen belegt einen Speicherplatz von 2 Byte. Daraus ergibt sich für den char-Datentyp ein Wertebereich (‚\u0000‘, ‚\uFFFF‘) von 216 Zeichen.
Neben normalen Zeichen können auch sogenannte Escape-Sequenzen angegeben werden. Das char-Literal L = '\u000D'
steht bspw. für den Zeilenumbruch (ehemals Wagenrücklauf, CR) oder L = '\u0003'
für das Textende (End of Text, ETX).
Eine Auflistung der Unicode-Escape-Sequenzen kannst du im Unicodeblock Basis-Lateinisch finden unter folgenden Link: http://de.wikipedia.org/wiki/Unicodeblock_Basis-Lateinisch.
Aber beachte, dass Unicode-Escape-Sequenzen bereits vor der eigentlichen Interpretation des Quelltextes ausgetauscht werden. Außerdem dürfen Unicode-Escape-Sequenzen an jeder beliebiger Stelle im Quelltextes stehen, auch außerhalb von char-Literalen.
Hinweis: Die Eingabe eines char-Literals erfolgt immer mit einfachen Apostroph (Hochkomma), bspw. L = 'Q'
oder L = '\u0051'
. Dadurch unterscheiden sich char-Literale von String-Literalen, die immer mit Anführungszeichen L = "A"
angegeben werden.
Referenzen auf Objekte – Die zweite Datentypart in Java
Zu den Referenztypen zählen neben den Objekten auch Strings und Arrays. Wobei Strings und Arrays einige besondere zusätzliche Merkmale besitzen.
Die Objekte werden in dem Heap-Speicher abgelegt und über eine Speicheradresse referenziert. Wird ein neues Objekt angelegt, dann wird zuerst der Typ der Klasse deklariert und anschließend mit Hilfe des new-Operators ein neues Objekt erzeugt.
Die Speicheradresse dieses Objekts wird dann in der zugehörigen Variable gespeichert. Über diese Speicheradresse kann dann auf das neu erzeugte Objekt zugegriffen werden.
In dem unteren Quelltext wird in Zeile 5 ein Objekt der Klasse Date angelegt. Zuerst wird der Klassentyp deklariert, anschließend wird das Objekt mit Hilfe des new-Operators erzeugt. Die Variable a enthält nur die Referenz auf das angelegte Objekt, dessen Speicheradresse.
In Zeile 6 wird der Variable b die Referenz der Variable a zugewiesen. Die Zuweisung einer Referenz kopiert nur den Verweis auf das referenzierte Objekt, das Objekt selbst wird dabei nicht kopiert. Bei der Zuweisung von zwei Referenztypen (a = b;
) zeigen beide auf das gleiche Objekt. Um Referenztypen zu kopieren, muss die Methode clone ausgeführt werden.
In Zeile 8 bis 10 wird der Inhalt von a und b ausgegeben. Da beide auf das gleiche Objekt referenzieren, ist die Ausgabe identisch.
In Zeile 12 wird a = null;
gesetzt und referenziert jetzt auf kein Objekt mehr. Die Variable a enthält jetzt nur noch die reservierte Adresse null.
In Zeile 14 bis 16 wird erneut der Inhalt von a und b ausgegeben. Diesmal referenziert nur noch b auf das Date-Objekt, der Inhalt von a ist null.
Um Objekte miteinander zu vergleichen gibt es zwei wichtige Vorgehensweisen. Der Gleichheitstest der beiden Referenzen a == b überprüft ob beide auf das gleiche Objekt referenzieren.
Möchte man aber prüfen ob zwei Objekte den gleichen Inhalt besitzen, muss man die equals-Methode ausführen. Diese prüft dann die beiden Objekte auf inhaltliche Gleichheit.
Beispielanwendung mit dem Referenz-Datentyp.
import java.util.Date; public class Referenzen { public static void main(String[] args) { Date a = new Date(453540060000L); // a referenziert das neu erstellte Objekt Date b = a; // b referenziert das gleiche Objekt wie a System.out.println("\n--------- a = b = Objektadresse ---------"); System.out.println("Datum a: " + a); System.out.println("Datum b: " + b); a = null; // a referenziert kein Objekt mehr, enthält die reservierte Adresse null System.out.println("\n------ a = null; b = Objektadresse ------"); System.out.println("Datum a: " + a); System.out.println("Datum b: " + b); Date d1 = new Date(300000000000L); System.out.println("\n\nDatum zu Beginn (in main): " + d1); changeDate(d1); System.out.println("Datum Mitte (wieder in main): " + d1); changeDate2(d1); System.out.println("Datum am Ende (wieder in main): " + d1); } public static void changeDate(Date d1) { System.out.println("Datum zu Beginn (in changeDate): " + d1); d1.setTime(800000000000L); // Merkmale des Objekts werden verändert System.out.println("Datum nach setTime (in changeDate): " + d1); } public static void changeDate2(Date d1) { System.out.println("Datum zu Beginn (in changeDate2): " + d1); d1 = new Date(100000000000L); // neues Objekt wird angelegt System.out.println("Datum neues Objekt (in changeDate2): " + d1); } }
In Java wird bei primitiven Datentypen bei der Anweisung zahlA = zahlB;
der Wert von Variable zahlB kopiert und in Variable zahlA gespeichert. Bei Objekten wird bei der Anweisung objektA = objektB;
nicht das Objekt selbst kopiert und übergeben, sondern lediglich die Objektreferenz, also die Speicheradresse.
Somit wird durch beide Anweisungen objektA.changeContent();
und objektB.changeContent();
ein und dasselbe Objekt verändert. Besonders wichtig wird dieses Verhalten bei Methodenaufrufen.
In Zeile 18 wird ein Date-Objekt angelegt. In Zeile 20 wird der Methode changeDate die Objektreferenz des Date-Objekts als Werteparameter übergeben.
In der Methode changeDate wird in Zeile 28 das Date-Objekt mit der Anweisung d1.setTime(800000000000L);
verändert. Da nur die Objektreferenz des Date-Objekts als Werteparameter übergeben und nicht das Objekt selbst kopiert wurde, ist nun der Inhalt des ursprünglichen Date-Objekts verändert worden.
Auch nach der Rückkehr aus der Methode bleibt somit das ursprüngliche Date-Objekt verändert.
In Zeile 22 wird nun der Methode changeDate2 die Objektreferenz des Date-Objekts als Werteparameter übergeben.
In der Methode changeDate2 wird aber diesmal in Zeile 34 ein neues Date-Objekt mit der Anweisung d1 = new Date(100000000000L);
angelegt. Nun enthält die lokale Variable d1 die Objektreferenz des neuen Date-Objekts anstelle der Objektreferenz des ursprünglichen Date-Objekts.
Es wurden keine Änderungen an dem Inhalt des ursprünglichen Date-Objekts vorgenommen. Dies bleibt auch nach der Rückkehr aus der Methode so.
In der unteren Abbildung ist die Kommandozeilen Ausgabe zu der oberen Beispielanwendung dargestellt.
Strings, Arrays, null – Die drei speziellen Referenztypen
Strings und Arrays sind Referenztypen, die einige besondere zusätzliche Merkmale besitzen. Die reservierte Adresse null ist eine vordefiniferte Konstante, die eine leere Referenz darstellt.
Besondere Merkmale von Strings und Arrays:
- Der Java-Compiler besitzt Vorkenntnisse über die innere Struktur von Strings.
- Arrays werden vom Java-Compiler erzeugt, ohne dass es eine Array-Klassendefinition gibt. Während der Laufzeit werden Arrays wie Objekte behandelt, sie besitzen eine öffentliche Instanzvariable length.
- Bei Strings und Arrays wird der new-Operator nicht benötigt.
- Der Java-Compiler benutzt für String-Operationen Methoden der Klassen String und StringBuffer. Eine ähnliche Verzahnung zwischen Java-Compiler und Laufzeitbibliothek ist auch bei Threads und Exceptions zu finden.
Comments 5
Pingback: Variablen, Datentypen und Typumwandlung (type casting) in Java -
Pingback: Interface als Flag nutzen am Beispiel des Cloneable-Interfaces in Java -
Pingback: Klassen und Objekte (Instanzen von Klassen) in Java
Pingback: Java-Grundlagen: Typumwandlung (type casting) in Java
Pingback: Java-Grundlagen: Arrays in Java – Deklaration, Initialisierung und Zugriff auf Arrays