8 Typwandlungen Hier geben wir Ihnen weitere Informationen zu Goto Java 2, die Sie ergänzend zu Hausaufgabe 3 lesen sollten. Sie sollten für die weiteren Ausführungen zunächst in Go To Java Kapitel 4.6 über “Typkonvertierunen” lesen. Dort wird besprochen, welche Typkonvertierungen der Java–Compiler automatisch vornehmen kann — leider werden keine Beispiele für Typkonvertierungen gebracht, und es wird auch nicht erwähnt, warum Typkonvertierungen wichtig sind. 8.1 Automatische Typkonvertierungen Im vorigen Kapitel haben Sie gelernt, daß jeder Ausdruck in einem Java–Programm einen Datentyp hat. Manchmal ist es notwendig, einen Ausdruck eines gewissen Datentyps in einen anderen Datentyp umzuwandeln. Teilweise werden diese Umwandlungen durch den Java–Compiler automatisch vorgenommen, teilweise muß man sie explizit befehlen. Warum Typwandlungen überhaupt nötig sind, soll an einem Beispiel demonstriert werden. Es seien die folgenden Variablen definiert: int i1 = 5; int i2 = 3; float f1 = 3.123f; float f2 = 2.723f; double d1 = 3.1234567890123456; Jede Rechnung mit diesen verschiedenen Variablen muß irgendwie vom Prozessor ausgeführt werden. Besprechen wir das an den Beispielen der Addition und der Multiplikation. Übliche Prozessoren enthalten Funktionen, mit denen zwei Integer addiert oder multipliziert werden können. Sie haben auch Funktionen, mit denen zwei Float oder zwei sonstige Datentypen addiert oder multipliziert werden können. Das Ergebnis dieser Funktionen hat dann normalerweise den gleichen Datentyp wie die Operanden der Rechnung. Beispiel: Wenn Sie mit zwei int–Variablen rechnen, kann der Prozessor die Rechnung direkt vornehmen. Das Ergebnis ist dann immer wieder ein Integer (überlegen Sie sich, warum das Ergebnis der Division nicht der Erwartung entspricht !) i1 + i2; // Ergebnis: int, 8 i1 / i2; // Ergebnis: int, 1 i1 * i2; // Ergebnis: int, 25 Genauso ist das Ergebnis beim Rechnen mit float–Variablen immer wieder ein Float: f1 + f2; // Ergebnis: float, 5.846 f1 / f2; // Ergebnis: float, 1.1468968... f1 * f2; // Erbebnis: float, 8.503929 60 Normalerweise enthalten Prozessoren keine Funktionen, die gleichzeitig mit verschiedenen Datentypen rechnen können. Will man also mit zwei Werten verschiedener Datentypen miteinander verrechnen, müssen erst beide Datentypen in einen gemeinsamen Datentyp konvertiert werden. Danach kann die Rechnung in diesem Datentyp stattfinden. Beispiel: Prozessoren sind meist nicht dazu in der Lage, einen IntegerWert und einen Float oder einen Float und einen Double zu addieren. Wenn Sie die Rechnung i1 + f1; durchführen wollen, muß sie also vorher noch aufbereitet werden. Der Prozessor kann entweder zwei Integer oder zwei Float addieren. Es bieten sich daher zwei Möglichkeiten an, um die Rechnung mit den Funktionen des Prozessors durchzuführen: • Wandlung der Float-Variable zu einem Integer und Durchführung der Addition als Integer-Addition: i1 + (int)f1; Dabei bedeutet der Ausdruck (int)f1: Wandele f1 in einen Integer– Wert um. In der Regel geschieht eine solche Umwandlung durch Wegwerfen aller Nachkommastellen. Mit den oben angegebenen Werten würde diese Rechnung wie folgt ausgeführt: i1 + (int)f1 == 5 + (int)3.123 == 5 + 3 == 8 • Wandlung des Integers zu einem Float und Durchführung der Addition als Float–Addition: (float)i1 + f1; Hier bedeutet der Ausdruck (float)i1: Wandle den Integer i1 in einen Float–Wert um. Dies geschieht ohne Verlust von Information. Mit den oben angegebenen Werten würde diese Rechnung so ausgeführt: (float)i1 + f1 == (float)5 + 3.123 == 5.0 + 3.123 == 8.123 Die Antwort, welche der beiden Möglichkeiten gewählt wird, fällt leicht: Wenn wir den Float–Wert zu einem Integer wandeln, verschenken wir Rechengenauigkeit und kommen zu falschen Ergebnissen. Wenn wir den Integer hingegen in einen Float wandeln, erhalten wir das erwartete Ergebnis. Bei Typwandlungen, die der Compiler automatisch vornimmt, wird immer darauf geachtet, keine Genauigkeit zu zerstören. Datentypen werden in Rechnungen darum nur zu genaueren Datentypen umgewandelt. Abbildung 4.1 im Kapitel 4.6 von “Go To Java 2” zeigt genau dies — ein short, der ja nur die ganzen Zahlen von −21 5 bis 21 5 − 1 darstellen kann, kann automatisch in die umfangreicheren Datentypen int, long oder float umgewandelt werden. Hingegen wird der Datentyp double niemals automatisch in einen anderen Datentyp gewandelt, da dies der genaueste und umfangreichste Datentyp der Sprache Java ist. 61 Typkonvertierungen treten immer auf, wenn ein Ausdruck eines gewissen Typs erwartet wird, aber ein anderer Typ vorliegt. Es kommt des öfteren vor, daß in einem Ausdruck ein niedriger Genauigkeit benötigt wird, der zu benutzende Wert aber eine hohe Genauigkeit hat. In solch einem Fall würde eine Typwandlung Genauigkeit verschenken. Der Java–Compiler würde daher statt eine automatische Typwandlung vorzunehmen, das Programm als fehlerhaft bemäkeln. Ein gutes Beispiel für Fälle, in denen eine Typkonvertierung zu ungenaueren Typen nötig ist, bieten die Zuweisungsoperatoren. Der einer Variable zugewiesene Wert muß ja immer den Datentyp der Variable haben. Wird nun einer Variable eines “ungenauen” Datentyps ein Wert eines genaueren Datentyps zugewiesen, so erzeugt das einen Kompilationsfehler. Beispiel: Zum Beispiel sind folgende Anweisungen erlaubt, da eine Wandlung zu einem genaueren Typ stattfindet: f1 = i1; d1 = i1; d1 = f1; // bei Zuweisung: autom. Wandlung i1 zu float // bei Zuweisung: autom. Wandlung i1 zu double // bei Zuweisung: autom. Wandlung f1 zu double Alle oben genannten Zuweisungen sind erlaubt, da hier bei den automatischen Typwandlungen keine Genauigkeit verlorengeht. Hingegen sind die folgenden Anweisungen nicht erlaubt: i1 = f1; i1 = d1; f1 = d1; // verboten, da float genauer als int // verboten, da double genauer als int // verboten, da double genauer als float In jedem dieser Fälle würde Genauigkeit verlorengehen, und darum würde der Compiler mit einer Fehlermeldung gegen jede der Anweisungen protestieren. Die Fehlermeldung könnte wie folgt aussehen: Programmname.java:11: Incompatible type for =. Explicit cast needed to convert float to int. i1 = f1; Beispiel: Sehr häufig kommen derartige Fehlermeldungen in Programmen vor, in denen man Float–Variablen einen Wert zuweist. Sie haben ja im vorigen Kapitel gelernt, daß reellwertige Zahlenliterale den Datentyp double erhalten, wenn sie nicht das Anhängsel “f” haben. Die Zuweisung eines double–Literals an eine float–Variable bedingt einen Genauigkeitsverlust und führt daher zu einem Compiler–Fehler: 01 02 03 04 05 06 07 08 09 10 11 12 int i; i = 1; i = 1L; i = 3.141; float f; f = 1; f = 3.141f; f = 3.141; double d; d = 1l; d = 3.141f; d = 3.141; // erlaubt: Zuweisung int-Literal an int // Fehler: long-Literal, aber int erwartet // Fehler: Zuweisung double-Literal an int // erlaubt: Zuweisung int-Literal an float // erlaubt: Zuweisung float-Literal an float // Fehler: double-Literal, doch float nötig // erlaubt: Zuweisung long-Literal an float; // erlaubt: Zuweisung float-Literal an double // erlaubt: Zuweisung double-Literal an double 62 8.2 Manuelle Typkonvertierungen Oft will man trotzdem Werte höherer Genauigkeit an Stellen verwenden, an denen eine niedrigere Genauigkeit benötigt wird. Wieder ist die Zuweisung eines Wertes an eine Variable das beste Beispiel für derartige Fälle. Wenn wir beispielsweise wissen, daß in einer float–Variable ein Wert ohne Nachkommastellen enthalten ist, sollten wir in der Lage sein, diesen als Integer aufzufassen. Oftmals brauchen wir die höhere Genauigkeit auch nicht wirklich. Es ist zum Beispiel üblich, die Zwischenschritte einer Rechnung in einer hohen Genauigkeit durchzuführen und dann das Endergebnis der Rechnung nur in einer niedrigen Genauigkeit zu verwenden. Java nimmt ohne unser Zutun solche Typwandlungen nicht vor. Wir können Java aber befehlen, eine Typwandlung vorzunehmen. Wir verwenden dazu einen Typwandlungsoperator und signalisieren Java damit: An dieser Stelle ist es in Ordnung, Genauigkeit zu verschenken, wir wissen schon was wir tun. Definition: Type–cast Eine manuelle Typwandlung nennt man type–cast. Um einen Wert explizit in anderen Datentyp zu wandeln, schreibt man den Datentyp, zu dem der Wert gewandelt werden soll, in runde Klammern und den Wert dahinter: (datentyp) wert Dies ist die Anweisung an den Compiler eine Typwandlung vorzunehmen. Beispiel: Folgende Zuweisungen sind erlaubt, obwohl bei der Wandlung Genauigkeit verloren geht: i1 = (int)f1; i2 = (int)d1; f1 = (float)d1; Mit den oben definierten Werten für die einzelnen Variablen ergeben sich hier die folgenden Werte für i1 bis f1: i1 == (int)f1 == (int)3.123 == 3 i2 == (int)d1 == (int)3.1982 == 3 f1 == (float)d1 == (float)3.1234567890123456 == 3.1234567 Ein Wert niedrigerer Genauigkeit ergibt sich dabei meist aus einem Wert höherer Genauigkeit durch wegwerfen von Nachkommastellen. Sie können bei jedem Ausdruck eine Typwandlung vornehmen. Sie dürfen Typwandlungen auch an Stellen vornehmen, an denen sie überflüssig sind. Beispiel: Die folgenden Type–Casts sind zwar erlaubt aber überflüssig, da Java hier einen automatischen Type–Cast vornehmen würde¿ 63 Type–cast f1 = (float)i1; d1 = (double)f1; Allerdings sind Typwandlungen nur dort gestattet, wo sie Sinn machen. Beispiel: Beispielsweise kann man Zahlen nur untereinander umwandeln, wobei der Datentyp char auch als Zahl zählt. Folgender Programmtext führt zu einem Fehler: int i1 = (int)"Ein Text"; // Fehler Eine wichtige Besonderheit ist bei Typwandlungen noch zu erwähnen: Die einzelnen primitiven Datentypen sind nicht nur unterschiedlich genau. Auch der Wertebereich unterscheidet sich. Auf diese Problematik wollen wir hier aber nicht weiter eingehen — hier nur ein Beispiel dazu: Beispiel: Wenn Sie einen zu großen Double–Wert in einen Float wandeln, wird der Wert als Infinity dargestellt: double d = 1.23e300; // ist nicht als float darstellbar float f = (float) d; // jetzt enthält f den Wert Infinity 8.3 Zusammenfassung Sie müssten nun die folgenden Fragen beantworten können: ⊲ Warum sind Typkonvertierungen nötig ? ⊲ Welche Arten von Typkonvertierungen führt Java automatisch durch ? ⊲ Geben Sie einige Beispiele, in denen Typkonvertierungen automatisch durchgeführt werden ! ⊲ Warum führt Java manche Typkonvertierungen nicht automatisch durch ? ⊲ Geben Sie einige Beispiele, in denen statt einer automatischen Typkonvertierung ein Fehler ausgegeben wird ! ⊲ Wie kann man Java dazu zwingen, einen Wert in einen anderen Typ zu wandeln ? ⊲ Nennen Sie einige Beispiele für überflüssige Typwandlungen ! 64