2.3 Datentypen
 
Java nutzt, wie es für imperative Programmiersprachen typisch ist, Variablen zum Ablegen von Daten. Eine Variable ist ein reservierter Speicherbereich und belegt eine feste Anzahl von Byte. Alle Variablen (und auch Ausdrücke) haben einen Typ, der zur Übersetzungszeit bekannt ist. Der Typ wird auch Datentyp genannt, da eine Variable einen Datenwert, auch Datum genannt, hält. Für jeden Typ lässt sich die Speichergröße berechnen. Beispiele für einfache Datentypen sind: Ganzzahlen, Fließkommazahlen, Wahrheitswerte, Zeichen. Zu den einfachen Datentypen gesellen sich die komplexen Datentypen Array und Objekte. Da jede Variable einen festen Datentyp hat und diesen nicht mehr ändern kann, zählt Java zu den streng typisierten Sprachen.1
Der Datentyp erlaubt dem Übersetzer auch, die Daten im Speicher nach bestimmten Regeln zu behandeln. Wenn wir einen Speicherauszug lesen und dort die Bitinformationen 01011010, 11010010, 01010011, 10100010 finden, ist uns auch nicht klar, ob dies nun vier Buchstaben sind, eine Fließkommazahl oder eine Ganzzahl. Der Typ bestimmt also auch die zulässigen Operationen.
Beispiel Ein Bit ist ein Informationsträger für die Aussage wahr oder falsch. Mehrere Bit werden zusammengesetzt zu Folgen wie einem Byte, das aus acht Bit besteht. Da jedes Bit anders belegt sein kann, bildet es in der Summe unterschiedliche Werte. Werden acht Bit zugrunde gelegt, so lassen sich durch unterschiedliche Belegungen 256 unterschiedliche Zahlen bilden. Ist kein Bit des Byte gesetzt, so ist die Zahl 0. Jede Stelle im Byte bekommt dabei eine Wertigkeit zugeordnet. Die Wertebelegung für die Zahl 19 berechnet sich aus 16 + 2 + 1, da sie zusammengesetzt aus einer Anzahl von Summanden der Form 2n
ist.
|
Bit
|
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
Wertigkeit
|
27=128
|
26=64
|
25=32
|
24=16
|
23=8
|
22=4
|
21=2
|
20=1
|
Belegung für 19
|
0
|
0
|
0
|
1
|
0
|
0
|
1
|
1
|
Bitbelegung für die Zahl 19
|
Primitiv- oder Verweis-Typ
Die Datentypen in Java zerfallen in zwei Kategorien: primitive Typen und Referenztypen (auch Klassentypen). Die einfachen Typen sind die eingebauten Datentypen, die nicht als Objekte verwaltet werden. Referenztypen sind genau das, was noch übrig bleibt. Es gibt aber auch Sprachen, die keine primitiven Datentypen besitzen. Als Beispiel sei noch einmal auf das in der Fußnote angesprochene Smalltalk verwiesen.
Warum Sun sich für diese Teilung entschieden hat, lässt sich mit zwei Gründen erklären:
|
Viele Programmierer kennen Syntax und Semantik von C(++) und anderen imperativen Programmiersprachen. Auf die neue Sprache Java zu wechseln, fällt leicht, und das objektorientierte Denken aus C++ hilft, sich auf der Insel zurechtzufinden. |
|
Der andere Grund ist, dass häufig vorkommende elementare Rechenoperationen schnell durchgeführt werden müssen und bei einem einfachen Typ leicht Optimierungen durchzuführen sind. |
Wir werden uns im Folgenden erst mit primitiven Datentypen beschäftigen. Referenzen werden nur dann eingesetzt, wenn Objekte ins Spiel kommen. Dies dauert jedoch noch etwas.
2.3.1 Primitive Datentypen
 
In Java gibt es eingebaute Datentypen für ganze Zahlen, Gleitkommazahlen nach IEEE 754, Zeichen und Wahrheitswerte. Strings werden bevorzugt behandelt, sind aber lediglich Verweise auf Objekte. Die folgende Tabelle gibt darüber einen Überblick. Anschließend betrachten wir jeden Datentyp präziser.
Tabelle 2.4
Java-Datentypen und deren Wertebereiche
Schlüsselwort/Typ
|
Länge in Byte
|
Belegung (Wertebereich)
|
boolean
|
1
|
true oder false
|
char
|
2
|
16-Bit Unicode Zeichen (0x0000...0xffff)
|
byte
|
1
|
–2^7 bis 2^7 – 1 (–128...127)
|
short
|
2
|
–2^15 bis 2^15 – 1 (–32768...32767)
|
int
|
4
|
–2^31 bis 2^31 – 1 (–2147483648...2147483647)
|
long
|
8
|
–2^63 bis 2^63 – 1
(–9223372036854775808...9223372036854775807)
|
float
|
4
|
1,40239846E-45f…3,40282347E+38f
|
double
|
8
|
4,94065645841246544E-324...
1,79769131486231570E+308
|
Für float und double ist das Vorzeichen nicht angegeben, da die kleinsten und größten darstellbaren Zahlen sowohl positiv wie auch negativ sein können. Mit anderen Worten: Die Wertebereiche unterscheiden sicht nicht in Abhängigkeit vom Vorzeichen.
 Hier klicken, um das Bild zu Vergrößern
