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.4 Ausdrücke, Operanden und Operatorendowntop

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 der Variablen ausgelesen und danach die Berechnung gemacht.


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.1 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. 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 Division 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. Dieses Wert ist entweder ein

gp E numerischer Typ (von arithmetischen Ausdrücken) oder ein
gp E Referenztyp (von einer Objekt-Allokation).

Operatoren erlauben die Verbindung von einzelnen Ausdrücken zu neuen Ausdrücken. Einige Operatoren sind aus der Schule bekannt, wie Addition, Vergleich, Zuweisung und Weitere. C(++)-Programmierer werden viele Freunde wieder erkennen.


Galileo Computing

2.4.1 Zuweisungsoperator und Verbundoperatordowntop

Der Zuweisungsoperator kopiert den Wert eines Ausdrucks der rechten Seite in die Variable der linken Seite. In Java wird eine Zuweisung (engl. assignment) mit dem Gleichheitszeichen = dargestellt. Der linke Teil einer Zuweisung ist eine Variable, die den Wert speichert und nennt sich l-Wert. Der rechte Teil ist der Ausdruck, der ausgewertet in die Variable geschrieben wird. Er nennt sich r-Wert.

int a;
a = 12 * 3;

Die Zuweisungen sehen zwar so aus wie mathematische Gleichungen, jedoch existiert ein wichtiger Unterschied: Die Formel a = a + 1 ist mathematisch nicht zu erfüllen, da es kein a geben kann, sodass a = a + 1 erfüllt ist - zumindest im Dezimalsystem. Aus Programmiersicht ist es in Ordnung, da a um Eins erhöht wird.

Da eine Zuweisung keine Anweisung, sondern ein Ausdruck mit einem Wert ist, der einen Rückgabewert ergibt, kann die Zuweisung auch an jeder anderen Stelle eingesetzt werden, an der ein Ausdruck stehen darf. Die Zuweisung kann auch in einem Funktionsaufruf erfolgen:

System.out.println( a = 19 );

Auch Zuweisungen der Form

a = b = 0;

sind erlaubt und gleichbedeutend mit b = 0 und a = b beziehungsweise a = (b = 0).

Daran lässt sich ablesen, dass beim Zuweisungsoperator die Auswertung von rechts nach links erfolgt.


Beispiel Die Wahrheitsvariable hatVorzeichen soll dann true sein, wenn das Zeichen vorzeichen gleich dem Minus ist. Für Vergleiche dient der Operator ==:
boolean hatVorzeichen = (vorzeichen == '-');

An dem Beispiel ist schön die Auswertungsreihenfolge zu sehen. Erst wird das Ergebnis des Vergleichs berechnet, und dieser Wahrheitswert wird anschließend in hatVorzeichen kopiert.

Verbundoperatoren

In Java können Zuweisungen mit numerischen (und auch bitweisen, aber dazu später) Operatoren kombiniert werden. Für einen Operator # in dem Ausdruck a = a # (b) gilt die Abkürzung durch einen Verbundoperator a #= b. So addiert der Ausdruck a += 2 zur Variable a 2 hinzu. Der Rückgabewert ist die um 2 erhöhte Variable a.

Falls es sich bei der rechten Seite um einen komplexeren Ausdruck handelt, wird dieser nur einmal ausgewertet. Dies ist wichtig bei Methodenaufrufen, die Seiteneffekte besitzen.


Beispiel Wir profitieren auch bei einem Feldzugriff von Verbundoperationen, da der Zugriff auf das Feldelement nur einmal stattfindet.
feld[2*i+j] = feld[2*i+j] + 1;

Leichter zu lesen ist die folgende Anweisung:

feld[2*i+j] += 1;

In der Langform a = a # (b) ist die Klammerung wichtig, denn bei dem Ausdruck a *= 3 + 5 gilt a = a * (3 + 5) und durch die Punkt-vor-Strich-Regelung nicht a = a * 3 + 5.



Galileo Computing

2.4.2 Präfix- oder Postfix-Inkrement und -Dekrementdowntop

