Galileo Computing < openbook >
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel (2. Aufl.) von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Java ist auch eine Insel (2. Auflage)
gp Kapitel 2 Sprachbeschreibung
  gp 2.1 Anweisungen und Programme
  gp 2.2 Programme
    gp 2.2.1 Kommentare
    gp 2.2.2 Funktionsaufrufe als Anweisungen
    gp 2.2.3 Die leere Anweisung
    gp 2.2.4 Der Block
  gp 2.3 Elemente einer Programmiersprache
    gp 2.3.1 Textkodierung durch Unicode-Zeichen
    gp 2.3.2 Unicode-Tabellen unter Windows
    gp 2.3.3 Bezeichner
    gp 2.3.4 Reservierte Schlüsselwörter
    gp 2.3.5 Token
    gp 2.3.6 Semantik
  gp 2.4 Datentypen
    gp 2.4.1 Primitive Datentypen
    gp 2.4.2 Wahrheitswerte
    gp 2.4.3 Variablendeklarationen
    gp 2.4.4 Ganzzahlige Datentypen
    gp 2.4.5 Die Fließkommazahlen
    gp 2.4.6 Zeichen
    gp 2.4.7 Die Typanpassung (das Casting)
    gp 2.4.8 Lokale Variablen, Blöcke und Sichtbarkeit
    gp 2.4.9 Initialisierung von lokalen Variablen
  gp 2.5 Ausdrücke
    gp 2.5.1 Zuweisungsoperator und Verbundoperator
    gp 2.5.2 Präfix- oder Postfix-Inkrement und -Dekrement
    gp 2.5.3 Unäres Minus und Plus
    gp 2.5.4 Arithmetische Operatoren
    gp 2.5.5 Die relationalen Operatoren
    gp 2.5.6 Logische Operatoren
    gp 2.5.7 Reihenfolge und Rang der Operatoren in der Auswertungsreihenfolge
    gp 2.5.8 Was C(++)-Programmierer vermissen könnten
  gp 2.6 Bedingte Anweisungen oder Fallunterscheidungen
    gp 2.6.1 Die if-Anweisung
    gp 2.6.2 Die Alternative wählen mit einer if/else-Anweisung
    gp 2.6.3 Die switch-Anweisung bietet die Alternative
  gp 2.7 Schleifen
    gp 2.7.1 Die while-Schleife
    gp 2.7.2 Schleifenbedingungen und Vergleiche mit ==
    gp 2.7.3 Die do/while-Schleife
    gp 2.7.4 Die for-Schleife
    gp 2.7.5 Ausbruch planen mit break und Wiedereinstieg mit continue
    gp 2.7.6 break und continue mit Sprungmarken
  gp 2.8 Methoden einer Klasse
    gp 2.8.1 Bestandteil einer Funktion
    gp 2.8.2 Aufruf
    gp 2.8.3 Methoden ohne Parameter
    gp 2.8.4 Statische Methoden (Klassenmethoden)
    gp 2.8.5 Parameter und Wertübergabe
    gp 2.8.6 Methoden vorzeitig mit return beenden
    gp 2.8.7 Nicht erreichbarer Quellcode bei Funktionen
    gp 2.8.8 Rückgabewerte
    gp 2.8.9 Methoden überladen
    gp 2.8.10 Vorinitialisierte Parameter bei Funktionen
    gp 2.8.11 Finale lokale Variablen
    gp 2.8.12 Finale Referenzen in Objekten und das fehlende const
    gp 2.8.13 Rekursive Funktionen
    gp 2.8.14 Die Ackermann-Funktion
    gp 2.8.15 Die Türme von Hanoi
  gp 2.9 Weitere Operatoren
    gp 2.9.1 Bitoperationen
    gp 2.9.2 Vorzeichenlose Bytes in ein Integer und Char konvertieren
    gp 2.9.3 Variablen mit Xor vertauschen
    gp 2.9.4 Die Verschiebeoperatoren
    gp 2.9.5 Setzen, Löschen, Umdrehen und Testen von Bits
    gp 2.9.6 Der Bedingungsoperator
    gp 2.9.7 Überladenes Plus für Strings
  gp 2.10 Einfache Benutzereingaben


Galileo Computing

2.5 Ausdrücke  downtop

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 und Operatoren. 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. 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 ist eine besondere Form der Anweisung, denn ein Ausdruck ergibt bei der Auswertung ein Ergebnis (auch Resultat genannt). Dieses ist oft ein

gp  numerischer Typ (von arithmetischen Ausdrücken) oder ein
gp  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 wiedererkennen.


Galileo Computing

2.5.1 Zuweisungsoperator und Verbundoperator  downtop

Der Zuweisungsoperator kopiert den Wert eines Ausdrucks der rechten Seite in die Variable der linken Seite. In Java wird eine Zuweisung mit dem Gleichheitszeichen = dargestellt.

int a;
a = 12*3;

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 == '-');

Schön zu sehen an dem Beispiel ist die Auswertungsreihenfolge. 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 auf die 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.5.2 Präfix- oder Postfix-Inkrement und -Dekrement  downtop

Erhöhen/Erniedrigen von Variablen ist eine sehr häufige Operation, wofür die Entwickler in der Vorgängersprache C auch einen Operator spendiert haben. 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 sind glücklicherweise Ausdrücke, sodass sie einen Wert liefern. 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. 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.)

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.5.3 Unäres Minus und Plus  downtop

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; dies haben die Entwickler jedoch aus Symmetriegründen mit eingeführt.

Minus und Plus sitzen direkt vor dem Operator 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.5.4 Arithmetische Operatoren  downtop

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 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 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(++)   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 richtet sich die Division und der Modulo nach einer einfachen Formel: int(a/b)*b + (a%b) = a

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

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, falls 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.

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, denn dieser wird häufig dafür verwendet, 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. Die 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 Divisions-Algorithmus 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 errechnet 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.5.5 Die relationalen Operatoren  downtop

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.5.6 Logische Operatoren  downtop

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.5.7 Reihenfolge und Rang der Operatoren in der Auswertungsreihenfolge  downtop

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 eigene 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 dieserlei Feinheiten gibt es zwei Lösungen. Entweder in einer Tabelle mit Vorrangregeln nachschlagen oder auf diese Ungenauigkeiten verzichten.

Tabelle 2.7   Operatoren mit Rangordnung in Java
Operator Rang Typ Beschreibung
++, -- 1 arithmetisch Inkrement u. Dekrement
+, - 1 arithmetisch Unäres Plus u. 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

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

Tipp   Alle Ausrü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.5.8 Was C(++)-Programmierer vermissen könnten  toptop

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, da 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 Vorrangsregeln kennen. Sie werten die Ausdrücke streng von rechts nach links oder umgekehrt aus.





Copyright © Galileo Press GmbH 2003
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