Zwei wesentliche Punkte zeichnen die primitiven Datentypen aus:
|
Alle Datentypen haben eine festgesetzte Länge, die sich unter keinen Umständen ändert. Der Nachteil, dass sich bei einigen Hochsprachen die Länge eines Datentyps ändern kann, besteht in Java nicht. In den Sprachen C(++) bleibt dies immer unsicher, und die Umstellung auf 64-Bit-Maschinen bringt viele Probleme mit sich. Bei der Betrachtung der Auflistung fällt auf, dass char 16 Bit lang ist. |
|
Die numerischen Datentypen byte, short, int und long sind vorzeichenbehaftet, Fließkommazahlen sowieso. Dies ist leider nicht immer praktisch, aber wir müssen stets daran denken. Probleme gibt es, wenn wir einem Byte zum Beispiel den Wert 240 zuweisen wollen, denn der Wertebereich ist –128 bis 127. Ein char ist im Prinzip ein vorzeichenloser Ganzzahltyp. |
2.3.2 Wahrheitswerte
 
Der Datentyp boolean beschreibt einen Wahrheitswert, der entweder true oder false ist. Die Zeichenketten true und false sind reservierte Wörter und bilden so genannte Literale. Kein anderer Wert ist für Wahrheitswerte möglich, insbesondere werden numerische Werte nicht als Wahrheitsweite interpretiert.
Der Boolesche Typ wird beispielsweise bei Bedingungen, Verzweigungen oder Schleifen benötigt.
2.3.3 Variablendeklarationen
 
Mit Variablen lassen sich Daten speichern, die vom Programm gelesen und geschrieben werden können. Um Variablen zu nutzen, müssen sie deklariert werden. Wir sprechen hier auch von der Definition2
einer Variablen. Die Schreibweise einer Variablendeklaration ist immer die gleiche: Hinter dem Typnamen folgt der Name der Variablen.
Typname Variablenname;
Der Typname ist entweder ein einfacher Typ (wie int) oder ein Referenztyp. Viel schwieriger ist eine Definition nicht – kryptische Definitionen wie in C gibt es in Java nicht.3
Ein Variablenname (der dann Bezeichner ist) kann alle Buchstaben und Ziffern des Unicode-Zeichensatzes beinhalten, mit der Ausnahme, dass am Anfang einer Zeichenkette keine Ziffer stehen darf. Auch darf der Variablenname mit keinem reservierten Schlüsselwort identisch sein.
Hinweis Zwei Variablen ähnlicher Schreibweise, etwa counter und counters, führen schnell zu Verwirrung. Auch 0 und O und 1 und l sind leicht zu verwechseln. Als Programmierer sollten wir uns konsistent an ein Namensschema halten. Auch sollten wir korrekt schreiben und auf Rechtschreibfehler achten, leicht wird aus necessaryConnection dann nesesarryConnection.
|
Extra verwirrende Bezeichner sind zu vermeiden. Gültig – aber böse – ist etwa:
int ínt, ìnt, înt;
boolean bôõleañ;
Die Kombination »rn« ist auch schwer zu lesen und je nach Zeichensatz leicht mit »m« zu verwechseln. Abstrakte Bezeichner sind zu vermeiden. Eine gemeine Idee ist auch folgende:
boolean FALSE = true;
boolean TRUE = false;
Im Programmcode wird dann mit FALSE und TRUE gearbeitet. Einer der obersten Plätze bei den verpfuschtesten Java-Programmen ist uns gewiss …
|
Da in Java alle Variablen einen Typ besitzen, heißt die Variablendeklaration auch Typdefinition. Sie ist eine Anweisung und wird daher mit einem Semikolon abgeschlossen.4
Beispiel Unicode-Sequenzen können vom Programmierer überall im Programm aufgenommen werden. Folgende Deklarationen mit den Bezeichnernamen sind daher gleich:
double übelkübel;
double \u00FCbelk\u00FCbel;
|

Ist ein Bezeichnername unglücklich gewählt, so lässt er sich problemlos konsistent umbenennen. Dazu ist im Menü Refactor, Rename – oder auch kurz (Alt)+(ª)+(R) – auszuwählen; der Cursor muss auf dem Bezeichner stehen. Eine optionale Vorschau (engl. preview) zeigt an, welche Änderungen die Umbenennung nach sich ziehen wird.
 Hier klicken, um das Bild zu Vergrößern
