Galileo Computing < openbook >
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.


Java ist auch eine Insel von Christian Ullenboom
Buch: Java ist auch eine Insel (Galileo Computing)
gp Kapitel 2 Sprachbeschreibung
gp 2.1 Anweisungen und Programme
gp 2.2 Elemente der Programmiersprache Java
gp 2.2.1 Textkodierung durch Unicode-Zeichen
gp 2.2.2 Unicode-Tabellen unter Windows
gp 2.2.3 Literale
gp 2.2.4 Bezeichner
gp 2.2.5 Reservierte Schlüsselwörter
gp 2.2.6 Token
gp 2.2.7 Semantik
gp 2.2.8 Kommentare
gp 2.2.9 Funktionsaufrufe als Anweisungen
gp 2.2.10 Die leere Anweisung
gp 2.2.11 Der Block
gp 2.3 Datentypen
gp 2.3.1 Primitive Datentypen
gp 2.3.2 Wahrheitswerte
gp 2.3.3 Variablendeklarationen
gp 2.3.4 Ganzzahlige Datentypen
gp 2.3.5 Die Fließkommazahlen
gp 2.3.6 Alphanumerische Zeichen
gp 2.3.7 Die Typanpassung (das Casting)
gp 2.3.8 Lokale Variablen, Blöcke und Sichtbarkeit
gp 2.3.9 Initialisierung von lokalen Variablen
gp 2.4 Ausdrücke, Operanden und Operatoren
gp 2.4.1 Zuweisungsoperator und Verbundoperator
gp 2.4.2 Präfix- oder Postfix-Inkrement und -Dekrement
gp 2.4.3 Unäres Minus und Plus
gp 2.4.4 Arithmetische Operatoren
gp 2.4.5 Die relationalen Operatoren
gp 2.4.6 Logische Operatoren
gp 2.4.7 Reihenfolge und Rang der Operatoren in der Auswertungsreihenfolge
gp 2.4.8 Überladenes Plus für Strings
gp 2.4.9 Was C(++)-Programmierer vermissen könnten
gp 2.5 Bedingte Anweisungen oder Fallunterscheidungen
gp 2.5.1 Die if-Anweisung
gp 2.5.2 Die Alternative wählen mit einer if/else-Anweisung
gp 2.5.3 Die switch-Anweisung bietet die Alternative
gp 2.6 Schleifen
gp 2.6.1 Die while-Schleife
gp 2.6.2 Schleifenbedingungen und Vergleiche mit ==
gp 2.6.3 Die do/while-Schleife
gp 2.6.4 Die for-Schleife
gp 2.6.5 Ausbruch planen mit break und Wiedereinstieg mit continue
gp 2.6.6 break und continue mit Sprungmarken
gp 2.7 Methoden einer Klasse
gp 2.7.1 Bestandteil einer Funktion
gp 2.7.2 Aufruf
gp 2.7.3 Methoden ohne Parameter
gp 2.7.4 Statische Methoden (Klassenmethoden)
gp 2.7.5 Parameter und Wertübergabe
gp 2.7.6 Methoden vorzeitig mit return beenden
gp 2.7.7 Nicht erreichbarer Quellcode bei Funktionen
gp 2.7.8 Rückgabewerte
gp 2.7.9 Methoden überladen
gp 2.7.10 Vorinitialisierte Parameter bei Funktionen
gp 2.7.11 Finale lokale Variablen
gp 2.7.12 Finale Referenzen in Objekten und das fehlende const
gp 2.7.13 Rekursive Funktionen
gp 2.7.14 Die Ackermann-Funktion
gp 2.7.15 Die Türme von Hanoi
gp 2.8 Weitere Operatoren
gp 2.8.1 Bitoperationen
gp 2.8.2 Vorzeichenlose Bytes in ein Integer und Char konvertieren
gp 2.8.3 Variablen mit Xor vertauschen
gp 2.8.4 Die Verschiebeoperatoren
gp 2.8.5 Setzen, Löschen, Umdrehen und Testen von Bits
gp 2.8.6 Der Bedingungsoperator
gp 2.9 Einfache Benutzereingaben