Erhöhen/Erniedrigen 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.

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 und dahinter. Steht das Inkrement vor der Variablen, sprechen wir von Prä-Inkrement/Prä-Dekrement, steht es dahinter von Post-Inkrement/Post-Dekrement: kurz auch Präfix/Postfix genannt. Nutzen wir einen Präfix-Operator, so wird die Variable erst erhöht beziehungsweise erniedrigt und dann der Wert geliefert. Neben der Wertrückgabe gibt es eine Veränderung der Variablen.


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
Wir erkennen hier, dass der Wert erst erhöht wird und anschließend in die Berechnung eingeht. Deutlicher ist der Unterschied beim Postfix:
System.out.println( i++ );       // 10
System.out.println( j-- );       // 20
System.out.println( i );         // 11
System.out.println( j );         // 19

Das bedeutet, der Wert wird im Ausdruck verwendet und erst anschließend erhöht. Wir bekommen mit dem Präfix den Ausdruck nach der Operation und mit dem Postfix den Ausdruck davor.

Den 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 wir erst einen Zugriff haben und dann die Erhöhung stattfindet - also C++ ist auch nur C, und der Vorteil kommt später. (Einer der Entwickler von Java, Bill Joy, hat einmal Java als C++-- beschrieben. Er meinte damit C++ ohne die schwer zu pflegenden Eigenschaften.) Bei den von Microsoft geschaffenen Buchstabe#-Sprachen - das # liest sich »Sharp« - wurde die Idee von den Musiknoten entlehnt. Ein Sharp hinter einer Note bedeutet, dass sie um einen Halbton erhöht wird. Das stimmt: Insbesondere C# nähert sich nur in Halbtonschritten der Ausgereiftheit.

Einige Besonderheiten

Wir wollen uns abschließend noch mit einer Kuriosität 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.


Galileo Computing

2.4.3 Unäres Minus und Plusdowntop

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. Das unäre Plus ist eigentlich unnötig; die Entwickler haben es jedoch aus Symmetriegründen mit eingeführt.

Minus und Plus sitzen direkt vor dem Operanden, und der Compiler weiß selbstständig, ob dies unär oder binär ist. Er hat es leicht, wenn typische Ausdrücke wie

a = -2;

geschrieben werden.


Beispiel Der Compiler erkennt auch folgende Konstruktion:
int i = - - - 2 + - + 3;
Dies ergibt den Wert -5. Achtung! Der Compiler erkennt einen Ausdruck wie ---2+-+3 nicht an, da die zusammenhängenden Minuszeichen als Inkrement erkannt werden und nicht als unärer Operator.


Galileo Computing

2.4.4 Arithmetische Operatorendowntop

Ein arithmetischer Operator verknüpft die Operanden mit den Operatoren Addition (+), Subtraktion (-), Multiplikation (*) und Division (/). Zusätzlich gibt es einen Modulo-Operator (auch Restwertoperator genannt), der den bei der Division verbleibenden Rest betrachtet. Das Zeichen für den Modulo-Operator ist %. Er ist für ganzzahlige Werte sowie für Fließkommazahlen definiert. Die arithmetischen Operatoren sind binär, und auf der linken und rechten Seite sind die Typen numerisch. Der Ergebnistyp ist ebenfalls numerisch. Bei unterschiedlichen Datentypgrößen werden vor der Anwendung der Operation alle Operanden auf den größten vorkommenden Typ gebracht. 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 zur null hin gerundet. Schon in der Schulmathematik war die Division durch null nicht definiert. Führen wir eine Ganzzahldivision mit dem Divisor 0 durch, so bestraft uns Java mit einer ArithmeticException. Bei Fließkommazahlen verläuft dies anders. Eine Division durch 0 liefert meistens bei +/- unendlich eine NaN; außer bei 0.0/0.0. Ein NaN steht für Not-A-Number und wird vom Prozessor erzeugt, falls er eine mathematische Operation wie die Division durch null nicht durchführen kann.

Der Modulo-Operator %

Bei einer Ganzzahldivision kann es passieren, dass wir einen Rest bekommen. So geht die Division 9/2 nicht auf. Der Rest ist 1. In Java sowie in C(++) ist es der Modulo-Operator (engl. remainder operator), der uns diese Zahl liefert. Somit ist 9%2 gleich 1.

Im Gegensatz zu C(++)2 erlaubt der Modulo-Operator in Java auch Fließkommazahlen, und die Operanden können negativ sein. Die Sprachdefinition von C(++) schreibt bei der Division und beim Modulo mit negativen Zahlen keine Berechnungsmethode vor. In Java richten sich die Division und der Modulo nach einer einfachen Formel: int(a/b)*b + (a%b) = a3 4


Hinweis In Java sind Modulo (%), Inkrement (++) und Dekrement (--) für alle numerischen Datentypen erlaubt.19


