2.5 Ausdrücke, Operanden und Operatoren 

Beginnen wir mit mathematischen Ausdrücken, um dann die Schreibweise in Java zu ermitteln. Eine mathematische Formel, etwa der Ausdruck –27 * 9, besteht aus Operanden (engl. operand) und Operatoren (engl. operator). Ein Operand ist eine Variable oder ein Literal. Im Fall einer Variablen wird der Wert aus der Variablen ausgelesen und mit ihm die Berechnung durchgeführt.
Beispiel Ein Ausdruck mit Zuweisungen: int i = 12, j; j = i * 2; Die Multiplikation berechnet das Produkt von 12 und 2 und speichert das Ergebnis in j ab. Von allen primitiven Variablen, die in dem Ausdruck vorkommen, wird also der Wert ausgelesen und in den Ausdruck eingesetzt. [Es gibt Programmiersprachen, in denen Wertoperationen besonders gekennzeichnet werden. So etwa in LOGO. Eine Wertoperation schreibt sich dort mit einem Doppelpunkt vor der Variablen, etwa :X + :Y.] |
Dies nennt sich auch Wertoperation, da der Wert der Variablen betrachtet wird und nicht ihr Speicherort oder gar ihr Variablenname.
Die Arten von Operatoren
Operatoren verknüpfen die Operanden. Je nach Anzahl der Operanden unterscheiden wir:
- Ist ein Operator auf genau einem Operand definiert, so nennt er sich unärer Operator (oder einstelliger Operator). Das Minus (negatives Vorzeichen) vor einem Operand ist ein unärer Operator, da er für genau den folgenden Operanden gilt.
- Die üblichen Operatoren Plus, Minus, Mal und Geteilt sind binäre (zweistellige) Operatoren.
- Es gibt auch einen Fragezeichenoperator für bedingte Ausdrücke, der dreistellig ist.
Ausdrücke
Ein Ausdruck (engl. expression) ergibt bei der Auswertung ein Ergebnis. Dieser Wert wird auch Resultat genannt. Ausdrücke haben immer einen Wert, während das für Anweisungen (wie eine Schleife) nicht gilt. Daher kann ein Ausdruck an allen Stellen stehen, an denen ein Wert benötigt wird. Dieser Wert ist entweder ein
- numerischer Wert (von arithmetischen Ausdrücken), Wahrheitswert (boolean) oder eine
- Referenz (etwa von einer Objekt-Erzeugung).
Operatoren erlauben die Verbindung einzelner Ausdrücke zu neuen Ausdrücken. Einige Operatoren sind aus der Schule bekannt, wie Addition, Vergleich, Zuweisung und weitere. C(++)-Programmierer werden viele Freunde wiedererkennen.
2.5.1 Arithmetische Operatoren 