Abbildung 2.2
Umbenennung von Variablen
Mehrere Variablen auf einen Schlag definieren
Werden mehrere Variablen gleichen Typs bestimmt, so können diese mit einem Komma getrennt werden. Eine Deklaration kann in jeden Block geschrieben werden:
Typname Variablenname1[, Variablenname2, ... ]
Schreiben wir ein einfaches Programm, welches eine Wahrheitsvariable definiert und zuweist. Die Variablenbelegung erscheint zusätzlich auf dem Bildschirm.
Listing 2.3
FirstVariable.java
class FirstVariable
{
public static void main( String[] args )
{
boolean hatBesucher;
hatBesucher = true;
System.out.print( "Sind Personen in der Disco? " );
System.out.println( hatBesucher );
}
}
Die Zeile hatBesucher = true ist eine Zuweisung – und somit ein Ausdruck, da sie einen Wert liefert –, die die Variable hatBesucher mit einem Wert initialisiert. Auch sie ist eine Anweisung und wird daher mit einem Semikolon abgeschlossen. Steht auf der rechten Seite keine Variable, so steht dort ein Literal, eine Konstante, wie in unserem Fall true. Wir haben schon erwähnt, dass es für Wahrheitswerte nur die Literale true und false gibt.
2.3.4 Ganzzahlige Datentypen
 
Java stellt vier ganzzahlige Datentypen zur Verfügung: byte, short, int und long. Sie unterscheiden sich nur in der Länge, die jeweils 1, 2, 4 und 8 Byte umfasst. Die definierte Länge ist eine wesentliche Eigenschaft von Java. Ganzzahlige Typen (mit der Ausnahme von char) sind in Java immer vorzeichenbehaftet; einen Modifizierer unsigned wie in C(++) gibt es nicht.5
Beispiel Variablendeklaration mit Wertinitialisierung
int quadratmeter;
int i = 1243, j = 01230, k = 0xcafebabe;
|
Den Variablen kann gleich bei der Definition ein Wert zugewiesen werden. Hinter einem Gleichheitszeichen wird der Wert geschrieben, der oft ein Literal ist. Eine Zuweisung gilt nur für immer genau eine Variable. Negative Zahlen werden durch Voranstellen eines Minuszeichens gebildet. Ein Pluszeichen für positive Zeichen ist möglich.
Das hexadezimale und oktale Zahlensystem
Die Literale für Ganzzahlen lassen sich in drei unterschiedlichen Zahlensystemen angeben. Das natürlichste ist das Dezimalsystem, wie das Beispiel an der Variablen i zeigt. Die Literale bestehen aus den Ziffern »0« bis »9«. Zusätzlich existiert die Oktal- und Hexadezimalform, die die Zahlen zur Basis 8 und 16 schreiben.
|
Ein oktaler Wert beginnt mit dem Präfix »0«. Mit der Basis 8 werden nur die Ziffern »0« bis »7« für oktale Werte benötigt. Der Name stammt aus dem lateinischen »octa«, was auf Deutsch »acht« heißt. Das Oktalsystem war früher eine verbreitete Darstellung, da nicht mehr einzelne Bit solo betrachtet werden mussten, sondern 3 Bit zu einer Gruppe zusammengefasst wurden. In der Kommunikationselektronik ist das Oktalsystem noch weiterhin beliebt. |
|
Ein hexadezimaler Wert beginnt mit »0x«. Da zehn Ziffern für 16 hexadezimale Zahlen nicht ausreichen, besteht eine Zahl zur Basis 16 zusätzlich aus den Buchstaben »a« bis »f« (beziehungsweise »A« bis »F«). Das Hexadezimalsystem heißt auch Sedezimalsystem. |
Achtung Wer sich im kalifornischen Cupertino (unter anderem Apple-Hauptsitz) aufhält und dieses Buch liest, sollte es vermeiden, gut hörbar das Hexadezimalsystem rückwärts aufzuzählen. Das ist gesetzlich verboten!
|
Für Dualzahlen (also Binärzahlen zur Basis 2) gibt es keine Notation.
Achtung In Java-Programmen sollten Oktalzahlen mit Bedacht eingesetzt werden. Wer aus optischen Gründen mit der 0 eine Zahl linksbündig auffüllt, erlebt eine Überraschung.
<span class="listing">int i = 118;
int j = 012; // Oktal 012 ist dezimal 10</span>
|
Der Datentyp long
Ein Literal für Ganzzahlen doppelter Größe wird mit einem »l« oder »L« am Ende versehen.
Beispiel Deklaration eines long mit angehängtem »L«
long l = 123456789098L, m = –1L, n = 0xC0B0L;
|
Betrachten wir folgende Zeile, so ist auf den ersten Blick kein Fehler zu erkennen:
System.out.println( 123456789012345 );
Der Übersetzungsvorgang fördert jedoch noch einmal zu Tage, dass alle Datentypen ohne explizite Größenangabe als int angenommen werden, das heißt, 32 Bit lang sind. Obige Zeile führt daher zu einem Compilerfehler, da die Zahl nicht im Wertebereich von –2147483648 bis 2147483647 liegt. Java reserviert also nicht so viele Bit wie benötigt und wählt nicht automatisch den passenden Wertebereich. Er muss ausdrücklich angegeben werden. Um die Zahl 123456789012345 gültig ausgeben zu lassen, müssen wir schreiben:
System.out.println( 123456789012345l );
Ersichtlich wird, dass ein kleines »l« sehr viel Ähnlichkeit mit der Ziffer Eins besitzt. Daher sollte bei Längenangaben immer ein großes »L« hinten angestellt werden.
System.out.println( 123456789012345L );
Allerdings ist das Compilerverhalten verwirrend, denn bei folgender Anweisung findet er auch automatisch die richtige Größe:
byte b = 12;
Überläufe bei Ganzzahlen
Passt das Ergebnis einer Berechnung nicht in den Wertebereich einer Zahl, so wird dieser Fehler nicht vom System angezeigt; weder der Compiler noch die Laufzeitumgebung melden dieses Problem.
Mathematisch gilt a * a / a = a, also etwa zum Beispiel 100 000 * 100 000 / 100 000 = 100 000. In Java ist das anders, da wir bei 100 000 * 100 000 einen Überlauf im int haben, und
System.out.println( 100000 * 100000 / 100000 ); // 14100
liefert 14100. Wenn wir den Datentyp auf long erhöhen, indem wir hinter ein 100 000 ein L setzen, sind wir bei dieser Multiplikation noch sicher, da ein long das Ergebnis aufnehmen kann.
System.out.println( 100000L * 100000 / 100000 ); // 100000
2.3.5 Die Fließkommazahlen
 