Beispiel Die Gleichung ist erfüllt, wenn wir etwa a = 10 und b = 3 wählen. Es gilt: int(10/3) = 3. 10%3 ergibt 1. Dann ergeben 3 * 3 + 1 = 10.

Aus dieser Gleichung folgt, dass beim Modulo 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 Modulo-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.


Beispiel Unterschiedliche Vorzeichen beim Modulo-Operator:

Listing 2.5 ModuloDivDemo.java

class ModuloDivDemo
{
  public static void main( String args[] )
  {
    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
  }
}

Modulo für Fließkommazahlen

Über die oben genannte Formel können wir auch bei Fließkommazahlen das Ergebnis einer Modulo-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 Modulo-Operation den Rest von einer rundenden Division berechnet und nicht von einer abschneidenden Division. So wäre das Verhalten nicht analog zum Modulo bei Ganzzahlen. Java definiert das Modulo jedoch bei Fließkommazahlen genauso wie das Modulo auf Ganzzahlen. Wünschen wir ein Modulo-Verhalten wie es IEEE 754 vorschreibt, so können wir immer noch die Bibliotheksfunktion Math.IEEEremainder() verwenden.

Auch bei der Modulo-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.


Beispiel Modulo bei Fließkommazahlen:
 5.0 %  3.0 =  2.0
 5.0 % -3.0 =  2.0
-5.0 %  3.0 = -2.0
-5.0 % -3.0 = -2.0

Anwendung des Modulo-Operators

Im Folgenden wollen wir eine Anwendung des Modulo-Operators kennen lernen, da dieser häufig dafür verwendet wird, um eine einfache Überprüfung vorzunehmen. Bei einer eingegebenen Nummer wird dann einfach der Modulo-Wert zu einer fest vorgegebenen Zahl berechnet, und ist dieser zum Beispiel 0, so ist die Nummer eine gültige Codenummer. Erstaunlicherweise gibt es Firmen, die tatsächlich nach diesem einfachen Kodierungsverfahren ihre Software schützen, vorne dabei ist Microsoft mit Windows 95 und Windows NT 4.0. Nachdem die Software installiert ist, wird der Benutzer aufgefordert, einen CD-Key einzugeben. Es werden von den älteren Softwarepaketen drei unterschiedliche Schlüssellängen verwendet.

gp Normale Version mit 10 Stellen: xxx-NNNNNNN
Die ersten drei Stellen werden nicht geprüft. Die sieben letzten Ziffern müssen eine Quersumme ergeben, die durch 7 teilbar ist, also für die x%7 = 0 gilt. So ist 1234567 eine gültige Zahl, da 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28 und 28%7 = 0 ist.
gp OEM Version: xxxxx-OEM-NNNNNNN-xxxxx
Die ersten acht und die letzten fünf Stellen werden nicht geprüft. Die in der Mitte liegenden restlichen sieben Stellen werden nach dem gleichen Verfahren wie in der normalen Version geprüft.
gp Neuer CD-Schlüssel: xxxx-NNNNNNN
Die ersten vier Ziffern steuern, ob es sich um eine Vollversion (0401) oder eine Update-Version (0502) handelt. Die restlichen sieben Ziffern sind wieder Modulo 7 zu nehmen.

Das Verfahren ist mehr als einfach. Diese Hintergründe sollen natürlich nicht zum Installieren illegal erworbener Software führen. Dies verstößt selbstverständlich gegen das Urheberrecht! Diese Variante darf höchstens dann angewendet werden, wenn die Produkt-ID für die lizenzierte Software verloren ging!

Rundungsfehler

Prinzipiell sollten Anweisungen wie 1.1 - 0.1 immer 1.0 ergeben, doch interne Rundungsfehler bei der Darstellung treten 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.


Galileo Computing

2.4.5 Die relationalen Operatorendowntop

Relationale Operatoren vergleichen Ausdrücke miteinander und geben einen Wahrheitswert vom Typ boolean zurück. Ein anderes Wort für relationaler Operator ist Vergleichsoperator. Die von Java zur Verfügung gestellten Operatoren sind Größer (>), Kleiner (<), Test auf Gleichheit (==) und Ungleichheit (!=) sowie die Verbindung zu einem Größer-gleich (>=) beziehungsweise Kleiner-gleich (<=).

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.

Verwechslungsprobleme durch == und =