Ein arithmetischer Operator verknüpft die Operanden mit den Operatoren Addition (+), Subtraktion (–), Multiplikation (*) und Division (/). Zusätzlich gibt es den Restwert-Operator %, der den bei der Division verbleibenden Rest betrachtet. Alle Operatoren sind für ganzzahlige Werte sowie für Fließkommazahlen definiert. [In C sind sie nur für Ganzzahlen definiert.] Die arithmetischen Operatoren sind binär, und auf der linken und rechten Seite sind die Typen numerisch. Der Ergebnistyp ist ebenfalls numerisch.
Numerische Umwandlung
Bei Ausdrücken mit unterschiedlichen numerischen Datentypen, etwa int und double, bringt der Compiler vor der Anwendung der Operation alle Operanden auf den umfassenderen Typ. Vor der Auswertung von 1 + 2.0 wird somit die Ganzzahl 1 zum double konvertiert und dann die Addition vorgenommen – das Ergebnis ist auch vom Typ double. Das nennt sich numerische Umwandlung (engl. numeric promotion). Bei byte und short gilt die Sonderregelung, dass sie vorher auf int konvertiert werden. [http://java.sun.com/docs/books/jls/third_edition/html/conversions.html#26917] (Auch im Java-Bytecode gibt es keine arithmetischen Operationen auf byte, short und char.) Anschließend wird die Operation ausgeführt, und der Ergebnistyp entspricht dem umfassenderen Typ.
Der Divisionsoperator
Der binäre Operator »/« bildet den Quotienten aus Dividend und Divisor. Auf der linken Seite steht der Dividend und auf der rechten der Divisor. Die Division ist für Ganzzahlen und für Fließkommazahlen definiert. Bei der Ganzzahldivision wird zu null hin gerundet, und das Ergebnis ist keine Fließkommazahl, sodass 1/3 das Ergebnis 0 ergibt und nicht 0,333... Den Datentyp des Ergebnisses bestimmen die Operanden und nicht der Operator. Soll das Ergebnis vom Typ double sein, muss ein Operand ebenfalls double sein.
System.out.println( 1.0 / 3 ); // 0.3333333333333333 System.out.println( 1 / 3.0 ); // 0.3333333333333333 System.out.println( 1 / 3 ); // 0
Schon die Schulmathematik lehrte uns, dass die Division durch null nicht erlaubt ist. Führen wir in Java eine Ganzzahldivision mit dem Divisor 0 durch, so bestraft uns Java mit einer ArithmeticException. Bei Fließkommazahlen liefert eine Division durch 0 keine Ausnahme, sondern +/– unendlich und bei 0.0/0.0 den Sonderwert NaN (mehr dazu in Kapitel 5, »Mathematisches«). Ein NaN steht für Not a Number (auch schon manchmal »Unzahl« genannt) und wird vom Prozessor erzeugt, falls er eine mathematische Operation wie die Division durch null nicht durchführen kann. In Kapitel 5 werden wir auf NaN noch einmal zurückkommen.
Anekdote Beim Lenkraketenkreuzer USS Yorktown gab ein Mannschaftsmitglied aus Versehen die Zahl Null ein. Das führte zu einer Division durch null, und der Fehler pflanzte sich so weit fort, dass die Software abstürzte und das Antriebssystem stoppte. Das Schiff trieb mehrere Stunden antriebslos im Wasser. |
Der Restwert-Operator %
Eine Ganzzahldivision muss nicht unbedingt glatt aufgehen, wie im Fall 9/2. In diesem Fall gibt es den Rest 1. Diesen Rest liefert der Restwert-Operator (engl. remainder operator), oft auch Modulo genannt. (Mathematiker unterscheiden die beiden Begriffe Rest und Modulo, da ein Modulo nicht negativ ist, der Rest in Java aber schon. Das soll uns aber egal sein.)
System.out.println( 9 % 2 ); // 1
Im Gegensatz zu C(++) [Wir müssten in C(++) die Funktion fmod() benutzen.] ist der Restwert-Operator in Java auch auf Fließkommazahlen anwendbar, und die Operanden können negativ sein. [Die Sprachdefinition von C(++) schreibt bei der Division und beim Modulo mit negativen Zahlen keine Berechnungsmethode vor.]
System.out.println( 12. % 2.5 ); // 2.0
Die Division und der Restwert richten sich in Java nach einer einfachen Formel: (int)(a/b)*b + (a%b) = a.
Beispiel Die Gleichung ist erfüllt, wenn wir etwa a = 10 und b = 3 wählen. Es gilt: (int)(10/3) = 3 und 10 % 3 ergibt 1. Dann ergeben 3 * 3 + 1 = 10. |
Aus dieser Gleichung folgt, dass beim Restwert das Ergebnis nur dann negativ ist, wenn der Dividend negativ ist; er ist nur dann positiv, wenn der Dividend positiv ist. Es ist leicht einzusehen, dass das Ergebnis der Restwert-Operation immer echt kleiner ist als der Wert des Divisors. Wir haben den gleichen Fall wie bei der Ganzzahldivision, dass ein Divisor mit dem Wert 0 eine ArithmeticException auslöst und bei Fließkommazahlen zum Ergebnis NaN führt.
Listing 2.5 RemainerAndDivDemo.java, main()
System.out.println( "+5 % +3 = " + (+5 % +3) ); // 2 System.out.println( "+5 / +3 = " + (+5 / +3) ); // 1 System.out.println( "+5 % –3 = " + (+5 % –3) ); // 2 System.out.println( "+5 / –3 = " + (+5 / –3) ); // –1 System.out.println( "-5 % +3 = " + (-5 % +3) ); // –2 System.out.println( "-5 / +3 = " + (-5 / +3) ); // –1 System.out.println( "-5 % –3 = " + (-5 % –3) ); // –2 System.out.println( "-5 / –3 = " + (-5 / –3) ); // 1
Gewöhnungsbedürftig ist die Tatsache, dass der erste Operand (Dividend) das Vorzeichen des Restes definiert und niemals der zweite (Divisor).
Hinweis Um mit value % 2 == 1 zu testen, ob value eine ungerade Zahl ist, muss value positiv sein, denn –3 % 2 wertet Java zu –1 aus. Der Test auf ungerade Zahlen wird erst wieder korrekt mit value % 2 != 0. |
Restwert für Fließkommazahlen und IEEEremainder()
Über die oben genannte Formel können wir auch bei Fließkommazahlen das Ergebnis einer Restwert-Operation leicht berechnen. Dabei muss beachtet werden, dass sich der Operator nicht so wie unter IEEE 754 verhält. Denn diese Norm schreibt vor, dass die Restwert-Operation den Rest von einer rundenden Division berechnet und nicht von einer abschneidenden. So wäre das Verhalten nicht analog zum Restwert bei Ganzzahlen. Java definiert den Restwert jedoch bei Fließkommazahlen genauso wie den Restwert bei Ganzzahlen. Wünschen wir ein Restwert-Verhalten, wie es IEEE 754 vorschreibt, so können wir immer noch die Bibliotheksfunktion Math.IEEEremainder() verwenden.
Auch bei der Restwert-Operation bei Fließkommazahlen werden wir niemals eine Exception erwarten. Eventuelle Fehler werden, wie im IEEE-Standard beschrieben, mit NaN angegeben. Ein Überlauf oder Unterlauf kann zwar passieren, aber nicht geprüft werden.
Rundungsfehler
Prinzipiell sollten Anweisungen wie 1.1 – 0.1 immer 1.0 ergeben, jedoch treten interne Rundungsfehler bei der Darstellung auf und lassen das Ergebnis von Berechnung zu Berechnung immer ungenauer werden. Ein besonders ungünstiger Fehler trat 1994 beim Pentium-Prozessor im Divisionsalgorithmus Radix-4 SRT auf, ohne dass der Programmierer der Schuldige war.
double x, y, z; x = 4195835.0; y = 3145727.0; z = x – (x/y) * y; System.out.println( z );
Ein fehlerhafter Prozessor liefert hier 256, obwohl laut Rechenregel das Ergebnis 0 sein muss. Laut Intel sollte für einen normalen Benutzer (Spieler, Softwareentwickler, Surfer?) der Fehler nur alle 27.000 Jahre auftauchen. Glück für die meisten. Eine Studie von IBM errechnete eine Fehlerhäufigkeit von einmal in 24 Tagen. Alles in allem hat Intel die CPUs zurückgenommen, über 400 Millionen US-Dollar verloren und spät den Kopf gerade noch aus der Schlinge gezogen.
Die meisten Rundungsfehler resultieren aber daher, dass endliche Dezimalbrüche im Rechner als Näherungswerte für periodische Binärbrüche repräsentiert werden müssen. 0.1 entspricht einer periodischen Mantisse im IEEE-Format.
2.5.2 Unäres Minus und Plus 

Die binären Operatoren sitzen zwischen zwei Operanden, während sich ein unärer Operator genau einen Operanden vornimmt. Das unäre Minus (Operator zur Vorzeichenumkehr) etwa dreht das Vorzeichen des Operanden um. So wird aus einem positiven Wert ein negativer und aus einem negativen ein positiver.
Beispiel Drehe das Vorzeichen einer Zahl um: a = –a; Alternativ ist: a = –1 * a; |
Das unäre Plus ist eigentlich unnötig; die Entwickler haben es jedoch aus Symmetriegründen mit eingeführt.
Beispiel Minus und Plus sitzen direkt vor dem Operanden, und der Compiler weiß selbstständig, ob dies unär oder binär ist. Der Compiler erkennt auch folgende Konstruktion: int i = – – – 2 + – + 3; Dies ergibt den Wert –5. Einen Ausdruck wie –––2+–+3 erkennt der Compiler dagegen nicht an, da die zusammenhängenden Minuszeichen als Inkrement interpretiert werden und nicht als unärer Operator. Das Leerzeichen ist also bedeutend. |
Vorzeichen erfragen
Um für das Vorzeichen einen Wert +1 für positive oder –1 für negative Zahlen und 0 für 0 zu bekommen, lässt sich die Funktion signum() verwenden. Sie ist nicht ganz logisch auf die Klasse Math für Fließkommazahlen und Integer/Long für Ganzzahlen verteilt:
- java.lang.Integer.signum( int i )
- java.lang.Long.signum( long i )
- java.lang.Math.signum( double d )
- java.lang.Math.signum( float f )
2.5.3 Zuweisung mit Operation 

In Java lassen sich Zuweisungen mit numerischen Operatoren kombinieren. Für einen Operator # im Ausdruck a = a # (b) kürzt der Verbundoperator den Ausdruck zu a #= b ab. So addiert a += 2 zur Variable a 2, und der Rückgabewert ist die um 2 erhöhte Variable a.
Besondere Obacht sollten wir auf die automatische Klammerung geben. Bei einem Ausdruck wie a *= 3 + 5 gilt a = a * (3 + 5) und nicht selbstverständlich die Punkt-vor-Strich-Regelung a = a * 3 + 5.
Falls es sich bei der rechten Seite um einen komplexeren Ausdruck handelt, wird dieser nur einmal ausgewertet. Dies ist wichtig bei Methodenaufrufen, die Nebenwirkungen besitzen, etwa Zustände wie einen Zähler verändern.
Beispiel Wir profitieren auch bei Feldzugriffen (siehe Abschnitt 3.10, »Arrays«) von Verbundoperationen, da die Auswertung des Indexes nur einmal stattfindet: feld[ 2 * i + j ] = feld[ 2 * i + j ] + 1; Leichter zu lesen ist die folgende Anweisung: feld[ 2 * i + j ] += 1; |
Typanpassung beim Verbundoperator
Beim Verbundoperator wird noch etwas mehr gemacht, als E1 #= E2 zu E1 = (E1) # (E2) aufzulösen. Interessanterweise kommt auch noch der Typ von E1 ins Spiel, denn der Ausdruck E1 # E2 wird vor der Zuweisung auf den Datentyp von E1 gebracht, sodass es genau heißen muss: E1 #= E2 wird zu E1 = (Typ von E1)((E1) # (E2)).
Beispiel Auf eine Ganzzahl soll der Verbundoperator eine Fließkommazahl addieren. int i = 1973; i += 30.2; Die Anwendung des Verbundoperators ist in Ordnung, denn der Übersetzer nimmt eine implizite Typanpassung vor, sodass die Bedeutung bei i = (int)(i + 30.2) liegt. So viel dazu, dass Java eine intuitive und einfache Programmiersprache sein soll. |
2.5.4 Präfix- oder Postfix-Inkrement und -Dekrement 

Das Herauf- und Heruntersetzen von Variablen ist eine sehr häufige Operation, wofür die Entwickler in der Vorgängersprache C auch einen Operator spendiert hatten. Die praktischen Operatoren ++ und -- kürzen die Programmzeilen zum Inkrement und Dekrement ab.
i++; // Abkürzung für i = i + 1 j--; // j = j – 1
Eine lokale Variable muss allerdings vorher initialisiert sein, da ein Lesezugriff vor einem Schreibzugriff stattfindet. Der ++/–--Operator erfüllt also zwei Aufgaben: Neben der Wertrückgabe gibt es eine Veränderung der Variablen.
Vorher oder nachher
Die beiden Operatoren liefern einen Ausdruck und geben daher einen Wert zurück. Es macht jedoch einen feinen Unterschied, wo dieser Operator platziert wird. Es gibt ihn nämlich in zwei Varianten: vor der Variablen, wie in ++i (Präfix-Schreibweise), oder dahinter, wie bei i++ (Postfix-Schreibweise). Der Präfix-Operator verändert die Variable vor der Auswertung des Ausdrucks und der Postfix-Operator nach der Auswertung des Ausdrucks. Mit anderen Worten: Nutzen wir einen Präfix-Operator, so wird die Variable erst herauf- beziehungsweise heruntergesetzt und dann der Wert geliefert.
Beispiel Präfix/Postfix in einer Ausgabeanweisung: int i = 10, j = 20; System.out.println( ++i ); // 11 System.out.println( --j ); // 19 System.out.println( i ); // 11 System.out.println( j ); // 19 int i = 10, j = 20; System.out.println( i++ ); // 10 System.out.println( j-- ); // 20 System.out.println( i ); // 11 System.out.println( j ); // 19 |
Mit der Möglichkeit, Variablen zu erhöhen und zu vermindern, ergeben sich vier Varianten:
Präfix | Postfix | |
Inkrement |
Prä-Inkrement, ++i |
Post-Inkrement, i++ |
Dekrement |
Prä-Dekrement, --i |
Post-Dekrement, i-- |
Hinweis In Java sind Inkrement (++) und Dekrement (--) für alle numerischen Datentypen erlaubt, also auch für Fließkommazahlen. double d = 12; System.out.println( --d ); // 11.0 double e = 12.456; System.out.println( --e ); // 11.456 |
Einige Kuriositäten
Wir wollen uns abschließend noch mit einer Besonderheit des Post-Inkrements und Prä-Inkrements beschäftigen, die nicht nachahmenswert ist:
a = 2; a = ++a; // a = 3 b = 2; b = b++; // b = 2
Im ersten Fall bekommen wir den Wert 3 und im zweiten Fall 2. Der erste Fall überrascht nicht, denn a = ++a erhöht den Wert 2 um 1, und anschließend wird 3 der Variablen a zugewiesen. Bei b ist es raffinierter: Der Wert von b ist 2, und dieser Wert wird intern vermerkt. Anschließend erhöht b++ die Variable b. Doch die Zuweisung setzt b auf den gemerkten Wert, der 2 war. Also ist b = 2.
Hinweis Das Post-Inkrement finden wir auch im Namen der Programmiersprache C++. Es soll ausdrücken, dass es C-mit-eins-drauf ist, also ein verbessertes C. Mit dem Wissen über den Postfix-Operator ist klar, dass diese Erhöhung aber erst nach der Nutzung auftritt – also C++ ist auch nur C, und der Vorteil kommt später. (Einer der Entwickler von Java, Bill Joy, hat einmal Java als C++-- [--C++ könnte besser passen: Erst wird C++ bereinigt und dann zu Java erweitert.] beschrieben. Er meinte damit C++ ohne die schwer zu pflegenden Eigenschaften.) Bei den von Microsoft geschaffenen Buchstabe-#-Sprachen wie C# – das # liest sich »Sharp« – wurde die Idee von den Musiknoten entlehnt. Doch eigentlich wird in der Musik das Sharp vor eine Note gesetzt und bedeutet, dass diese um einen Halbton erhöht wird. |
2.5.5 Die relationalen Operatoren und die Gleichheitsoperatoren 

Relationale Operatoren sind Vergleichsoperatoren, die Ausdrücke miteinander vergleichen und einen Wahrheitswert vom Typ boolean zurückgeben. Die von Java für numerische Vergleiche zur Verfügung gestellten Operatoren sind:
- Größer (>)
- Kleiner (<)
- Größer-gleich (>=), Kleiner-gleich (<=)
Weiterhin gibt es einen Spezial-Operator instanceof zum Testen von Referenzeigenschaften. Zudem kommen zwei Vergleichsoperatoren hinzu, die Java als Gleichheitsoperatoren bezeichnet:
- Test auf Gleichheit (==)
- Test auf Ungleichheit (!=)
Dass Java hier einen Unterschied macht, liegt an einem etwas anderen Vorrang, der uns aber nicht weiter beschäftigen soll.
Hinweis Bei einem Gleichheitsvergleich zwischen Variable und Literal schreiben viele Entwickler die Variable gerne links und die Konstante rechts, etwa in folgender Fallunterscheidung: if ( anzahlZentnerGoldes == 666 ) Beide Operanden können aber vertauscht werden, was am Ergebnis nichts ändert, denn der Vergleichsoperator ist kommutativ, wenn die beiden Seiten keine beeinflussenden Seiteneffekte produzieren, also etwa Zustände ändern. if ( 666 == anzahlZentnerGoldes ) |
Ebenso wie arithmetische Operatoren passen die relationalen Operatoren ihre Operanden an einen gemeinsamen Typ an. Handelt es sich bei den Typen um Referenztypen, so sind nur die Vergleichsoperatoren == und != erlaubt.
Kaum Verwechslungsprobleme durch == und =
Die Verwendung des relationalen Operators == und der Zuweisung = führt bei Einsteigern oft zu Problemen, da die Mathematik für Vergleiche und Zuweisungen immer nur ein Gleichheitszeichen kennt. Glücklicherweise ist das Problem in Java nicht so drastisch wie beispielsweise in C(++), da die Typen der Operatoren unterschiedlich sind. Der Vergleichsoperator ergibt immer nur den Rückgabewert boolean. Zuweisungen von numerischen Typen ergeben jedoch wieder einen numerischen Typ. Es kann also kein Problem wie das folgende geben:
int a = 10, b = 11; boolean result1 = ( a = b ); // Compilerfehler boolean result2 = ( a == b ); // Das ist OK
Beispiel Die Wahrheitsvariable hatVorzeichen soll dann true sein, wenn das Zeichen vorzeichen gleich dem Minus ist. boolean hatVorzeichen = (vorzeichen == '-'); Schön ist die Auswertungsreihenfolge zu sehen: Erst wird das Ergebnis des Vergleichs berechnet, und dieser Wahrheitswert wird anschließend in hatVorzeichen kopiert. |
2.5.6 Logische Operatoren Und, Oder, Xor, Nicht 

Mit logischen Operatoren werden Wahrheitswerte nach definierten Mustern verknüpft. Logische Operatoren operieren nur auf boolean-Typen, andere Typen führen zu Compilerfehlern. Java bietet die Operatoren Und (&&), Oder (||), Xor (^) und Nicht (!) an. Xor ist eine Operation, die genau dann falsch zurückgibt, wenn entweder beide Operanden wahr oder beide falsch sind. Sind sie unterschiedlich, so ist das Ergebnis wahr. Xor heißt auch exklusives beziehungsweise ausschließendes Oder.
Kurzschlussoperatoren
Eine Besonderheit sind die beiden Operatoren && (Und) beziehungsweise || (Oder). In der Regel muss ein logischer Ausdruck nur dann weiter ausgewertet werden, wenn er das Schlussergebnis noch beeinflussen kann. Zwei Operatoren bieten sich zur Optimierung der Ausdrücke an:
- Und: Ist einer der beiden Ausdrücke falsch, so kann der Ausdruck schon nicht mehr wahr werden. Das Ergebnis ist falsch.
- Oder: Ist mindestens einer der Ausdrücke schon wahr, so ist auch der gesamte Ausdruck wahr.
Der Compiler bzw. die Laufzeitumgebung kann den Programmfluss abkürzen. Daher nennen sich die beiden Operatoren auch Kurzschlussoperatoren (engl. short-circuit operator). [Den Begriff verwendet die Java-Sprachdefinition nicht! Siehe dazu auch http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.23.] Kürzt er ab, wertet er nur den ersten Ausdruck aus und den zweiten dann nicht mehr.
Nicht-Kurzschlussoperatoren
In einigen Fällen ist erwünscht, dass die Laufzeitumgebung alle Teilausdrücke auswertet. Das kann der Fall sein, wenn Funktionen Nebenwirkungen haben sollen, etwa Zustände ändern. Daher bietet Java zusätzlich die nicht über einen Kurzschluss arbeitenden Operatoren | und & an, die eine Auswertung aller Teilausdrücke erzwingen.
Beispiel In der ersten und dritten Anweisung wird die Methode boolean foo() nicht aufgerufen, in der zweiten und vierten schon. System.out.println( true || foo() ); // true, foo() wird nicht aufgerufen System.out.println( true | foo() ); // true, foo() wird aufgerufen System.out.println( false && foo() ); // false, foo() wird nicht aufgerufen System.out.println( false & foo() ); // false, foo() wird aufgerufen |
Für Xor kann es keinen Kurzschlussoperator geben, da immer beide Operanden ausgewertet werden müssen, bevor das Ergebnis feststeht.
2.5.7 Rang der Operatoren in der Auswertungsreihenfolge 

Aus der Schule ist der Spruch »Punktrechnung geht vor Strichrechnung« bekannt, sodass sich der Ausdruck 1 + 2 * 3 zu 7 und nicht zu 9 auswertet. [Dass von diesen Rechnungen eine gewisse Spannung ausgeht, zeigen diverse Fernsehkanäle, die damit ihr Abendprogramm füllen.]
Beispiel Auch wenn bei Ausdrücken wie a() + b() * c() zuerst das Produkt gebildet wird, schreibt doch die Auswertungsreihenfolge von binären Operatoren vor, dass der linke Operand zuerst ausgewertet werden muss, was bedeutet, dass Java zuerst die Methode a() aufruft. |
In den meisten Programmiersprachen gibt es eine Unzahl von Operatoren neben Plus und Mal, die alle ihre eigenen Vorrangregeln besitzen. [Es gibt Programmiersprachen wie APL, die keine Vorrangregeln kennen. Sie werten die Ausdrücke streng von rechts nach links oder umgekehrt aus.] Der Multiplikationsoperator besitzt zum Beispiel eine höhere Priorität und damit eine andere Auswertungsreihenfolge als der Plus-Operator. Die Rangordnung der Operatoren (engl. operator precedence) legt folgende Tabelle fest, wobei der arithmetische Typ für Ganz- und Fließkommazahlen steht und der integrale Typ für char und Ganzzahlen:
Operator | Rang | Typ | Beschreibung |
++, -- |
1 |
arithmetisch |
Inkrement und Dekrement |
+, - |
1 |
arithmetisch |
unäres Plus und Minus |
~ |
1 |
Integral |
bitweises Komplement |
! |
1 |
boolean |
logisches Komplement |
(Typ) |
1 |
jeder |
Cast |
*, /, % |
2 |
arithmetisch |
Multiplikation, Division, Rest |
+, - |
3 |
arithmetisch |
Addition und Subtraktion |
+ |
3 |
String |
String-Konkatenation |
<< |
4 |
Integral |
Verschiebung links |
>> |
4 |
Integral |
Rechtsverschiebung mit Vorzeichenerweiterung |
>>> |
4 |
Integral |
Rechtsverschiebung ohne Vorzeichenerweiterung |
<, <=, >, >= |
5 |
arithmetisch |
numerische Vergleiche |
instanceof |
5 |
Objekt |
Typvergleich |
==, != |
6 |
primitiv |
Gleich-/Ungleichheit von Werten |
==, != |
6 |
Objekt |
Gleich-/Ungleichheit von Referenzen |
& |
7 |
Integral |
bitweises Und |
& |
7 |
boolean |
logisches Und |
^ |
8 |
Integral |
bitweises Xor |
^ |
8 |
boolean |
logisches Xor |
| |
9 |
Integral |
bitweises Oder |
| |
9 |
boolean |
logisches Oder |
&& |
10 |
boolean |
logisches konditionales Und, Kurzschluss |
|| |
11 |
boolean |
logisches konditionales Oder, Kurzschluss |
?: |
12 |
jeder |
Bedingungsoperator |
= |
13 |
jeder |
Zuweisung |
*=, /=, %=, +=, -=, <<=, >>=, >>>=, &=, ^=, |= |
14 |
jeder |
Zuweisung mit Operation |
Die Rechenregeln für Mal vor Plus kann sich jeder noch leicht merken. Auch ist leicht zu merken, dass die typischen arithmetischen Operatoren wie Plus und Mal eine höhere Priorität als Vergleichsoperationen haben. Komplizierter ist die Auswertung bei den zahlreichen Operatoren, die seltener im Programm vorkommen.
Beispiel Wie ist die Auswertung bei dem nächsten Ausdruck? boolean A = false,
B = false,
C = true;
System.out.println( A && B || C ); |
Das Ergebnis könnte je nach Rangordnung true oder false sein. Doch die Tabelle lehrt uns, dass im Beispiel A && B || C das Und stärker als das Oder bindet, also der Ausdruck mit der Belegung A=false, B=false, C=true zu true ausgewertet wird. |
Vermutlich gibt es Programmierer, die dies wissen oder eine Tabelle mit Rangordnungen immer am Monitor kleben haben. Aber beim Durchlesen von fremdem Code ist es nicht schön, immer wieder die Tabelle konsultieren zu müssen, die verrät, ob nun das binäre Xor oder das binäre Und stärker bindet.
Tipp Alle Ausdrücke, die über die einfache Regel »Punktrechnung geht vor Strichrechnung« hinausgehen, sollten geklammert werden. Da die unären Operatoren ebenfalls sehr stark binden, kann eine Klammerung wegfallen. |
Links- und Rechtsassoziativität
Bei den Operatoren + und * gilt die mathematische Kommutativität und Assoziativität. Das heißt, die Operanden können prinzipiell umgestellt werden, und das Ergebnis sollte davon nicht beeinträchtigt sein. Bei der Division unterscheiden wir zusätzlich Links- und Rechtsassoziativität. Deutlich wird das am Beispiel A / B / C. Den Ausdruck wertet Java von links nach rechts aus, und zwar als (A / B) / C; daher ist der Divisionsoperator linksassoziativ. Hier sind Klammern angemessen. Denn würde der Compiler den Ausdruck zu A / (B / C) auswerten, käme es einem A * C / B gleich. In Java sind die meisten Operatoren linksassoziativ, aber es gibt Ausnahmen, wie Zuweisungen der Art A = B = C, die der Compiler zu A = (B = C) auswertet.
Hinweis Die mathematische Assoziativität ist natürlich gefährdet, wenn durch Überläufe Rechenfehler mit im Spiel sind. float a = –16777217F; float b = 16777216F; float c = 1F; System.out.println( a + b + c ); // 1.0 System.out.println( a + (b + c) ); // 0.0 Mathematisch ergibt –16777217 + 16777216 den Wert –1, und –1 plus +1 ist 0. Im zweiten Fall liefert –16777217 + (16777216 + 1) = –16777217 + 16777217 = 0. Doch Java wertet a + b durch die Beschränkung von float zu 0 aus, sodass mit c addiert, also 1, die Ausgabe 1 statt 0 erscheint. |
2.5.8 Die Typanpassung (das Casting) 

Zwar ist Java eine getypte Sprache, aber nicht so stark, dass es hinderlich ist. So übersetzt der Compiler die folgenden Zeilen problemlos:
int anInt = 1; long long1 = 1; long long2 = anInt;
Streng genommen könnte ein Compiler bei einer sehr starken Typisierung die beiden unteren Zeilen ablehnen, denn das Literal 1 ist vom Typ int und kein 1L, also long, und in long2 = anInt ist die Variable anInt vom Typ int statt vom gewünschten Datentyp long.
Arten der Typanpassung
In der Praxis kommt es also 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. Daher funktioniert unser erstes Beispiel mit etwa long2 = anInt.
- Explizite Typanpassung. Ein größerer Typ kann einem kleineren Typ mit möglichem Verlust von Informationen zugewiesen werden.
Typanpassungen gibt es bei primitiven Datentypen und bei Referenztypen. Während die folgenden Absätze die Anpassungen bei einfachen Datentypen beschreiben, kümmert sich Kapitel 6, »Eigene Klassen schreiben«, um die Typkompatibilität bei Referenzen.
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 führt Java automatisch aus:
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 in Klammern gesetzt.
Beispiel Umwandlung einer Fließkommazahl in eine Ganzzahl: int n = (int) 3.1415; // n = 3 |
Passt der Typ eines Ausdrucks nicht, lässt er sich mit +
korrigieren.

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 in 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.
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 Bits 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
si wird eine negative Zahl, da das 16. Bit beim int ii gesetzt war und nun beim short das negative Vorzeichen anzeigt. Die Zahl hinter ij hat kein 16. Bit gesetzt, und so wird das short sj positiv.
Unterschiedliche Wertebereiche bei Fließ- und Ganzzahlen
Natürlich kann die Konvertierung double 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« für einen großen Exponenten. Bei der impliziten Konvertierung eines long in ein double können einige Bit als Informationsträger herausfallen, wie es das folgende Beispiel illustriert.
long l = 1111111111111111111L; // 1111111111111111111
double d = l; // 1111111111111111170 (1.11111111111111117E18)
long m = (long) d; // 1111111111111111168
Java erlaubt ohne explizite Anpassung die Konvertierung eines long an ein double und auch an ein noch kleineres float, was vielleicht noch merkwürdiger ist, da float nur eine Genauigkeit von 6 bis 7 Stellen, long hingegen 18 Stellen hat.
long l = 1000000000000000000L;
float f = l;
System.out.printf( "%f", f ); // 999999984306749440,000000
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, weil dort im Unicode das erste Bit gesetzt ist, das 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 arithmetische 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).
Berechnungen bei Datentypen byte und short auf int-Basis
Leider ist die Typanpassung nicht ganz so einleuchtend, wie folgendes Beispiel zeigt:
Listing 2.6 AutoConvert.java, main()
short s1 = 1, s2 = 2; byte b1 = 1, b2 = 2; int i1 = 1, i2 = 2; long l1 = 1, l2 = 2; // short s3 = s1 + s2; // Type mismatch: cannot convert from int to short // byte b3 = b1 + b2; // Type mismatch: cannot convert from int to byte short s3 = (short)( s1 + s2 ); byte b3 = (byte)( b1 + b2 ); int i3 = i1 + i2; long l3 = l1 + l2;
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; intern im Bytecode ist es ebenso realisiert. Überläufe werden 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.
Tipp »Kleine« Typen wie short und byte führen oft zu Problemen. Wenn sie nicht absichtlich in großen Feldern verwendet werden und Speicherplatz nicht ein absolutes Kriterium ist, erweist sich int als die beste Wahl – auch weil Java nicht durch besonders intuitive 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" Der Compiler meldet einen Fehler, denn 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. [http://java.sun.com/docs/books/jls/third_edition/html/conversions.html#5.6.1] |
Materialverlust durch Überläufe
Überläufe bei Berechnungen können zu schwerwiegenden 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) hatte die unbemannte Rakete, die vier Satelliten an Bord hatte, von Französisch-Guayana aus gestartet. 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 2^15 – 1 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 das Programm schnell abgebrochen werden können. Ungünstig war, dass der Programmteil unverändert durch Wiederverwendung per Copy & Paste aus der Ariane-4-Software kopiert worden war, die Ariane 5 aber schneller flog.
Typanpassung zwischen einfachen Typen und Referenztypen
Allgemeine Umwandlungen zwischen einfachen Typen und Referenztypen gibt es nicht. Falsch sind zum Beispiel:
Listing 2.7 TypecastPrimRef.java, main() Teil 1
String s = (String) 1; // Cannot cast from int to String int i = (int) "1"; // Cannot cast from String to int
Einiges sieht dagegen nach Typanpassung aus, ist aber in Wirklichkeit eine Technik, die sich Autoboxing nennt (Kapitel 3, »Klassen und Objekte«, geht näher darauf ein).
Listing 2.8 TypecastPrimRef.java, main() Teil 2
Long lông = (Long) 2L; // Or: Long lông = 2L; System.out.println( (Boolean) true ); ((Integer)2).toString();
2.5.9 Überladenes Plus für Strings 