Java unterscheidet Fließkommazahlen einfacher Genauigkeit (float) und doppelter Genauigkeit (double). Die Datentypen sind im IEEE-754-Standard beschrieben und haben eine Länge von 4 Byte für float und 8 Byte für double.
Zur Darstellung der Fließkomma-Literale gibt es zwei Notationen: Standard und Wissenschaftlich. Egal ob Standard oder Wissenschaftlich, die Literale bestehen aus einem Vorkommateil, einem Dezimalpunkt (kein Komma) und einem Nachkommateil. Eine Fließkomma-Literal muss keine Nachkommastellen besitzen, so dass auch gültig ist:
double d = 10. + 20.;
Die Größe der Datentypen lässt sich über die Konstanten MAX_VALUE und MIN_VALUE bei den Klassen Double und Float erfragen.
Wissenschaftliche Notation bei Fließkommazahlen
Die wissenschaftliche Notation ist eine Erweiterung der Standard-Notation. Es folgt hinter den Nachkommastellen ein »E« (oder »e«) mit einem Exponenten zur Basis 10. Der Exponent kann entweder positiv oder negativ6
muss aber eine Ganzzahl sein. Die Tabelle stellt drei Beispiele zusammen:
Standard
|
Wissenschaftlich
|
123450.0
|
1.2345E5
|
123450.0
|
1.2345E+5
|
0.000012345
|
1.2345E-5
|
Der Datentyp float
Standardmäßig sind die Literale vom Typ double. Ein nachgestelltes »f« (oder »F«) zeigt an, dass es sich um ein float handelt. Vorkommateil und Exponent dürfen durch die Vorzeichen »+« oder »–« eingeleitet werden.
Beispiel Gültige Zuweisungen für Fließkommazahlen vom Typ double und float:
double pi = 3.1415, klein = .001, x = 3.00e+8;
float y = 3.00E+8F;
|
Mantisse und Exponent
Intern bestehen Fließkommazahlen aus drei Teilen: Einem Vorzeichen, einem Exponenten und einer Mantisse. Während die Mantisse die Genauigkeit bestimmt, gibt der Exponent die Größenordnung der Zahl an. Das Vorzeichen kostet immer ein Bit, und die Anzahl der Bit für Exponent und Mantisse richtet sich nach dem Datentyp. Bei float benötigt der Exponent 8 Bit, die Mantisse 23 und beim Datentyp double 11 Bit und 52 Bit.
Noch genauere Auflösung bei Fließkommazahlen
Einen höher auflösenden beziehungsweise präziseren Datentyp für Gleitkommazahlen als double gibt es nicht. Sun bietet für diese Aufgabe im Paket java.math die Klasse BigDecimal an. Das ist sinnvoll für Daten, die eine sehr gute Genauigkeit aufweisen sollen, wie zum Beispiel Währungen.
Einige Programmiersprachen besitzen für Währungen eingebaute Datentypen, wie LotusScript mit Currency, was mit acht Byte einen Wertebereich 922 337 203 685 477,5807 bis 922 337 203 685 477,5807 abdeckt. Erstaunlicherweise gab es einmal in C# den Datentyp currency für ganzzahlige Währungen. MS ist Vorreiter der Initiative: »Weg mit den Cents« – doch dieser »Weg« wurde eingestampft. In C# gibt es den Datentyp decimal, der mit 14 Byte auch genügend Präzision bietet, um eine Zahl wie 0.0000000000000000000000000001 auszudrücken.
2.3.6 Alphanumerische Zeichen
 