Die Verwendung des relationalen Operators == und der Zuweisung = führt bei Einsteigern oft zu Problemen, da die Mathematik für beide 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

Galileo Computing

2.4.6 Logische Operatorendowntop

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 Operatoren wahr oder beide falsch sind. Sind sie unterschiedlich, so ist das Ergebnis wahr.

Kurzschlussoperatoren

Eine Besonderheit sind die Kurzschlussoperatoren (engl. short-circuit-operator) && beziehungsweise || für Und und Oder. In der Regel wird ein logischer Ausdruck nur dann weiter ausgewertet, wenn er das Schlussergebnis noch beeinflussen kann. Sonst optimiert der Compiler die Programme zum Beispiel bei zwei Operanden.

gp Und: Ist einer der beiden Ausdrücke falsch, so kann der Ausdruck schon nicht mehr wahr werden. Das Ergebnis ist falsch.
gp Oder: Ist mindestens einer der Ausdrücke schon wahr, so ist auch der gesamte Ausdruck wahr.

Es ist aber in einigen Fällen gewünscht, dass alle Teilausdrücke ausgewertet werden, insbesondere wenn Funktionen Seiteneffekte bewirken. Daher führt Java zusätzliche Nicht-Kurzschlussoperatoren | und & ein, die in einem komplexen Ausdruck alle Teilausdrücke auswerten. Für das ausschließende Oder Xor (Operator ^) kann es keinen Kurzschlussoperator geben, da immer beide Operanden ausgewertet werden müssen, bevor das Ergebnis feststeht.


Beispiel In dem ersten Ausdruck wird die Methode foo() nicht aufgerufen, im zweiten schon.
b = true || foo();   // foo() wird nicht aufgerufen.
b = false & foo();   // foo() wird aufgerufen.


Galileo Computing

2.4.7 Reihenfolge und Rang der Operatoren in der Auswertungsreihenfolgedowntop

Aus der Schule ist der Spruch »Punktrechnung geht vor Strichrechnung« bekannt, sodass Ausdrücke der Art

1 + 2 * 3

zu 7 und nicht zu 9 ausgewertet werden. In den meisten Programmiersprachen gibt es eine Unzahl von Operatoren neben Plus und Mal, die alle ihre eigenen Vorrangregeln besitzen. Der Multiplikationsoperator besitzt zum Beispiel eine höhere Vorrangregel (kurz Rang) und damit eine andere Auswertungsreihenfolge als der Plus-Operator.


Beispiel Zur Umwandlung einer Temperatur von Fahrenheit in Celsius wird von dem Wert in Fahrenheit 32 abgezogen und das Ergebnis mit 5/9 multipliziert:
celsius = fahrenheit - 32 * 5 / 9;

Die erste Idee ist aber leider falsch, denn hier berechnet der Compiler 32 * 5/9. Das Ergebnis 17 wird von Fahrenheit abgezogen, was keine gültige Umrechnung ist. Richtig ist Folgendes:

celsius = ( fahrenheit - 32 ) * 5 / 9;

Die Rechenregeln für Mal vor Plus kann sich jeder noch leicht merken. 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 );

Gilt, dass entweder A && B oder C wahr sein müssen oder etwa A und B || C? Das Ergebnis fällt unterschiedlich aus. Entweder ist es true oder false.

Für derlei Feinheiten gibt es zwei Lösungen: entweder in einer Tabelle mit Vorrangregeln nachschlagen oder auf diese Ungenauigkeiten verzichten.


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 jedes Cast
*, /, % 2 arithmetisch Multiplikation, Division, Rest
+, - 3 arithmetisch Addition und Subtraktion

+

3 String String-Konkatenation

<<

4 integral Shift links

>>

4 integral Shift rechts m. Vorzeichenerweiterung

>>>

4 integral Shift rechts o. 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 alles Bedingungsoperator

=

13 jede Zuweisung

*=, /=, %=, +=, -=, <<=, >>=, >>>=, &=, ^=, |=

14 jede Zuweisung mit Operation

Tabelle 2.7 Operatoren mit Rangordnung in Java

Die Tabelle lehrt uns, dass im Beispiel A && B || C das Und stärker als das Oder bindet, also der Wert 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 »Punktrechung geht vor Strichrechnung« hinausgehen, sollten geklammert werden. Da die unären Operatoren ebenfalls sehr stark binden, kann eine Klammerung wegfallen.


Beispiel Bei den Operatoren +, * 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 gilt das nicht.
A / B / C

