Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger. 
Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Eigene Klassen schreiben
6 Objektorientierte Beziehungsfragen
7 Ausnahmen müssen sein
8 Äußere.innere Klassen
9 Besondere Typen der Java SE
10 Generics<T>
11 Lambda-Ausdrücke und funktionale Programmierung
12 Architektur, Design und angewandte Objektorientierung
13 Komponenten, JavaBeans und Module
14 Die Klassenbibliothek
15 Einführung in die nebenläufige Programmierung
16 Einführung in Datenstrukturen und Algorithmen
17 Einführung in grafische Oberflächen
18 Einführung in Dateien und Datenströme
19 Einführung ins Datenbankmanagement mit JDBC
20 Einführung in <XML>
21 Testen mit JUnit
22 Bits und Bytes und Mathematisches
23 Die Werkzeuge des JDK
A Java SE-Paketübersicht
Stichwortverzeichnis


Download:

- Beispielprogramme, ca. 35,4 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 Erste Idee der 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 Konsoleneingaben
Pfeil 2.3.4 Fließkommazahlen mit den Datentypen float und double
Pfeil 2.3.5 Ganzzahlige Datentypen
Pfeil 2.3.6 Wahrheitswerte
Pfeil 2.3.7 Unterstriche in Zahlen *
Pfeil 2.3.8 Alphanumerische Zeichen
Pfeil 2.3.9 Gute Namen, schlechte Namen
Pfeil 2.3.10 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 Zuweisung mit Operation
Pfeil 2.4.5 Präfix- oder Postfix-Inkrement und -Dekrement
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.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 Ausbruch planen mit break und Wiedereinstieg mit continue
Pfeil 2.6.6 break und continue mit Marken *
Pfeil 2.7 Methoden einer Klasse
Pfeil 2.7.1 Bestandteil 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 Sichtbarkeit und Gültigkeitsbereich
Pfeil 2.7.12 Vorgegebener Wert für nicht aufgeführte Argumente *
Pfeil 2.7.13 Finale lokale Variablen
Pfeil 2.7.14 Rekursive Methoden *
Pfeil 2.7.15 Die Türme von Hanoi *
Pfeil 2.8 Zum Weiterlesen
 

Zum Seitenanfang

2.4Ausdrü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 eine Variable oder ein Literal. 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 Operand ist ein unärer Operator, da er für genau den folgenden Operanden gilt.

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

  • Es gibt auch einen 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.1Zuweisungsoperator 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.

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.

Sprachenvergleich

Das einfache Gleichheitszeichen = dient in Java nur der Zuweisung. In anderen Programmiersprachen wird die Zuweisung durch ein anderes Symbol deutlich gemacht, etwa wie in Pascal mit :=. Um Zuweisungen von Vergleichen trennen zu können, definiert Java hier, der C(++)-Tradition folgend, einen binären Vergleichsoperator ==. 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

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
 

Zum Seitenanfang