Der alphanumerische Datentyp char (von engl. character, Zeichen) ist 2 Byte groß und nimmt ein Unicode-Zeichen auf. Ein char ist nicht vorzeichenbehaftet. Die Literale für Zeichen werden in einfache Hochkommata gesetzt. Spracheinsteiger verwechseln häufig die einfachen Hochkommata mit den Anführungszeichen der Zeichenketten (Strings). Die einfache Merkregel: Ein Zeichen – ein Hochkomma, mehrere Zeichen – zwei Hochkommata (Gänsefüßchen).
Beispiel Korrekte Hochkommata für Zeichen und Zeichenketten:
char c = 'a';
String s = "Heut' schon gebeckert?";
|
Escape-Sequenzen/Fluchtsymbole
Für spezielle Zeichen stehen Escape-Sequenzen7
zur Verfügung, die so nicht direkt als Zeichen dargestellt werden können.
Tabelle 2.5
Escape-Sequenzen
Zeichen
|
Bedeutung
|
\b
|
Rückschritt (Backspace)
|
\n
|
Zeilenschaltung (Newline)
|
\f
|
Seitenumbruch (Formfeed)
|
\r
|
Wagenrücklauf (Carriage return)
|
\t
|
Horizontaler Tabulator
|
\"
|
Doppeltes Anführungszeichen
|
\'
|
Einfaches Anführungszeichen
|
\\
|
Backslash
|
Beispiel Zeichenvariablen mit Initialwerten und Sonderzeichen:
char a = 'a',
singlequote = '\'',
newline = '\n',
Die Fluchtsymbole sind für Zeichenketten die gleichen. Auch dort können bestimmte Zeichen mit Escape-Sequenzen dargestellt werden.
|
Beispiel String s = "Er fragte: \"Wer lispelt wie Katja Burkard?\"";
|
2.3.7 Die Typanpassung (das Casting)
 
Möglicherweise kommt es vor, dass Datentypen konvertiert werden müssen. Dies nennt sich Typanpassung (engl. typecast) oder auch casten. Java unterscheidet zwei Arten der Typanpassung:
|
Automatische (implizite) Typanpassung. Daten eines kleineren Datentyps werden automatisch (implizit) dem größeren angepasst. Der Compiler nimmt diese Anpassung selbstständig vor. |
|
Explizite Typanpassung. Ein größerer Typ kann einem kleineren Typ mit möglichem Verlust von Informationen zugewiesen werden. |
Automatische Anpassung der Größe
Werte der Datentypen byte und short werden bei Rechenoperationen automatisch in den Datentyp int umgewandelt. Ist ein Operand vom Datentyp long, dann werden alle Operanden auf long erweitert. Wird aber short oder byte als Ergebnis verlangt, dann ist dieses durch einen expliziten Typecast anzugeben, und nur die niederwertigen Bit des Ergebniswerts werden übergeben. Folgende Typumwandlungen sind ohne Informationsverlust möglich:
Tabelle 2.6
Zuweisungen ohne Informationsverlust
Von Typ
|
In Typ
|
byte
|
short, char, int, long, float, double
|
short
|
int, long, float, double
|
char
|
int, long, float, double
|
int
|
long, float, double
|
long
|
float, double
|
float
|
double
|
Die Anpassung wird im Englischen auch widening conversion genannt, weil sie den Wertebereich automatisch erweitert.
Explizite Typanpassung
Die explizite Anpassung engt einen Typ ein, sodass diese Operation auch narrowing conversion genannt wird. Der gewünschte Typ für eine Typanpassung wird vor den umzuwandelnden Datentyp geschrieben. Der gewollte Datentyp ist geklammert.
Beispiel Umwandlung einer Fließkommazahl in eine Ganzzahl:
int n = (int) 3.1415;
|

Passt der Typ eines Ausdrucks nicht, lässt er sich mit (Strg)+(1) korrigieren.
 Hier klicken, um das Bild zu Vergrößern