Der Ausdruck wird von links nach rechts ausgewertet, und zwar als (A / B) / C. Hier sind Klammern angemessen. Denn würde der Compiler den Ausdruck zu A / (B / C) auswerten, käme es einem A * C / B gleich.


Die mathematische Assoziativität gilt bei Gleitkommazahlen natürlich nicht, da diese nicht ohne Rechenfehler ablaufen. Daher gilt eine Auswertung von links nach rechts.
Galileo Computing

2.4.8 Überladenes Plus für Stringsdowntop

Obwohl sich in Java die Operatoren fast alle auf primitive Datentypen beziehen, gibt es doch eine bemerkenswerte Verwendung des Plus-Operators. Objekte vom Typ String können durch den Plus-Operator mit anderen Strings verbunden werden. Dies wurde in Java eingeführt, da ein Aneinanderhängen von Zeichenketten oft benötigt wird. Im Kapitel über die verschiedenen Klassen wird String noch etwas präziser dargestellt. Insbesondere werden die Gründe dargelegt, die zur Einführung eines String-Objekts auf der einen Seite, aber auch zur Sonderbehandlung in der Sprachdefinition auf der anderen Seite führten.

Listing 2.6 HelloName.java

// Ein Kleines ,Trallala'-Programm in Java
class HelloName
{
  public static void main( String args[] )
  {
    // Zwei Strings deklarieren
    String name,
           intro;
    // Ersetze "Ulli" durch deinen Namen
    name = "Ulli";
    // Nun die Ausgabe
    intro = "Tri Tra Trallala \"Trullala\", sage " + name;
    System.out.println( intro );
  }
}

Nachdem ein String intro aus verschiedenen Objekten zusammengesetzt wurde, läuft die Ausgabe auf dem Bildschirm über die Funktion println() ab.

Tri Tra Trallala "Trullala", sage Ulli

Die Funktion schreibt den String auf die Konsolenausgabe und setzt hinter die Zeile noch einen Zeilenvorschub. (Obwohl das \n nicht unbedingt plattformunabhängig ist, wollen wir es trotzdem nutzen.5) Das Objekt System.out definiert den Ausgabekanal.

Falls zusammenhängende Teile nicht alle den Datentyp String annehmen, werden sie automatisch in einen String umgewandelt.

"Sandmännchen wünscht " + 12

ergibt den einen String »Sandmännchen wünscht 12«.

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


Beispiel Da der Plus-Operator für Zeichenketten streng von links nach rechts geht, bringt das mit eingebetteten arithmetischen Ausdrücken mitunter Probleme. Diese müssen dann geklammert werden, wie im Folgenden zu sehen:
"Ist 1 größer als 2? " + (1 > 2 ? "nein" : "ja");

Wäre der Ausdruck um den Bedingungsoperator nicht geklammert, dann würde der Plus-Operator den Ausdruck 1>2 auswerten, in einen String umwandeln und diesen an die erste Zeichenkette anhängen. Jetzt käme das Fragezeichen. Dies ist aber nur für boolesche Werte erlaubt, aber links stände die Zeichenkette. Das wäre ein Fehler.


Galileo Computing

2.4.9 Was C(++)-Programmierer vermissen könntentoptop

Da es in Java keine Pointer-Operationen gibt, existiert das Operatorzeichen zur Referenzierung (&) und Dereferenzierung (*) nicht. Ebenso ist ein sizeof unnötig, da das Laufzeitsystem und der Compiler immer die Größe von Klassen kennen beziehungsweise die primitiven Datentypen immer eine feste Länge haben. Eine abgeschwächte Version vom Kommaoperator ist in Java nur im Kopf von for-Schleifen erlaubt.






1 Es gibt Programmiersprachen, in denen werden Wertoperationen besonders gekennzeichnet. So etwa in LOGO. Eine Wertoperation schreibt sich mit einem Doppelpunkt vor der Variablen, etwa :X + :Y.

2 Wir müssten in C(++) die Funktion fmod() benutzen.

3 In C sind sie nur für Ganzzahlen definiert.

4 Es gibt Programmiersprachen, wie APL, die keine Vorrangregeln kennen. Sie werten die Ausdrücke streng von rechts nach links oder umgekehrt aus.

5 Besser ist es, das Absatzendezeichen aus der Systemeigenschaft zu nehmen. Dazu dient die Anweisung System.getProperty("line.separator"), die einen String liefert.





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