Rheinwerk Computing < openbook >


 
Inhaltsverzeichnis
Materialien
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Arrays und ihre Anwendungen
5 Der Umgang mit Zeichenketten
6 Eigene Klassen schreiben
7 Objektorientierte Beziehungsfragen
8 Ausnahmen müssen sein
9 Geschachtelte Typen
10 Besondere Typen der Java SE
11 Generics<T>
12 Lambda-Ausdrücke und funktionale Programmierung
13 Architektur, Design und angewandte Objektorientierung
14 Java Platform Module System
15 Die Klassenbibliothek
16 Einführung in die nebenläufige Programmierung
17 Einführung in Datenstrukturen und Algorithmen
18 Einführung in grafische Oberflächen
19 Einführung in Dateien und Datenströme
20 Einführung ins Datenbankmanagement mit JDBC
21 Bits und Bytes, Mathematisches und Geld
22 Testen mit JUnit
23 Die Werkzeuge des JDK
A Java SE-Module und Paketübersicht
Stichwortverzeichnis


Download:

- Listings, ca. 2,7 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil 2 Imperative Sprachkonzepte
Pfeil 2.1 Elemente der Programmiersprache Java
Pfeil 2.1.1 Token
Pfeil 2.1.2 Textkodierung durch Unicode-Zeichen
Pfeil 2.1.3 Bezeichner
Pfeil 2.1.4 Literale
Pfeil 2.1.5 (Reservierte) Schlüsselwörter
Pfeil 2.1.6 Zusammenfassung der lexikalischen Analyse
Pfeil 2.1.7 Kommentare
Pfeil 2.2 Von der Klasse zur Anweisung
Pfeil 2.2.1 Was sind Anweisungen?
Pfeil 2.2.2 Klassendeklaration
Pfeil 2.2.3 Die Reise beginnt am main(String[])
Pfeil 2.2.4 Der erste Methodenaufruf: println(…)
Pfeil 2.2.5 Atomare Anweisungen und Anweisungssequenzen
Pfeil 2.2.6 Mehr zu print(…), println(…) und printf(…) für Bildschirmausgaben
Pfeil 2.2.7 Die API-Dokumentation
Pfeil 2.2.8 Ausdrücke
Pfeil 2.2.9 Ausdrucksanweisung
Pfeil 2.2.10 Erster Einblick in die Objektorientierung
Pfeil 2.2.11 Modifizierer
Pfeil 2.2.12 Gruppieren von Anweisungen mit Blöcken
Pfeil 2.3 Datentypen, Typisierung, Variablen und Zuweisungen
Pfeil 2.3.1 Primitive Datentypen im Überblick
Pfeil 2.3.2 Variablendeklarationen
Pfeil 2.3.3 Automatisches Feststellen der Typen mit var
Pfeil 2.3.4 Finale Variablen und der Modifizierer final
Pfeil 2.3.5 Konsoleneingaben
Pfeil 2.3.6 Fließkommazahlen mit den Datentypen float und double
Pfeil 2.3.7 Ganzzahlige Datentypen
Pfeil 2.3.8 Wahrheitswerte
Pfeil 2.3.9 Unterstriche in Zahlen
Pfeil 2.3.10 Alphanumerische Zeichen
Pfeil 2.3.11 Gute Namen, schlechte Namen
Pfeil 2.3.12 Keine automatische Initialisierung von lokalen Variablen
Pfeil 2.4 Ausdrücke, Operanden und Operatoren
Pfeil 2.4.1 Zuweisungsoperator
Pfeil 2.4.2 Arithmetische Operatoren
Pfeil 2.4.3 Unäres Minus und Plus
Pfeil 2.4.4 Präfix- oder Postfix-Inkrement und -Dekrement
Pfeil 2.4.5 Zuweisung mit Operation (Verbundoperator)
Pfeil 2.4.6 Die relationalen Operatoren und die Gleichheitsoperatoren
Pfeil 2.4.7 Logische Operatoren: Nicht, Und, Oder, XOR
Pfeil 2.4.8 Kurzschluss-Operatoren
Pfeil 2.4.9 Der Rang der Operatoren in der Auswertungsreihenfolge
Pfeil 2.4.10 Die Typumwandlung (das Casting)
Pfeil 2.4.11 Überladenes Plus für Strings
Pfeil 2.4.12 Operator vermisst *
Pfeil 2.5 Bedingte Anweisungen oder Fallunterscheidungen
Pfeil 2.5.1 Verzweigung mit der if-Anweisung
Pfeil 2.5.2 Die Alternative mit einer if-else-Anweisung wählen
Pfeil 2.5.3 Der Bedingungsoperator
Pfeil 2.5.4 Die switch-Anweisung bietet die Alternative
Pfeil 2.5.5 Switch-Ausdrücke
Pfeil 2.6 Immer das Gleiche mit den Schleifen
Pfeil 2.6.1 Die while-Schleife
Pfeil 2.6.2 Die do-while-Schleife
Pfeil 2.6.3 Die for-Schleife
Pfeil 2.6.4 Schleifenbedingungen und Vergleiche mit == *
Pfeil 2.6.5 Schleifenabbruch mit break und zurück zum Test mit continue
Pfeil 2.6.6 break und continue mit Marken *
Pfeil 2.7 Methoden einer Klasse
Pfeil 2.7.1 Bestandteile einer Methode
Pfeil 2.7.2 Signatur-Beschreibung in der Java-API
Pfeil 2.7.3 Aufruf einer Methode
Pfeil 2.7.4 Methoden ohne Parameter deklarieren
Pfeil 2.7.5 Statische Methoden (Klassenmethoden)
Pfeil 2.7.6 Parameter, Argument und Wertübergabe
Pfeil 2.7.7 Methoden vorzeitig mit return beenden
Pfeil 2.7.8 Nicht erreichbarer Quellcode bei Methoden *
Pfeil 2.7.9 Methoden mit Rückgaben
Pfeil 2.7.10 Methoden überladen
Pfeil 2.7.11 Gültigkeitsbereich
Pfeil 2.7.12 Vorgegebener Wert für nicht aufgeführte Argumente *
Pfeil 2.7.13 Rekursive Methoden *
Pfeil 2.7.14 Die Türme von Hanoi *
Pfeil 2.8 Zum Weiterlesen
 