Galileo Computing

2.3 Datentypendowntop

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 Bytes. 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.

Die grobe Richtung

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:

gp 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.
gp 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.


Galileo Computing

2.3.1 Primitive Datentypendowntop

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.


Schlüsselwort/Typ Länge in Bytes 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

Tabelle 2.4 Java-Datentypen und deren Wertebereiche

Abbildung
Hier klicken, um das Bild zu Vergrößern

Zwei wesentliche Punkte zeichnen die primitiven Datentypen aus:

gp 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.
gp 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.

Galileo Computing

2.3.2 Wahrheitswertedowntop

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.

Der boolesche Typ wird beispielsweise bei Bedingungen, Verzweigungen oder Schleifen benötigt.


Galileo Computing

2.3.3 Variablendeklarationendowntop

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 wir in C gibt es in Java nicht3. 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. Ebenfalls darf der Variablenname mit keinem reservierten Schlüsselwort identisch sein.


Hinweis Zwei Variablen ähnlicher Schreibweise, etwa counter und counters, führen schnell zu Verwirrung. Als Programmierer sollten wir uns konsistent an ein Namensschema halten.10

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;

Abbildung
Hier klicken, um das Bild zu Vergrößern

Ist ein Bezeichnername unglücklich gewählt, so lässt er sich ohne Probleme konsistent umbenennen. Dazu ist im Menü Refactor, Rename - oder auch kurz (Alt)+(ª)+(R) - auszuwählen; der Cursor muss auf dem Bezeichner stehen. Ein optionaler Preview zeigt an, welche Änderungen die Umbenennung nach sich ziehen wird.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 2.2 Definition mehrerer Variablen gleichzeitig

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. Sie ist ebenfalls 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.


Galileo Computing

2.3.4 Ganzzahlige Datentypendowntop

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 (lassen wir char einmal außen vor) sind in Java immer vorzeichenbehaftet; einen Modifizierer unsigned wie in C(++) gibt es nicht.5


Beispiel Variablendeklaration mit Wertinitialisierung
int schuhGröße;
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 optional.

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.

gp 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 zu Deutsch »acht« heißt. Das Oktalsystem war früher eine verbreitete Darstellung, da nicht mehr einzelne Bits solo betrachtet werden mussten, sondern 3 Bits zu einer Gruppe zusammengefasst wurden. In der Kommunikationselektronik ist das Oktalsystem noch weiterhin beliebt.
gp 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.

Für Dualzahlen (also Binärzahlen zur Basis 2) gibt es keine Notation.


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!

Der Datentyp long

Ganzzahlen doppelter Größe werden 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 Bits 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

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.


Galileo Computing

2.3.5 Die Fließkommazahlendowntop

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.

Die Literale bestehen aus einem Vorkommateil, einem Dezimalpunkt (kein Komma) und einem Nachkommateil. Optional kann ein Exponent angegeben werden. 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;

Der Exponent kann entweder positiv oder negativ sein6, muss aber eine Ganzzahl sein.


Galileo Computing

2.3.6 Alphanumerische Zeichendowntop

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.


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

Tabelle 2.5 Escape-Sequenzen


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?\"";


Galileo Computing

2.3.7 Die Typanpassung (das Casting)downtop

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:

gp Automatische Typanpassung
gp Daten eines kleineren Datentyps werden automatisch (implizit) dem größeren angepasst. Der Compiler nimmt diese Anpassung selbstständig vor.
gp Explizite Typanpassung
gp Ein größerer Typ kann einem kleineren Typ nur mit 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 Bits des Ergebniswerts werden übergeben. Folgende Typumwandlungen sind ohne Informationsverlust möglich:


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

Tabelle 2.6 Zuweisungen ohne Informationsverlust

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;

Abbildung
Hier klicken, um das Bild zu Vergrößern

Passt der Typ eines Ausdrucks nicht, lässt er sich mit (Strg)+(1) korrigieren.