Obwohl sich in Java die Operatoren fast alle auf primitive Datentypen beziehen, gibt es doch eine weitere Verwendung des Plus-Operators. Diese wurde in Java eingeführt, da ein Aneinanderhängen von Zeichenketten oft benötigt wird. Objekte vom Typ String können durch den Plus-Operator mit anderen Strings und Datentypen verbunden werden. Falls zusammenhängende Teile nicht alle den Datentyp String annehmen, werden sie automatisch in einen String umgewandelt. Der Ergebnistyp ist immer String.
Beispiel Setze fünf Teile zu einem String zusammen: String s = '"' + "Extrem Sandmännchen" + '"' + " frei ab " + 18; // char String char String int System.out.println( s ); // "Extrem Sandmännchen" frei ab 18 |
Besteht der Ausdruck aus mehreren Teilen, so muss die Auswertungsreihenfolge beachtet werden, andernfalls kommt es zu seltsamen Zusammensetzungen. So ergibt "Aufruf von " + 1 + 0 + 0 + " Ökonomen" tatsächlich »Aufruf von 100 Ökonomen« und nicht »Aufruf von 1 Ökonomen«, da der Compiler die Konvertierung in Strings dann startet, wenn er einen Ausdruck als String-Objekt erkannt hat.
Beispiel Auswertungsreihenfolge vom Plus: Listing 2.9 PlusString.java, main() System.out.println( 1 + 2 ); // 3 System.out.println( "1" + 2 + 3 ); // 123 System.out.println( 1 + 2 + "3" ); // 33 System.out.println( 1 + 2 + "3" + 4 + 5 ); // 3345 Nur eine Zeichenkette in doppelten Anführungszeichen ist ein String, und der Plus-Operator entfaltet seine besondere Wirkung. Ein einzelnes Zeichen in einfachen Hochkommata wird lediglich auf ein int gecastet, und Additionen sind Ganzzahl-Additionen. System.out.println( '0' + 2 ); // 50 – ASCII value for '0' is 48 System.out.println( 'A' + 'a' ); // 162 – 'A'=65, 'a'=97 |
Beispiel Der Plus-Operator für Zeichenketten geht streng von links nach rechts und bereitet mit eingebetteten arithmetischen Ausdrücken mitunter Probleme. Eine Klammerung hilft, wie im Folgenden zu sehen ist: "Ist 1 größer als 2? " + (1 > 2 ? "ja" : "nein"); |
Wäre der Ausdruck um den Bedingungsoperator nicht geklammert, dann würde der Plus-Operator an die Zeichenkette die 1 anhängen, und es käme der >-Operator. Der erwartet aber kompatible Datentypen, die in unserem Fall – links stünde die Zeichenkette und rechts die Ganzzahl 2 – nicht gegeben sind. |