Zum Seitenanfang

2.4    Ausdrücke, Operanden und Operatoren Zur vorigen ÜberschriftZur nächsten Überschrift

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. operands) und Operatoren (engl. operators). Ein Operand ist zum Beispiel eine Variable, ein Literal oder die Rückgabe eines Methodenaufrufs. Im Fall einer Variablen wird der Wert aus der Variablen ausgelesen und mit ihm die Berechnung durchgeführt.

Die Arten von Operatoren

Die Operatoren verknüpfen die Operanden. Je nach Anzahl der Operanden unterscheiden wir folgende Arten von Operatoren:

  • Ist ein Operator auf genau einem Operanden definiert, so nennt er sich unärer Operator (oder einstelliger Operator). Das Minus (negatives Vorzeichen) vor einem Operanden ist ein unärer Operator, da er für genau den folgenden Operanden gilt.

  • Die üblichen Operatoren Plus, Minus, Mal sowie Geteilt sind binäre (zweistellige) Operatoren.

  • Es gibt auch einen Fragezeichen-Operator für bedingte Ausdrücke, der dreistellig ist.

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.

 

Zum Seitenanfang

2.4.1    Zuweisungsoperator Zur vorigen ÜberschriftZur nächsten Überschrift

In Java dient das Gleichheitszeichen = der Zuweisung (engl. assignment).[ 75 ](Die Zuweisungen sehen zwar so aus wie mathematische Gleichungen, doch existiert ein wichtiger Unterschied: Die Formel a = a + 1 ist – zumindest im Dezimalsystem ohne zusätzliche Algebra – mathematisch nicht zu erfüllen, da es kein a geben kann, das a = a + 1 erfüllt. Aus Programmiersicht ist es in Ordnung, da die Variable a um eins erhöht wird. ) Der Zuweisungsoperator ist ein binärer Operator, bei dem auf der linken Seite die zu belegende Variable steht und auf der rechten Seite ein Ausdruck.