2.4.2Arithmetische 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 ](http://docs.oracle.com/javase/specs/jls/se8/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 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, die, wenn sie nicht behandelt wird, zum Ende des Programmablaufs führt. 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 22, »Bits und Bytes und Mathematisches«). Ein 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. )

System.out.println( 9 % 2 ); // 1

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

System.out.println( 12.0 % 2.5 ); // 2.0

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; er ist nur dann positiv, wenn der Dividend positiv ist. Es ist leicht einzusehen, dass das Ergebnis der Restwert-Operation immer echt kleiner ist als der Wert des Divisors. Wir haben den gleichen Fall wie bei der Ganzzahldivision, dass ein Divisor mit dem Wert 0 eine ArithmeticException auslöst und bei Fließkommazahlen zum Ergebnis NaN führt.

Listing 2.11RemainderAndDivDemo.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 22, »Bits und Bytes und Mathematisches«, 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 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.3Unä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. Einen Ausdruck wie ---2+-+3 erkennt der Compiler dagegen nicht an, da die zusammenhängenden Minuszeichen als Inkrement interpretiert werden und nicht als unärer Operator. Das Trennzeichen, Leerzeichen in diesem Fall, ist also bedeutend.

 

Zum Seitenanfang

2.4.4Zuweisung mit Operation 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 den Ausdruck zu a #= b ab. Dazu einige Beispiele:

Ausführliche Schreibweise

Schreibweise mit Verbundoperator

a = a + 2;

a += 2;

a = a * –1;

a *= –1;

a = a / 10;

a /= 10;

Tabelle 2.8Ausgeschriebene Variante und kurze Schreibweise mit dem Verbundoperator

Dass eine Zuweisung immer auch ein Ausdruck ist, zeigt folgendes Beispiel:

int a = 0;

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

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

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

Besondere Obacht sollten wir auf die automatische Klammerung geben. Bei einem Ausdruck wie a *= 3 + 5 gilt a = a * (3 + 5) und nicht selbstverständlich die Punkt-vor-Strich-Regelung a = a * 3 + 5.

Falls es sich bei der rechten Seite um einen komplexeren Ausdruck handelt, wird dieser nur einmal ausgewertet. Dies ist wichtig bei Methodenaufrufen, die Nebenwirkungen besitzen, also etwa Zustände wie einen Zähler verändern.

[zB]Beispiel

Wir profitieren auch bei Array-Zugriffen (siehe Abschnitt 3.8, »Arrays«) von Verbundoperationen, da die Auswertung des Index nur einmal stattfindet:

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

Leichter zu lesen ist die folgende Anweisung:

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

Zum Seitenanfang

2.4.5Prä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.9Prä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 Schreizugriff 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 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.6Die 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 Referenzeigenschaften.

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.

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 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 so genannten Yoda-Stil[ 81 ](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), etwa bei »Begun the Clone War has«. 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 so sollte diese Yoda-Schreibweise vermieden werden.

 

Zum Seitenanfang

2.4.7Logische 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.

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

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.10Verknüpfungen der logischen Operatoren Nicht, Und, Oder und XOR

Die logischen Operatoren arbeiten immer auf dem Typ boolean. In Abschnitt 22.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 weiterhin die Implikation (Wenn-dann-Verknüpfung) und die Äquivalenz. Für beide gibt es keinen eigenen Operator. Bei der Implikation ist es das Ergebnis von a || !b, und bei der logischen Äquivalenz ist es die Negation der Kontravalenz (XOR), daher auch Exklusiv-nicht-oder-Verknüpfung genannt, also ein !(a ^ b). Logische Äquivalenz herrscht demnach immer dann, wenn beide Wahrheitswerte gleich sind, also entweder a und b true oder a und b false sind. Wird die Konjunktion (also das Und) negiert, entspricht das in der digitalen Elektronik dem NAND-Gatter bzw. dem Shefferschen Strich. Es ist also definiert als !(a && b). Die Negation eines Oders, also !(a || b), entspricht der Peirce-Funktion – in der digitalen Elektronik einem NOR-Gatter. Auch dafür gibt es in Java keine eigenen Operatoren. Wem jetzt der Kopf raucht: Kein Problem, das brauchen wir alles nicht.

 

Zum Seitenanfang

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

Eine Besonderheit sind die beiden Operatoren && (Und) bzw. || (Oder). In der Regel muss ein logischer Ausdruck nur dann weiter ausgewertet werden, wenn er das Endergebnis noch beeinflussen kann. Zwei Operatoren bieten sich zur Optimierung der Ausdrücke an:

  • Und: Ist einer der beiden Ausdrücke falsch, so kann der Ausdruck schon nicht mehr wahr werden. Das Ergebnis ist falsch.

  • Oder: Ist mindestens einer der Ausdrücke schon wahr, so ist auch der gesamte Ausdruck wahr.

Der Compiler bzw. die Laufzeitumgebung kann den Programmfluss abkürzen. Daher nennen sich die beiden Operatoren auch Kurzschluss-Operatoren (engl. short-circuit operators).[ 82 ](Den Begriff verwendet die Java-Sprachdefinition nicht! Siehe dazu auch http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.23. ) Kürzt der Compiler ab, wertet er nur den ersten Ausdruck aus und den zweiten dann nicht mehr.

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.12CircuitNotCircuitOperator.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.

 

Zum Seitenanfang

2.4.9Der 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.[ 83 ](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.

In den meisten Programmiersprachen gibt es eine Unzahl von Operatoren neben Plus und Mal, die alle ihre eigenen Vorrangregeln besitzen.[ 84 ](Es gibt Programmiersprachen wie APL, die keine Vorrangregeln kennen. Sie werten die Ausdrücke streng von rechts nach links oder umgekehrt aus. ) Der Multiplikationsoperator besitzt zum Beispiel eine höhere Priorität und damit eine andere Auswertungsreihenfolge als der Plus-Operator. Die Rangordnung der Operatoren (engl. operator precedence) führt die folgende Tabelle auf, wobei der arithmetische Typ für Ganz- und Fließkommazahlen steht, 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

Addition und Subtraktion

+

3

String

String-Konkatenation

<<

4

integral

Verschiebung links

>>

4

integral

Rechtsverschiebung mit Vorzeichenerweiterung

>>>

4

integral

Rechtsverschiebung ohne Vorzeichenerweiterung

<, <=, >, >=

5

arithmetisch

numerische Vergleiche

instanceof

5

Objekt

Typvergleich

==, !=

6

primitiv

Gleich-/Ungleichheit von Werten

==, !=

6

Objekt

Gleich-/Ungleichheit von Referenzen

&

7

integral

bitweises Und

&

7

boolean

logisches Und

^

8

integral

bitweises XOR

^

8

boolean

logisches XOR

|

9

integral

bitweises Oder

|

9

boolean

logisches Oder

&&

10

boolean

logisches konditionales Und, Kurzschluss

||

11

boolean

logisches konditionales Oder, Kurzschluss

?:

12

jeder

Bedingungsoperator

=

13

jeder

Zuweisung

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

14

arithmetisch

Zuweisung mit Operation

+=

14

String

Zuweisung mit String-Konkatenation

Tabelle 2.11Operatoren mit Rangordnung in Java

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 A && B || C das Und stärker als das Oder bindet, also der Ausdruck mit der Belegung A=false, B=false, C=true zu true ausgewertet wird.

Vermutlich gibt es Programmierer, die dies wissen oder eine Tabelle mit Rangordnungen 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 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.10Die Typumwandlung (das CastingZur vorigen ÜberschriftZur nächsten Überschrift

Zwar ist Java eine getypte Sprache, aber sie ist nicht so stark getypt, 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 5, »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.12Implizite Typumwandlungen

Die Anpassung wird im Englischen widening conversion genannt, weil sie den Wertebereich automatisch erweitert.

[»]Hinweis

Obwohl von der Datentypgröße her ein char (16 Bit) zwischen byte (8 Bit) und int (32 Bit) liegt, taucht der Typ in einer rechten Spalte der oberen Tabelle nicht 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 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.

[zB]Beispiel

Umwandlung einer Fließkommazahl in eine Ganzzahl:

int n = (int) 3.1415; // n = 3
Passt der Typ eines Ausdrucks nicht, lässt er sich mit den Tasten (Strg) + (1) korrigieren.

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

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

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.13AutoConvert.java, main()

int i1 = 1, i2 = 2;

int i3 = i1 + i2;

long l1 = 1, l2 = 2;

long l3 = l1 + l2;

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

short s1 = 1, s2 = 2;

byte b1 = 1, b2 = 2;

// short s3 = s1 + s2; // inline Type mismatch: cannot convert from int to short

// byte b3 = b1 + b2; // inline Type mismatch: cannot convert from int to byte

Die auskommentierten Zeilen machen schon deutlich: Es ist nicht möglich, ohne explizite Typumwandlung zwei short- oder byte-Zahlen zu addieren. Richtig ist:

short s3 = (short)(s1 + s2);

byte b3 = (byte)(b1 + b2);

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 Cannot convert from int to byte

b = +b; // inline 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.[ 85 ](http://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.6.1)

Keine Typumwandlung zwischen einfachen Typen und Referenztypen

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

Listing 2.14TypecastPrimRef.java, main(), Teil 1

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

int i = (int) "1"; // inline 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 9.4, »Wrapper-Klassen und Autoboxing«, geht näher darauf ein):

Listing 2.15TypecastPrimRef.java, main(), Teil 2

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

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

((Integer)2).toString();

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

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 vom Plus an einem Beispiel an:

Listing 2.16PlusString.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

Nur eine Zeichenkette in doppelten Anführungszeichen ist ein String, und der Plus-Operator entfaltet seine besondere Wirkung. Ein einzelnes Zeichen in einfachen Hochkommata wird lediglich auf ein int gecastet, und Additionen sind Ganzzahl-Additionen.

System.out.println( '0' + 2 ); // 50 – ASCII Wert von '0' ist 48

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

[zB]Beispiel

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.

 

Zum Seitenanfang

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

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. Einige Programmiersprachen haben einen Potenz-Operator (etwa **), den es in Java ebenfalls nicht gibt. 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 !~.

 


Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< zurück

 

 


Copyright © Rheinwerk Verlag GmbH 2017

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