Hinweis Natürlich kann die Konvertierung von double in long nicht verlustfrei sein. Wie sollte das auch gehen? Zwar verfügt sowohl ein long als auch ein double über 64 Bit zur Datenspeicherung, aber ein double kann eine Ganzzahl nicht so effizient speichern wie ein long und hat etwas »Overhead«. Daher können bei der impliziten Konvertierung eines long in ein double einige Bit als Informationsträger herausfallen, wie es das folgende Beispiel illustriert.
long z = 1111111111111111111L; // 1111111111111111111
double d = z; // 1111111111111111170 (1.11111111111111117E18)
long m = (long) d; // 1111111111111111168
|
Eine Typumwandlung hat eine sehr hohe Priorität. Daher muss der Ausdruck gegebenenfalls geklammert werden.
Beispiel Die Zuweisung an n verfehlt das Ziel.
int n = (int) 1.0315 + 2.1;
int m = (int)(1.0315 + 2.1); // das ist korrekt
|
Typumwandlung von Fließkommazahlen zu Ganzzahlen
Bei der expliziten Typumwandlung von double und float in einen Ganzzahltyp kann es selbstverständlich zum Verlust von Genauigkeit kommen sowie zur Einschränkung des Wertebereichs. Bei der Konvertierung von Fließkommazahlen verwendet Java eine Rundung gegen null.
Beispiel Zahlen, die bei der Konvertierung die Rundung nach null aufzeigen
System.out.println( (int) +12.34 ); // 12
System.out.println( (int) +67.89 ); // 67
System.out.println( (int) –12.34 ); // –12
System.out.println( (int) –67.89 ); // –67
|
Bei der Konvertierung eines größeren Ganzzahltyps in einen kleineren werden einfach die oberen Bit abgeschnitten. Eine Anpassung des Vorzeichens findet nicht statt. Die Darstellung in Bit zeigt das sehr anschaulich:
int ii = 123456789; // 00000111010110111100110100010101
int ij = –123456; // 11111111111111100001110111000000
short si = (short) ii; // 1100110100010101
short sj = (short) ij; // 0001110111000000
System.out.println( si ); // –13035
System.out.println( sj ); // 7616
sj wird eine negative Zahl, da das 16. Bit beim int ii gesetzt war und nun beim short das negative Vorzeichen anzeigt. Die native Zahl ij hat kein 16. Bit gesetzt, und so wird das short sj positiv.
short und char
Ein short hat wie ein char eine Länge von 16 Bit. Doch diese Umwandlung ist nicht ohne ausdrückliche Konvertierung möglich. Das liegt am Vorzeichen von short. Zeichen sind per Definition immer ohne Vorzeichen. Würde ein char mit einem gesetzten höchstwertigen letzten Bit in ein short konvertiert, käme eine negative Zahl heraus. Ebenso wäre, wenn ein short eine negative Zahl bezeichnet, das oberste Bit im char gesetzt, was unerwünscht ist. Die ausdrückliche Umwandlung erzeugt immer nur positive Zahlen.
Der Verlust bei der Typumwandlung von char nach short tritt etwa bei der Han-Zeichenkodierung für chinesische, japanische oder koreanische Zeichen auf. Denn dort ist im Unicode das erste Bit gesetzt, welches bei der Umwandlung in ein short dem nicht gesetzten Vorzeichen-Bit weichen muss.
Typanpassungen von int und char
Die Methode printXXX() reagiert auf die Typen char und int, und eine Typumwandlung führt zur gewünschten Ausgabe.
int c1 = 65;
char c2 = 'A';
System.out.println( c1 ); // 65
System.out.println( (int)c2 ); // 65
System.out.println( (char)c1 ); // A
System.out.println( c2 ); // A
System.out.println( (char)(c1 + 1) ); // B
System.out.println( c2 + 1 ); // 66
Einen Ganzzahlwert in einem int können wir als Zeichen ausgeben, genauso wie eine char-Variable als Zahlenwert. Wir sollten beachten, dass eine mathematische Operation auf char-Typen zu einem int führt. Daher funktioniert für ein char c Folgendes nicht:
c = c + 1;
Richtig wäre c = (char)(c+1).
Probleme bei Zuweisungen
Leider ist die Typanpassung nicht ganz so einleuchtend, wie folgendes Beispiel zeigt:
Listing 2.4
AutoConvert.java
public class AutoConvert
{
public static void main( String[] args )
{
int i1 = 1, i2 = 2, i3;
long l1 = 1, l2 = 2, l3;
short s1 = 1, s2 = 2, s3;
byte b1 = 1, b2 = 2, b3;
i3 = i1 + i2; // das ist noch OK
l3 = l1 + l2;
// s3 = s1 + s2; // Compilerfehler!
// b3 = b1 + b2;
s3 = (short) ( s1 + s2 ); // das ist wieder OK
b3 = (byte) ( b1 + b2 );
}
}
Dies ist auf den ersten Blick paradox. Es ist nicht möglich, ohne explizite Typumwandlung zwei short- oder byte-Zahlen zu addieren. Das Verhalten des Übersetzers lässt sich mit der automatischen Anpassung erklären. Wenn Ganzzahl-Ausdrücke vom Typ kleiner int mit einem Operator verbunden werden, passt der Compiler eigenmächtig den Typ auf int an. Die Addition der beiden Zahlen arbeitet also nicht mit short- oder byte-Werten, sondern mit int-Werten. So werden auch Überläufe korrekt behandelt.
Bei der Zuweisung wird dies zum Problem. Denn dann steht auf der rechten Seite ein int und auf der linken Seite der kleinere Typ byte oder short. Nun muss der Compiler meckern, da Zahlen abgeschnitten werden könnten. Mit der ausdrücklichen Typumwandlung erzwingen wir diese Konvertierung und akzeptieren ein paar fehlende Bit. Diese Eigenart ist insofern verwunderlich, als auch ein int nur dann zu einem long erweitert wird, wenn einer der Operanden eines Ausdrucks vom Typ long ist.8
Tipp »Kleine« Typen wie short und byte führen oft zu Problemen. Wenn sie nicht gerade in großen Feldern verwendet werden und Speicherplatz nicht ein absolutes Kriterim ist, ist int die beste Wahl – auch weil Java nicht durch besondes intuitve Typ-Konvertierungen glänzt, wie das Beispiel mit dem unären Minus und Plus zeigt:
byte b = 0;
b = -b; // "Cannot convert from int to byte"
b = +b; // "Cannot convert from int to byte"
Richtig: Ein Fehler! Der Grund: Der Ausdruck auf der rechten Seite wird durch den unären Operator in ein int umgewandelt; was immer für die Typen byte, short und char gilt.17
|
Materialverlust durch Überläufe
Überläufe bei Berechnungen können zu schwer wiegenden Fehlern führen, so wie beim Absturz der Ariane 5 am 4. Juni 1996 genau 36.7 Sekunden nach dem Start. Die europäische Raumfahrtbehörde European Space Agency (ESA) startete von Französisch-Guyana aus eine unbemannte Rakete mit vier Satelliten an Bord, die 40 Sekunden nach dem Start explodierte. Glücklicherweise kamen keine Menschen ums Leben, doch der materielle Schaden belief sich auf etwa 500 Millionen US-Dollar. In dem Projekt steckten zusätzlich Entwicklungskosten von etwa 7 Milliarden US-Dollar. Grund für den Absturz war ein Rundungsfehler, der durch die Umwandlung einer 64-Bit-Fließkommazahl (die horizontale Geschwindigkeit) in eine vorzeichenbehaftete 16-Bit-Ganzzahl auftrat. Die Zahl war leider größer als 215 und die Umwandlung nicht gesichert, da die Programmierer diesen Zahlenbereich nicht angenommen hatten. Als Konsequenz brach das Lenksystem zusammen und die Selbstzerstörung wurde ausgelöst, da die Triebwerke abzubrechen drohten. Das wirklich Dumme an dieser Geschichte ist, dass die Software nicht unbedingt für den Flug notwendig war und nur den Startvorbereitungen diente. Im Fall einer Unterbrechung während des Countdowns hätte dann das Programm schnell abgebrochen werden können. Ungünstig war, dass der Programmteil unverändert durch Wiederverwendung per Copy-and-Paste aus der Ariane-4-Software kopiert worden war, die Ariane 5 aber schneller flog.
 2.3.8 Lokale Variablen, Blöcke und Sichtbarkeit
 