Eine Typumwandlung hat eine sehr hohe Priorität. Daher muss der Ausdruck gegebenenfalls geklammert werden.

Abbildung
Hier klicken, um das Bild zu Vergrößern


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
double w = +12.34;
double x = +67.89;
double y = -12.34;
double z = -67.89;
System.out.println( (int) w );  // 12
System.out.println( (int) x );  // 67
System.out.println( (int) y );  // -12
System.out.println( (int) z );  // -67

Bei der Konvertierung eines größeren Ganzzahltyps in einen kleineren werden einfach die oberen Bits abgeschnitten. Eine Anpassung des Vorzeichens findet nicht statt.

int i = 123456789;
int j = -123456;
System.out.println( (short) i );  // -13035
System.out.println( (short) j );  // 7616

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 zu der 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 Bits. Diese Eigenart ist insofern verwunderlich, als dass doch auch ein int nur dann zu einem long erweitert wird, wenn einer der Operanden eines Ausdrucks vom Typ long ist.

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 war nicht gesichert, da die Programmierer diesen Zahlenbereich nicht angenommen hatten. Die Konsequenz war, dass das Lenksystem zusammenbrach und die Selbstzerstörung ausgelöst wurde, 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.


Galileo Computing

2.3.8 Lokale Variablen, Blöcke und Sichtbarkeitdowntop

In jedem Block und auch in jeder Klasse8 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. Falls Objekte im Block angelegt wurden, wird der GC diese wieder freigeben, falls keine zusätzliche Referenz besteht.

Variablennamen können innerhalb eines Blocks nicht genauso gewählt werden wie lokale Variablennamen 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.

Abbildung
Hier klicken, um das Bild zu Vergrößern

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.

Abbildung
Hier klicken, um das Bild zu Vergrößern


Abbildung
Hier klicken, um das Bild zu Vergrößern


Galileo Computing

2.3.9 Initialisierung von lokalen Variablentoptop

Während Objektvariablen automatisch mit einem Nullwert initialisiert werden, geschieht dies bei lokalen Variablen nicht. Das heißt, der Programmierer muss sich selbst um die Initialisierung kümmern.


Beispiel Häufig passieren Fehler bei falsch angewendeten bedingten Anweisungen, wie das folgende Programmsegment demonstriert:
void test()
{
  int nene, williWurm;
  nene += 1;                   // Compilerfehler
  nene = 0; nene = nene + 1;
  if ( nene == 1 )
    williWurm = 2;
  williWurm = williWurm + 1;   // Compilerfehler
}

Die beiden lokalen Variablen nene und williWurm werden nicht automatisch mit Null initialisiert - so wie dies für Objektvariablen der Fall ist. So kommt es bei der Inkrementierung von nene zu einem Compilerfehler. Denn dazu ist erst ein Lesezugriff auf die Variable nötig, um anschließend den Wert 1 zu addieren. Der erste Zugriff muss aber eine Zuweisung sein. Das bedeutet, nene = 0 ist in Ordnung. Den Fehler würden wir auch bekommen, wenn wir in System.out.println(nene) die Variablenbelegung auslesen würden.

Oftmals gibt es jedoch bei Zuweisungen in bedingten Anweisungen Probleme. Da williWurm nur nach der if-Abfrage auf den Wert 2 gesetzt wird, wäre nur unter der Bedingung nene gleich 2 ein Lesezugriff auf williWurm möglich. Da diese Variable jedoch sonst vorher nicht gesetzt wurde, ergäbe sich das oben angesprochene Problem.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Ein Hinweis und Verbesserungsvorschlag, wenn eine lokale Variable nicht initialisiert ist.

Abbildung
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.

6 LOGO verwendet für negative Exponenten den Buchstaben N anstelle des E. In Java bleibt das E mit einem folgenden unären Plus- oder Minus-Zeichen.

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 Die so genannten Objektvariablen oder Klassenvariablen, doch dazu später mehr.





Copyright (c) Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de