[zB]  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.[ 76 ](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.

Erst nach dem Auswerten des Ausdrucks kopiert der Zuweisungsoperator das Ergebnis in die Variable. Gibt es Laufzeitfehler, etwa durch eine Division durch null, gibt es keinen Schreibzugriff auf die Variable.

[»]  Sprachvergleich

Das einfache Gleichheitszeichen = dient in Java nur der Zuweisung. Das ist in fast allen Programmiersprachen so. Selten verwenden Programmiersprachen für die Zuweisung ein anderes Symbol, etwa Pascal := oder F# und R <-. Um Zuweisungen von Vergleichen trennen zu können, definiert Java hier, der C(++)-Tradition folgend, einen binären Vergleichsoperator == mit zwei Gleichheitszeichen. Der Vergleichsoperator liefert immer den Ergebnistyp boolean:

int baba = 1;

System.out.println( baba == 1 ); // "true": Ausdruck mit Vergleich

System.out.println( baba = 2 ); // "2": Ausdruck mit Zuweisung

Zuweisungen sind auch Ausdrücke

Zwar finden sich Zuweisungen oft als Ausdrucksanweisung wieder, doch können sie an jeder Stelle stehen, an der ein Ausdruck erlaubt ist, etwa in einem Methodenaufruf wie printXXX(…):

int a = 1;                          // Deklaration mit Initialisierung

a = 2; // Anweisung mit Zuweisung

System.out.println( a = 3 ); // Ausdruck mit Zuweisung. Liefert 3.

Mehrere Zuweisungen in einem Schritt

Zuweisungen der Form a = b = c = 0; sind erlaubt und gleichbedeutend mit den drei Anweisungen c = 0; b = c; a = b;. Die explizite Klammerung a = (b = (c = 0)) macht noch einmal deutlich, dass sich Zuweisungen verschachteln lassen und Zuweisungen wie c = 0 Ausdrücke sind, die einen Wert liefern. Doch auch dann, wenn wir meinen, dass

a = (b = c + d) + e;

eine coole Vereinfachung im Vergleich zu

b = c + d;

a = b + e;

ist, sollten wir mit einer Zuweisung pro Zeile auskommen.

Die Reihenfolge der Auswertung zeigt anschaulich folgendes Beispiel:

int b = 10;

System.out.println( (b = 20) * b ); // 400

System.out.println( b ); // 20

Im Produktivcode sollte so etwas dennoch nicht stehen.

 

Zum Seitenanfang

2.4.2    Arithmetische Operatoren Zur vorigen ÜberschriftZur nächsten Überschrift

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. 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 in ein 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 in int konvertiert werden.[ 77 ](https://docs.oracle.com/javase/specs/jls/se11/html/jls-5.html#jls-5.6.1) (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 mindestens 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

Strafe bei Division durch null

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. Wird die ArithmeticException nicht behandelt, führt das zum Ende des ausführenden Threads, und wenn die ungeprüfte Ausnahme im main-Thread stattfindet und nicht abgefangen wird, führt dies zum Ende des Programms. Bei Fließkommazahlen liefert eine Division durch 0 keine Ausnahme, sondern +/– unendlich und bei 0.0/0.0 den Sonderwert NaN (mehr dazu folgt in Kapitel 21, »Bits und Bytes, Mathematisches und Geld«). NaN steht für Not a Number (auch manchmal »Unzahl« genannt) und wird vom Prozessor erzeugt, falls er eine mathematische Operation wie die Division durch null nicht durchführen kann.

[»]  Anekdote

Auf dem 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 von 9/2. In diesem Fall gibt es den Rest 1. Diesen Rest liefert der Restwert-Operator (engl. remainder operator), oft auch Modulo genannt.[ 78 ](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. ) Die Operanden können auch negativ sein.

[zB]  Beispiel
System.out.println( 9 % 2 );            // 1

Die Division und der Restwert richten sich in Java nach einer einfachen Formel: (int)(a/b) × b + (a%b) = a.

[zB]  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; das Ergebnis 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.12    src/main/java/RemainderAndDivDemo.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). In Kapitel 21, »Bits und Bytes, Mathematisches und Geld«, werden wir eine Methode floorMod(…) kennenlernen, die etwas anders arbeitet.

[»]  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

Der Restwert-Operator ist auch auf Fließkommazahlen anwendbar, und die Operanden können wiederum negativ sein.

[zB]  Beispiel

Teste, ob eine double-Zahl doch eine Ganzzahl ist: (d % 1) == 0.

Wem das zu verrückt ist, der nutzt alternativ d == Math.rint(d).

Restwert für Fließkommazahlen und Math.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 IEEE 754 es vorschreibt, so können wir die statische Bibliotheksmethode Math.IEEEremainder(…)[ 79 ](Es gibt auch Methoden, die nicht mit Kleinbuchstaben beginnen, wobei das sehr selten ist und nur in Sonderfällen auftritt. ieeeRemainder() sah für die Autoren nicht nett aus. ) 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 vorkommen, 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 nahm Intel die CPUs zurück, verlor über 400 Millionen US-Dollar und zog spät den Kopf gerade noch aus der Schlinge.

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.

 

Zum Seitenanfang

2.4.3    Unäres Minus und Plus Zur vorigen ÜberschriftZur nächsten Überschrift

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 Wert ein positiver.

[zB]  Beispiel

Drehe das Vorzeichen einer Zahl um: a = -a;

Eine Alternative ist: a = –1 * a;

Das unäre Plus ist eigentlich unnötig; die Entwickler haben es jedoch aus Symmetriegründen mit eingeführt.

[zB]  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. Es ist leichter, den Compiler zu verstehen, wenn wir die Operatorrangfolge einbeziehen und gedanklich Klammen setzen: -(-(-2)) + (-(+3)). Einen Ausdruck wie ‐--2+-+3 erkennt der Compiler dagegen nicht an, da die zusammenhängenden Minuszeichen als Dekrement interpretiert werden und nicht als unärer Operator. Das Trennzeichen, Leerzeichen in diesem Fall, ist also bedeutend.

 

Zum Seitenanfang

2.4.4    Präfix- oder Postfix-Inkrement und -Dekrement Zur vorigen ÜberschriftZur nächsten Überschrift

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 somit zwei Aufgaben: Neben der Wertrückgabe gibt es eine Veränderung der Variablen.

[»]  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 ist C++ auch nur C, und der Vorteil kommt später. Einer der Entwickler von Java, Bill Joy, beschrieb einmal Java als C++-- . Er meinte damit C++ ohne die schwer zu pflegenden Eigenschaften.

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: Er kann vor der Variablen stehen, 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 ändert sie nach der Auswertung des Ausdrucks. Mit anderen Worten: Nutzen wir einen Präfix-Operator, so wird die Variable erst herauf- bzw. heruntergesetzt und dann der Wert geliefert.

[zB]  Beispiel

Präfix/Postfix in einer Ausgabeanweisung:

Präfix-Inkrement und -Dekrement

Postfix-Inkrement und -Dekrement

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

Tabelle 2.8    Präfix- und Postfix-Inkrement und -Dekrement

[»]  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:

int i = 1;

i = ++i;

System.out.println( i ); // 2

int j = 1;

j = j++;

System.out.println( j ); // 1

Der erste Fall überrascht nicht, denn i = ++i erhöht den Wert 1 um 1, und anschließend wird 2 der Variablen i zugewiesen. Bei j ist es raffinierter: Der Wert von j ist 1, und dieser Wert wird intern vermerkt. Anschließend erhöht j++ die Variable um 1. Doch die Zuweisung setzt j auf den gemerkten Wert, der 1 war. Also ist j = 1.

[»]  Sprachvergleich: Sequenzpunkte in C(++) *

Je mehr Freiheiten ein Compiler hat, desto ungenierter kann er optimieren. Besonders Schreibzugriffe interessieren einen Compiler, denn kann er diese einsparen, läuft das Programm später ein bisschen schneller. Damit das Resultat eines Compilers jedoch beherrschbar bleibt, definiert der C(++)-Standard Sequenzpunkte (engl. sequence points), an denen alle Schreibzugriffe klar zugewiesen wurden (dass der Compiler später Optimierungen vornimmt, ist eine andere Geschichte; die Sequenzpunkte gehören zum semantischen Modell, Optimierungen verändern das nicht). Das Semikolon als Abschluss von Anweisungen bildet zum Beispiel einen Sequenzpunkt. In einem Ausdruck wie i = i + 1; j = i; muss der Schreibzugriff auf i aufgelöst sein, bevor ein Lesezugriff für die Zuweisung zu j erfolgt. Problematisch ist, dass es gar nicht so viele Sequenzpunkte gibt und es passieren kann, dass zwischen zwei Sequenzpunkten zwei mehrdeutige Schreibzugriffe auf die gleiche Variable stattfinden. Da das jedoch in C(++) undefiniert ist, kann sich der Compiler so verhalten, wie er will – er muss sich ja nur an den Sequenzpunkten so verhalten, wie gefordert. Problemfälle sind: (i=j) + i oder, weil ein Inkrement/Dekrement ein Lese-/Schreibzugriff ist, auch i = i++, was ja nichts anderes als i = (i = i + 1) ist. Bedauerlicherweise bildet die Zuweisung keinen Sequenzpunkt. Bei Zuweisungen der Art i = ++i + --i kann alles Mögliche später in i stehen, je nachdem, was der Compiler zu welchem Zeitpunkt ausführt. In Java sind diese Dinge von der Spezifikation klar geregelt; in C(++) ist nur geregelt, dass das Verhalten zwischen den Sequenzpunkten klar sein muss. Doch moderne Compiler erkennen konkurrierende Schreibzugriffe zwischen zwei Sequenzpunkten und mahnen sie (bei entsprechender Warnstufe) an.[ 80 ](Beim GCC-Compiler ist das der Schalter -Wsequence-point (der auch bei -Wall mitgenommen wird), siehe dazu http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html. )

 

Zum Seitenanfang

2.4.5    Zuweisung mit Operation (Verbundoperator) Zur vorigen ÜberschriftZur nächsten Überschrift

In Java lassen sich Zuweisungen mit numerischen Operatoren kombinieren. Für einen binären Operator (symbolisch # genannt) im Ausdruck a = a # (b) kürzt der Verbundoperator (engl. compound assignment operator) den Ausdruck zu a #= b ab.[ 81 ](Die Abkürzung existiert nur im Programmcode, im Bytecode gibt es keinen Verbundoperator. )

Dazu einige Beispiele:

Ausführliche Schreibweise

Schreibweise mit Verbundoperator

a = a + 2

a += 2

a = a - 10

a -= 10

a = a * –1

a *= –1

a = a / 10

a /= 10

Tabelle 2.9    Ausgeschriebene Variante und kurze Schreibweise mit dem Verbundoperator

Während das Präfix-/Postfix-Inkrement/-Dekrement nur um eins vergrößert/vermindert, erlauben die Verbundoperationen in einer relativ kompakten Schreibweise auch größere Inkremente/Dekremente, wie eben a+=2 oder a-=10.

[zB]  Beispiel

Eine Zuweisung ist auch immer ein Ausdruck:

int a = 0;

System.out.println( a ); // 0

System.out.println( a = 2 ); // 2

System.out.println( a += 1 ); // 3

System.out.println( a ); // 3

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.

[»]  Sprachspielerei: Ein neuer Operator?

Kaum bekannt ist der »Sleepy-Operator« -=-. Beispiel: i -=- i. Was passiert hier?

Einmalige Auswertung bei Array-Zugriffen *

Falls die linke Seite beim Verbundoperator ein Array-Zugriff ist (siehe Abschnitt 4.1, »Einfache Feldarbeit«), wird die Indexberechnung nur einmal vorgenommen. Dies ist wichtig beim Einsatz des Präfix-/Postfix-Operators oder von Methodenaufrufen, die Nebenwirkungen besitzen, also etwa Zustände wie einen Zähler verändern.[ 82 ](Details in der JLS unter https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.26.2)

[zB]  Beispiel

Wir profitieren bei Array-Zugriffen vom Verbundoperator, da erstens die Schreibweise kurz ist und zweitens die Auswertung des Index nur einmal stattfindet:

int[] array1 = { 10, 90, 0 };

int i = 0;

array1[++i] = array1[++i] + 10;

System.out.println( Arrays.toString( array1 ) ); // [10, 10, 0]

int[] array2 = { 0, 90, 0 };

int j = 0;

array2[++j] += 10;

System.out.println( Arrays.toString( array2 ) ); // [0, 100, 0]
 

Zum Seitenanfang

2.4.6    Die relationalen Operatoren und die Gleichheitsoperatoren Zur vorigen ÜberschriftZur nächsten Überschrift

Relationale Operatoren sind Vergleichsoperatoren, die Ausdrücke miteinander vergleichen und einen Wahrheitswert vom Typ boolean ergeben. Die von Java für numerische Vergleiche zur Verfügung gestellten Operatoren sind:

  • größer (>)

  • kleiner (<)

  • größer/gleich ()

  • kleiner/gleich ()

Außerdem gibt es einen Spezialoperator instanceof zum Testen von Typbeziehungen.

Zudem kommen zwei Vergleichsoperatoren hinzu, die Java als Gleichheitsoperatoren bezeichnet:

  • Test auf Gleichheit (==)

  • Test auf Ungleichheit (!=)

Dass Java hier einen Unterschied zwischen Gleichheitsoperatoren und Vergleichsoperatoren macht, liegt an einem etwas anderen Vorrang, der uns aber nicht weiter beschäftigen soll.

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 – eine Ausnahme ist der String, bei dem auch + erlaubt ist.

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 ); // inline image Compilerfehler

boolean result2 = ( a == b );
[zB]  Beispiel

Die Wahrheitsvariable hasSign soll dann true sein, wenn das Zeichen sign gleich dem Minus ist:

boolean hasSign = (sign == '-');

Die Auswertungsreihenfolge ist folgende: Erst wird das Ergebnis des Vergleichs berechnet, und dieser Wahrheitswert wird anschließend in hasSign kopiert.

[»]  (Anti-)Stil

Bei einem Vergleich mit == können beide Operanden vertauscht werden – wenn die beiden Seiten keine beeinflussenden Seiteneffekte produzieren, also etwa Zustände ändern. Am Ergebnis ändert sich nichts, denn der Vergleichsoperator ist kommutativ. So sind

if ( worldExpoShanghaiCostInUSD == 58000000000L )

und

if ( 58000000000L == worldExpoShanghaiCostInUSD )

semantisch gleich. Bei einem Gleichheitsvergleich zwischen Variable und Literal werden viele Entwickler mit einer Vergangenheit in der Programmiersprache C die Konstanten links und die Variable rechts setzen. Der Grund für diesen sogenannten Yoda-Stil[ 83 ](Yoda ist eine Figur aus Star Wars, die eine für uns ungewöhnliche Satzstellung nutzt. Anstatt Sätze mit Subjekt + Prädikat + Objekt (SPO) aufzubauen, nutzt Yoda die Form Objekt + Subjekt + Prädikat (OSP); Objekt und Subjekt sind umgedreht, so wie die Operanden aus dem Beispiel auch, sodass dieser Ausdruck sich so lesen würde: »Wenn 58000000000 gleich worldExpoShanghaiCostInUSD ist« statt der üblichen SPO-Lesung »wenn worldExpoShanghaiCostInUSD ist gleich 58000000000«. Im Arabischen ist diese OSP-Stellung üblich, sodass Entwickler aus dem arabischen Sprachraum diese Form eigentlich natürlich finden könnten. Wenn das mal nicht eine Studie wert ist … ) ist die Vermeidung von Fehlern. Fehlt in C ein Gleichheitszeichen, so ist if(worldExpoShanghaiCostInUSD = 58000000000L) als Zuweisung compilierbar (wenn auch mittlerweile mit einer Warnung), if(58000000000L = worldExpoShanghaiCostInUSD) aber nicht. Die erste fehlerhafte Version initialisiert eine Variable und springt immer in die if-Anweisung, da in C jeder Ausdruck (hier von der Zuweisung, die ja ein Ausdruck ist) ungleich 0 als wahr interpretiert wird. Das ist ein logischer Fehler, den die zweite Schreibweise verhindert, denn sie führt zu einem Compilerfehler. In Java ist dieser Fehlertyp nicht zu finden – es sei denn, der Variablentyp ist boolean, was sehr selten vorkommt –, und daher sollte diese Yoda-Schreibweise vermieden werden.

 

Zum Seitenanfang

2.4.7    Logische Operatoren: Nicht, Und, Oder, XOR Zur vorigen ÜberschriftZur nächsten Überschrift

Die Abarbeitung von Programmcode ist oft an Bedingungen geknüpft. Diese Bedingungen sind oftmals komplex zusammengesetzt, wobei drei Operatoren am häufigsten vorkommen:

  • Nicht (Negation): Dreht die Aussage um; aus wahr wird falsch, und aus falsch wird wahr.

  • Und (Konjunktion): Beide Aussagen müssen wahr sein, damit die Gesamtaussage wahr wird.

  • Oder (Disjunktion): Eine der beiden Aussagen muss wahr sein, damit die Gesamtaussage wahr wird.

[»]  Hinweis

Mehr als diese drei logischen Operatoren sind auch nicht nötig, um alle möglichen logischen Verknüpfungen zu realisieren. In der Mathematik wird das boolesche Algebra genannt.

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 Nicht (!), Und (&&), Oder (||) und XOR (^) an. XOR ist eine Operation, die nur dann wahr liefert, wenn genau einer der beiden Operanden true ist. Sind beide Operanden gleich (also entweder true oder false), so ist das Ergebnis false. XOR heißt auch exklusives oder ausschließendes Oder. Im Deutschen trifft die Formulierung »entweder … oder« diesen Sachverhalt gut: Entweder ist es das eine oder das andere, aber nicht beides zusammen. Beispiel: »Willst du entweder ins Kino oder DVD schauen?« a ^ b ist eine Abkürzung für (a && !b) || (!a && b).

boolean a

boolean b

! a

a && b

a || b

a ^ b

true

true

false

true

true

false

true

false

false

false

true

true

false

true

true

false

true

true

false

false

true

false

false

false

Tabelle 2.10    Verknüpfungen der logischen Operatoren Nicht, Und, Oder und XOR

Die logischen Operatoren arbeiten immer auf dem Typ boolean. In Abschnitt 21.1.1, »Die Bit-Operatoren Komplement, Und, Oder und XOR«, werden wir sehen, dass sich die gleichen Verknüpfungen auf jedem Bit einer Ganzzahl durchführen lassen.

[»]  Ausblick auf die Aussagenlogik *

Verknüpfungen dieser Art sind in der Aussagenlogik bzw. booleschen Algebra sehr wichtig. Die für uns gängigen Begriffe Und, Oder, XOR sind dort auch unter anderen Namen bekannt. Die Und-Verknüpfung nennt sich Konjunktion, die Oder-Verknüpfung Disjunktion, und das exklusive Oder heißt Kontravalenz. Die drei binären Operatoren Und, Oder, XOR decken bestimmte Verknüpfungen ab, jedoch nicht alle, die prinzipiell möglich sind. In der Aussagenlogik gibt es außerdem die Implikation (Wenn-dann-Verknüpfung) und die Äquivalenz.

 

Zum Seitenanfang

2.4.8    Kurzschluss-Operatoren Zur vorigen ÜberschriftZur nächsten Überschrift

Ein logischer Ausdruck muss nur dann weiter ausgewertet werden, wenn sich das Endergebnis noch ändern kann. Steht das Ergebnis schon vor der Auswertung aller Teile unumstößlich fest, kürzt der Compiler den Programmfluss ab. Die beiden Operatoren && (Und) bzw. || (Oder) 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.

Nehmen wir zum Beispiel true || Math.random() > 0.5; hier wird es nicht zum Aufruf der Methode kommen, denn die beiden Operatoren && und || sind Kurzschluss-Operatoren (engl. short-circuit operators).[ 84 ](Den Begriff verwendet die Java-Sprachdefinition nicht! Siehe dazu auch https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.23. ) Das Kennzeichen der Kurzschluss-Operatoren ist also eine Abkürzung, wenn das Ergebnis des Ausdrucks feststeht; die restlichen Ausdrücke werden nicht ausgewertet.

Nicht-Kurzschluss-Operatoren *

In einigen Fällen ist es erwünscht, dass die Laufzeitumgebung alle Teilausdrücke auswertet. Das kann der Fall sein, wenn Methoden 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. Das Ergebnis der Auswertung ist das Gleiche wie vorher.

Die Arbeitsweise dokumentiert das folgende Programm, bei dem gut abzulesen ist, dass eine Variable nur dann erhöht wird, wenn der Nicht-Kurzschluss-Operator auswertet:

Listing 2.13    src/main/java/CircuitNotCircuitOperator.java, main()

int a = 0, b = 0, c = 0, d = 0;

System.out.println( true || a++ == 0 ); // true, a wird nicht erhöht

System.out.println( a ); // 0

System.out.println( true | b++ == 0 ); // true, b wird erhöht

System.out.println( b ); // 1

System.out.println( false && c++ == 0 ); // false, c wird nicht erhöht

System.out.println( c ); // 0

System.out.println( false & d++ == 0 ); // false, d wird erhöht

System.out.println( d ); // 1

Für XOR kann es keinen Kurzschluss-Operator geben, da immer beide Operanden ausgewertet werden müssen, bevor das Ergebnis feststeht.

[»]  Hinweis

Unter gewissen Voraussetzungen kann der Verzicht auf den Kurzschlussoperator performanter sein, weil der Compiler Verzweigungen und Sprünge einsparen kann.

 

Zum Seitenanfang

2.4.9    Der Rang der Operatoren in der Auswertungsreihenfolge Zur vorigen ÜberschriftZur nächsten Überschrift

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.[ 85 ](Dass von diesen Rechnungen eine gewisse Spannung ausgeht, zeigen diverse Fernsehkanäle, die damit ihr Abendprogramm füllen. )

[zB]  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.

Neben Plus und Mal gibt es eine Vielzahl von Operatoren, die alle ihre eigenen Vorrangregeln besitzen.[ 86 ](Es gibt (sehr alte) 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) ist in Tabelle 2.11 aufgeführt, wobei auf den Lambda-Pfeil -> verzichtet wird. Der arithmetische Typ steht für Ganz- und Fließkommazahlen, der integrale Typ für char und Ganzzahlen und der Eintrag »primitiv« für jegliche primitiven Datentypen (also auch boolean):

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

binärer Operator für Addition und Subtraktion

+

3

String

String-Konkatenation

<<

4

integral

Verschiebung nach 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

arithmetisch

Zuweisung mit Operation

+=

14

String

Zuweisung mit String-Konkatenation

Tabelle 2.11    Operatoren mit Rangordnung in Java (Operatorrangfolge)

Die Rechenregel 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.

[zB]  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 das Und stärker als das Oder bindet, also der Ausdruck als (A && B) || C und nicht als A && (B || C) gelesen wird und somit zu true ausgewertet wird.

Vermutlich gibt es Programmierer, die dies wissen oder eine Tabelle mit Rangordnungen 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 in ihrem Fall 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 dies 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 oder Nichtdarstellbarkeit 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 –16.777.217 + 16.777.216 den Wert –1, und –1 plus +1 ist 0. Im zweiten Fall liefert –16.777.217 + (16.777.216 + 1) = –16.777.217 + 16.777.217 = 0. Doch Java wertet a + b durch die Beschränkung von float zu 0 aus, sodass bei 0 plus c die Ausgabe 1 statt 0 erscheint.

 

Zum Seitenanfang

2.4.10    Die Typumwandlung (das CastingZur vorigen ÜberschriftZur nächsten Überschrift

Java eine statisch typisierte Sprache, aber sie ist nicht so stark typisiert, 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 letzten beiden 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 Typumwandlung

In der Praxis kommt es also vor, dass Datentypen konvertiert werden müssen. Dies nennt sich Typumwandlung (engl. typecast, kurz cast). Java unterscheidet zwei Arten der Typumwandlung:

  • Automatische (implizite) Typumwandlung: 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 Typumwandlung: Ein größerer Typ kann einem kleineren Typ mit möglichem Verlust von Informationen zugewiesen werden.

Typumwandlungen 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:

Vom Typ

In den Typ

byte

short, int, long, float, double

short

int, long, float, double

char

int, long, float, double

int

long, float, double

long

float, double

float

double

Tabelle 2.12    Implizite Typumwandlungen

Grafische Darstellung der automatischen Typumwandlung

Abbildung 2.4    Grafische Darstellung der automatischen Typumwandlung

Die Anpassung wird im Englischen widening conversion genannt, weil sie den Wertebereich automatisch erweitert. Der Typ boolean taucht nicht auf; er lässt sich in keinen anderen primitiven Typ konvertieren.

[»]  Hinweis

Dass ein long auf ein double gebracht werden kann – das Gleiche gilt für int auf float –, ist wohl im Nachhinein als Fehler in der Sprache Java zu betrachten, denn es gehen Informationen verloren. Ein double kann die 64 Bit für Ganzzahlen nicht so »effizient« nutzen wie ein long. Ein Beispiel:

System.out.println( Long.MAX_VALUE ); // 9223372036854775807

double d = Long.MAX_VALUE;

System.out.printf( "%.0f%n", d ); // 9223372036854776000

System.out.println( (long) d ); // 9223372036854775807

Die implizite Typumwandlung sollte aber verlustfrei sein.

[»]  Hinweis

Obwohl von der Datentypgröße her ein char (16 Bit) zwischen byte (8 Bit) und int (32 Bit) liegt, taucht der Typ nirgends in der rechten Spalte von Tabelle 2.12 auf, da char kein Vorzeichen speichern kann, während die anderen Datentypen byte, short, int, long, float, double alle ein Vorzeichen besitzen. Daher kann so etwas wie das Folgende nicht funktionieren:

byte b = 'b';

char c = b; // inline image Type mismatch: cannot convert from byte to char

Explizite Typumwandlung

Die explizite Anpassung engt einen Typ ein, sodass diese Operation im Englischen narrowing conversion genannt wird. Der gewünschte Typ für eine Typumwandlung wird vor den umzuwandelnden Datentyp in Klammern gesetzt. Uns muss bewusst sein, dass bei jeder expliziten Typumwandlung Informationen verloren gehen können.

[zB]  Beispiel

Umwandlung einer Fließkommazahl in eine Ganzzahl, der gesamte Nachkommaanteil verschwindet:

int n = (int) 3.1415;                  // n = 3

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

[zB]  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
Passt der Typ eines Ausdrucks nicht, lässt er sich mit den Tasten (Strg)+(1) korrigieren.

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

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, schneidet also schlicht den Nachkommaanteil ab.

[zB]  Beispiel

Explizite Typumwandlung einer Fließkommazahl in ein int:

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

int r = (int)(Math.random() * 5 ); // 0 <= r <= 4

Automatische Typumwandlung bei Berechnungen mit byte und short in int *

Eine Operation vom Typ int mit int liefert den Ergebnistyp int, und long mit long liefert ein long.

Listing 2.14    src/main/java/AutoConvert.java, main()

int   int1  = 1, int2 = 2;

int int3 = int1 + int2;

long long1 = 1, long2 = 2;

long long3 = long1 + long2;

Diese Zeilen übersetzt der Compiler wie erwartet. Und so erscheint es intuitiv, dass das Gleiche auch für die Datentypen short und byte gilt. Während

short short1 = 1, short2 = 2;

byte byte1 = 1, byte2 = 2;

noch funktioniert, führt

short short3 = short1 + short2;  // inline image Type mismatch: cannot convert from int to short

byte byte3 = byte1 + byte2; // inline image Type mismatch: cannot convert from int to byte

zu einem Compilerfehler.

Es ist nicht möglich, ohne explizite Typumwandlung zwei short- oder byte-Zahlen zu addieren. Richtig ist:

short short3 = (short)( short1 + short2 );

byte byte3 = (byte)( byte1 + byte2 );

Der Grund liegt beim Java-Compiler. 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 im Beispiel arbeitet also nicht mit short- oder byte-Werten, sondern mit int-Werten; intern im Bytecode ist es ebenso realisiert. So führen also alle Ganzzahloperationen mit short und byte automatisch zum Ergebnistyp int. Und das führt bei der Zuweisung aus dem Beispiel zu einem Problem, denn steht auf der rechten Seite der Typ int und auf der linken Seite der kleinere Typ byte oder short, muss der Compiler einen Fehler melden. Mit der ausdrücklichen Typumwandlung erzwingen wir diese Konvertierung.

Dass der Compiler diese Anpassung vornimmt, müssen wir einfach akzeptieren. int und int bleibt int, long und long bleibt long. Wenn ein int mit einem long tanzt, wird der Ergebnistyp long. Arbeitet der Operator auf einem short oder byte, ist das Ergebnis automatisch int.

[+]  Tipp

Kleine Typen wie short und byte führen oft zu Problemen. Wenn sie nicht absichtlich in großen Arrays verwendet werden und Speicherplatz nicht ein absolutes Kriterium ist, erweist sich int als die beste Wahl – auch weil Java nicht durch besonders intuitive Typkonvertierungen glänzt, wie das Beispiel mit dem unären Minus und Plus zeigt:

byte b = 0;

b = -b; // inline image Cannot convert from int to byte

b = +b; // inline image 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.[ 87 ](https://docs.oracle.com/javase/specs/jls/se14/html/jls-5.html#jls-5.6)

Keine Typumwandlung zwischen einfachen Typen und Referenztypen

Allgemeine Umwandlungen zwischen einfachen Typen und Referenztypen gibt es nicht. Falsch sind zum Beispiel:

Listing 2.15    src/main/java/TypecastPrimRef.java, Ausschnitt main()

String s = (String) 1;      // inline image Cannot cast from int to String

int i = (int) "1"; // inline image Cannot cast from String to int
[»]  Getrickse mit Boxing *

Einiges sieht dagegen nach Typumwandlung aus, ist aber in Wirklichkeit eine Technik, die sich Autoboxing nennt (Abschnitt 10.5, »Wrapper-Klassen und Autoboxing«, geht näher darauf ein):

Listing 2.16    src/main/java/TypecastPrimRef.java, Ausschnitt main()

Long lông = (Long) 2L;      // Alternativ: Long lông = 2L;

System.out.println( (Boolean) true );

((Integer)2).toString();

Methoden zur Typumwandlung

Bei der explizierten Typumwandlung von Ganzzahlen schneidet Java die höherwertigen Bytes ab. Bei der Konvertierung von long (8 Byte) in ein int (4 Byte) fallen die oberen 4 Byte heraus. Sind diese vier oberen Byte 0x00 – wir betrachten nur die positiven Ganzzahlen –, dann gibt es keinen Verlust an Informationen. Wollen wir von long in int umwandeln, wobei aber ein Verlust von Informationen gemeldet werden soll, so kann die statische Math-Methode int toIntExact(long value) verwendet werden. Sie löst eine Ausnahme aus, wenn die Umwandlung einen Datenverlust bedeutet.

Bei der Umwandlung von Fließkommazahlen in Ganzzahlen kommt es immer zum Abschneiden der Nachkommastellen. Die Klasse Math hat Methoden, die auch runden können. Dazu zählen Math.round(float) und long round(double); Kapitel 21, »Bits und Bytes, Mathematisches und Geld«, gibt detaillierte Auskunft über die Mathe-Klasse.

Typumwandlung beim Verbundoperator *

Beim Verbundoperator wird noch etwas mehr gemacht, als E1 #= E2 zu E1 = (E1) # (E2) aufzulösen, wobei # symbolisch für einen binären Operator steht. 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)).

[zB]  Beispiel

Der Verbundoperator soll eine Ganzzahl zu einer Fließkommazahl addieren:

int i = 1973;

i += 30.2;

Die Anwendung des Verbundoperators ist in Ordnung, denn der Übersetzer nimmt eine implizite Typumwandlung vor, sodass die Bedeutung bei i = (int)(i + 30.2) liegt. So viel dazu, dass Java eine intuitive und einfache Programmiersprache sein soll.

 

Zum Seitenanfang

2.4.11    Überladenes Plus für Strings Zur vorigen ÜberschriftZur nächsten Überschrift

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.

[zB]  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

Die String-Konkatenation erfolgt strikt von links nach rechts und ist natürlich nicht kommutativ wie die numerische Addition. 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.

Schauen wir uns die Auswertungsreihenfolge des Plus an einem Beispiel an:

Listing 2.17    src/main/java/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

System.out.println( 1 + 2 + "3" + (4 + 5) ); // 339

System.out.println( 1 + 2 + "3" + (4 + 5) + 6 ); // 3396
[»]  Hinweis

Der Plus-Operator für Zeichenketten geht streng von links nach rechts vor 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.

char-Zeichen in der Konkatenation

Nur eine Zeichenkette in doppelten Anführungszeichen ist ein String, und der Plus-Operator entfaltet seine besondere Wirkung. Ein einzelnes Zeichen in einfachen Hochkommata konvertiert Java nach den Regeln der Typumwandlung bei Berechnungen in ein int, und Additionen sind Ganzzahl-Additionen.

System.out.println( '0' + 2 );     // 50, denn der ASCII-Wert von '0' ist 48

System.out.println( 'A' + 'a' ); // 162, denn 'A'=65, 'a'=97
 

Zum Seitenanfang

2.4.12    Operator vermisst * Zur vorigen ÜberschriftZur nächsten Überschrift

Einige Programmiersprachen haben einen Potenz-Operator (etwa **), den es in Java nicht gibt. Da es in Java keine Pointer-Operationen gibt, existieren die unter C(++) bekannten 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 bzw. die primitiven Datentypen immer eine feste Länge haben.

Skriptsprachen wie Perl oder Python bieten nicht nur einfache Datentypen, sondern definieren zum Beispiel Listen oder Assoziativspeicher. Damit sind automatisch Operatoren assoziiert, etwa um die Datenstrukturen nach Werten zu fragen oder Elemente einzufügen. Zudem erlauben viele Skriptsprachen das Prüfen von Zeichenketten gegen reguläre Ausdrücke, etwa Perl mit den Operatoren =~ und !~.

Beim Testen von Referenzen auf Identität gibt es in Java den Operator ==. Einige Programmiersprachen bieten zusätzlich einen Operator ===, sodass mit dem einen Operator ein Test auf Gleichheit und mit dem anderen ein Test auf Identität möglich ist. Wir werden uns in Kapitel 3, »Klassen und Objekte«, mit der Identität und dem ==-Operator näher beschäftigen.

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java ist auch eine Insel Java ist auch eine Insel

Jetzt Buch bestellen


 Buchempfehlungen
Zum Rheinwerk-Shop: Captain CiaoCiao erobert Java

Captain CiaoCiao erobert Java




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Objektorientierte Programmierung

Objektorientierte Programmierung




 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2021

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.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de



Cookie-Einstellungen ändern