In jedem Block und auch in jeder Klasse9
können Variablen deklariert werden. Globale Variablen, die für alle Funktionen und Klassen sichtbar sind, gibt es in Java nicht. (Eine globale Variable müsste in einer Klasse definiert werden, die dann alle Klassen übernehmen.)
Sichtbarkeit
Jede Variable hat einen Geltungsbereich (engl. scope), auch Gültigkeitsbereich beziehungsweise Lebensdauer genannt. Sie ist nur in dem Block lebendig, in dem sie definiert wurde. In dem Block ist die Variable lokal.
Beispiel Da ein Block immer mit geschweiften Klammern angegeben wird, erzeugen wir durch folgende Funktionen Blöcke, die einen weiteren inneren Block besitzen. Somit sind Blöcke ineinander geschachtelt.
void foo()
{
int i;
{
int j; // j gilt nur in dem Block
j = 1;
}
// j = 2; // Funktioniert auskommentiert nicht
}
|
void bar()
{
int i, k; // i hat mit oberem i nichts zu tun
{
// int k; // Das würde nicht gehen!
}
}
|
Zu jeder Zeit können Blöcke definiert werden. Außerhalb des Blocks sind deklarierte Variablen nicht sichtbar. Nach Abschluss des inneren Blocks, der j deklariert, ist ein Zugriff auf j nicht mehr möglich; auf i ist der Zugriff weiterhin erlaubt.
Innerhalb eines Blocks können Variablennamen nicht genauso gewählt werden wie Namen lokaler Variablen eines äußeren Blocks oder wie die Namen für die Parameter einer Funktion. Das zeigt zum Beispiel die Definition der Variablen k. Obwohl andere Programmiersprachen das erlauben, haben sich die Java-Sprachentwickler dagegen entschieden, um Fehlerquellen zu vermeiden.

Soll eine Variable in ihrem lokalen Kontext umbenannt werden, so gibt es neben dem Rename auch eine andere Möglichkeit. Dazu lässt sich auf der Variablen mit (Strg)+(1) ein Popup-Fenster mit Local Rename öffnen. Der Bezeichner wird selektiert und lässt sich ändern. Gleichzeitig ändern sich alle Bezüge auf die Variable mit.
 Hier klicken, um das Bild zu Vergrößern
 Hier klicken, um das Bild zu Vergrößern
2.3.9 Initialisierung von lokalen Variablen
 
Während Objektvariablen (und statische Variablen sowie Felder) automatisch mit einem Nullwert initialisiert werden, geschieht dies bei lokalen Variablen nicht; wir müssen uns selbst um die Initialisierung kümmern.
Beispiel Häufig passieren Fehler bei falsch angewendeten bedingten Anweisungen, wie das folgende Programmsegment demonstriert:
public static void main( String[] args )
{
int nene, williWurm;
System.out.println( nene ); // Compilerfehler
nene = 0;
|
if ( nene == 1 )
williWurm = 2;
System.out.println( williWurm ); // Compilerfehler
}
Die beiden lokalen Variablen nene und williWurm werden nicht automatisch mit null initialisiert, und so kommt es bei der versuchten Ausgabe von nene zu einem Compilerfehler, da ein Lesezugriff nötig ist, aber vorher noch kein Schreibzugriff stattfand. Erst die nächste Zeile mit nene = 0 ist in Ordnung.
Weil Zuweisungen in bedingten Anweisungen nicht immer ausgeführt werden, meldet der Compiler auch einen Fehler, wenn er sich vorstellen kann, dass es einen Programmfluss ohne die Zuweisung gibt. Da williWurm nur nach der if-Abfrage auf den Wert 2 gesetzt wird, wäre nur unter der Bedingung nene gleich 1 ein Schreibzugriff auf williWurm gemacht und ein folgender Lesezugriff möglich. Doch da der Compiler annimmt, dass es andere Fälle gebe kann, wäre ein Zugriff auf eine nicht initialisert Variable ein Fehler.
|

Ein Hinweis und Verbesserungsvorschlag, wenn eine lokale Variable nicht initialisiert ist.
 Hier klicken, um das Bild zu Vergrößern
1 Im Gegensatz dazu steht Smalltalk. In Smalltalk sind zuerst einmal alles Objekte, und diese haben keinen Typ. Die Operationen werden erst zur Laufzeit an die Objekte gebunden.
2 In C(++) bedeuten Definition und Deklaration etwas Verschiedenes. In Java kennen wir diesen Unterschied nicht und betrachten daher beide Begriffe als gleichwertig.
3 Das ist natürlich eine Anspielung auf C, in dem Deklarationen wie char (*(*a[2])())[2] möglich sind. Gut, dass es mit cdel ein Programm zum »Vorlesen« solcher Definitionen gibt.
4 Eine Software wie Mathematica warnt vor Variablen mit fast identischem Namen.
5 In Java bilden long und short einen eigenen Datentyp. Sie dienen nicht wie in C(++) als Modifizierer. Eine Deklaration wie long int ist also falsch. Auf den iSeries Servern von IBM – früher AS/400 – gibt es auch einen Datentyp unsigned long long int.
6 LOGO verwendet für negtive Exponenten den Buchstaben N anstelle des E. In Java bleibt das E mit einem folgenden ??? Plus- oder Minuszeichen.
7 Nicht alle aus C stammenden Escape-Sequenzen finden sich auch in Java wieder. Es gibt kein '\a' (Alert), '\v' (vertikaler Tabulator), '\?' (Ausrufezeichen) und kein '\x', was eine hexadezimale Zahl einleitet (dafür lässt sich in Java \uXXXX nutzen).
8 http//:java.sun.com/docs/books/jls/third_edition/html/conversions.html#5.6.1
9 Die so genannten Objektvariablen oder Klassenvariablen, doch dazu später mehr.
|