10.5 Wrapper-Klassen und Autoboxing
Die Klassenbibliothek bietet für jeden primitiven Datentyp wie int, double, char spezielle Klassen an. Diese sogenannten Wrapper-Klassen (auch Ummantelungsklassen, Mantelklassen oder envelope classes genannt) erfüllen drei wichtige Aufgaben:
Wrapper-Klassen bieten statische Hilfsmethoden zur Konvertierung eines primitiven Datentyps in einen String (Formatierung) und vom String zurück in einen primitiven Datentyp (Parsen).
Die Datenstrukturen wie Listen und Mengen, die in Java Verwendung finden, können nur Referenzen aufnehmen. So stellt sich das Problem, wie primitive Datentypen diesen Containern hinzugefügt werden können. Wrapper-Objekte kapseln einen einfachen primitiven Wert in einem Objekt, sodass eine Referenz existiert, die etwa in einer vorgefertigten Datenstruktur gespeichert werden kann.
Der Wrapper-Typ ist wichtig bei Generics. Wenn etwa eine spezielle Function eine Ganzzahl auf eine Fließkommazahl abbildet, ist eine Implementierung mit Function<int, double> nicht korrekt – es muss Function<Integer,Double> heißen. Primitive Datentypen gibt es auch bei Generics nicht, es kommen immer die Wrapper-Typen zum Einsatz.
Es existieren Wrapper-Klassen zu allen primitiven Datentypen (siehe Tabelle 10.2).
Primitiver Typ | |
---|---|
Byte | byte |
Short | short |
Integer | int |
Long | long |
Double | double |
Float | float |
Boolean | boolean |
Character | char |
[»] Hinweis
Für void, das kein Datentyp ist, existiert die Klasse Void. Sie deklariert nur die Konstante TYPE vom Typ Class<Void> und ist für Reflection (das Auslesen von Eigenschaften einer Klasse) interessanter.
In diesem Abschnitt wollen wir uns zunächst um das Erzeugen von Wrapper-Objekten kümmern, dann um Methoden, die in allen Wrapper-Klassen vorkommen, und schließlich die individuellen Methoden der einzelnen Wrapper-Klassen vorstellen. Der Klasse Character haben wir uns schon zu Beginn von Kapitel 5, »Der Umgang mit Zeichenketten«, gewidmet, als es um Zeichen und Zeichenketten ging.
10.5.1 Wrapper-Objekte erzeugen
Wrapper-Objekte lassen sich auf drei Arten aufbauen:
über statische valueOf(…)-Methoden, denen ein primitiver Ausdruck oder ein String übergeben wird
über Boxing: Aus einem primitiven Wert erzeugt der Compiler automatisch valueOf(…)-Methodenaufrufe, die das Wrapper-Objekt liefern.
über Konstruktoren der Wrapper-Klassen. Diese Konstruktoren sind jedoch seit Java 9 deprecated.
[zB] Beispiel
Erzeuge einige Wrapper-Objekte:
Integer int1 = Integer.valueOf( "30" ); // valueOf()
Long lng1 = Long.valueOf( 0xC0B0L ); // valueOf()
Integer int2 = new Integer( 29 ); // Konstruktor (deprecated)
Long lng2 = new Long( 0xC0B0L ); // Konstruktor (deprecated)
Double dobl = new Double( 12.3 ); // Konstruktor (deprecated)
Boolean bool = true; // Boxing
Integer int3 = 42; // Boxing
Alternativen zu Wrapper-Konstruktoren
Lassen wir die veraltete Variante über den Konstruktor weg, bleiben zwei Möglichkeiten, an Wrapper-Objekte zu kommen:
Boxing ist vom Schreibaufwand her gesehen die kürzeste und im Allgemeinen die beste, weil kompakteste Variante (Boxing ist allerdings nicht ganz unproblematisch, wie Abschnitt 10.5.13, »Autoboxing: Boxing und Unboxing«, zeigt). Da Boxing auf die valueOf(…)-Methoden zugreift, sind die beiden Varianten semantisch identisch und unterscheiden sich nur im Programmcode, aber nicht im Bytecode.
Statt einem Konstruktor eine statische Methode valueOf(…)-Methode zum Erzeugen von Objekten einzusetzen ist clever, da anders als ein Konstruktor eine statische Methode Objekte nicht immer neu erzeugen muss, sondern auch auf vorkonstruierte Objekte zurückgreifen kann. Und das ist genau das, was valueOf(…) bei den vier Klassen Byte, Short, Integer und Long macht: Stammen die Ganzzahlen aus dem Wertebereich –128 bis +127, so greift valueOf(…) auf vorbereitete Objekte aus einem Cache zurück. Das Ganze klappt natürlich nur, weil Aufrufer von valueOf(…) ein unveränderliches (engl. immutable) Objekt bekommen – ein Wrapper-Objekt kann nach dem Aufbau nicht verändert werden.
[»] Hinweis
In der Wrapper-Klasse Integer gibt es drei statische überladene Methoden, getInteger (String), getInteger(String, int) und getInteger(String, Integer), die von Spracheinsteigern wegen der gleichen Rückgabe und Parameter schnell mit der valueOf(String)-Methode verwechselt werden. Allerdings lesen die getInteger(String)-Methoden eine Umgebungsvariable aus und haben somit eine völlig andere Aufgabe als valueOf(String). In der Wrapper-Klasse Boolean gibt es mit getBoolean(String) Vergleichbares. Die anderen Wrapper-Klassen haben keine Methoden zum Auslesen einer Umgebungsvariablen.
Wrapper-Objekte sind immutable
Ist ein Wrapper-Objekt erst einmal erzeugt, lässt sich der im Wrapper-Objekt gespeicherte Wert nachträglich nicht mehr verändern. Um dies auch wirklich sicherzustellen, sind die konkreten Wrapper-Klassen allesamt final. Die Wrapper-Klassen sind nur als Ummantelung und nicht als vollständiger Datentyp gedacht. Da sich der Wert nicht mehr ändern lässt (er ist ja immutable), heißen Objekte mit dieser Eigenschaft auch Werte-Objekte. Wollen wir den Inhalt eines Integer-Objekts io zum Beispiel um eins erhöhen, so müssen wir ein neues Objekt referenzieren:
Integer io = Integer.valueOf( 12 );
io = Integer.valueOf( io.intValue() + 1 );
Die Variable io referenziert zum Schluss ein zweites Integer-Objekt, und der Wert des ersten io-Objekts mit 12 bleibt unangetastet. Das entspricht im Wesentlichen auch der Nutzung von String. Heißt es String s = "Co"; s = s + "ra";, wird der Referenzvariablen s auch nur ein neues Objekt zugewiesen, aber der String an sich ändert sich nicht.
10.5.2 Konvertierungen in eine String-Repräsentation
Alle Wrapper-Klassen bieten statische toString(value)-Methoden zur Konvertierung des primitiven Elements in einen String an:
String s1 = Integer.toString( 1234567891 ),
s2 = Long.toString( 123456789123L ),
s3 = Float.toString( 12.345678912f ),
s4 = Double.toString( 12.345678912 ),
s5 = Boolean.toString( true );
System.out.println( s1 ); // 1234567891
System.out.println( s2 ); // 123456789123
System.out.println( s3 ); // 12.345679
System.out.println( s4 ); // 12.345678912
System.out.println( s5 ); // true
[+] Tipp
Ein Java-Idiom[ 199 ](Es ist wiederum ein JavaScript-Idiom, mit dem Ausdruck s – 0 aus einem String eine Zahl zu machen, wenn denn die Variable s eine String-Repräsentation einer Zahl ist. ) zur Konvertierung ist auch folgende Anweisung:
String s = "" + number;
Der String erscheint immer in der englisch geschriebenen Variante. So steht bei den Dezimalzahlen ein Punkt statt des uns vertrauten Kommas.
[»] Hinweis
Bei der Darstellung von Zahlen ist eine landestypische (länderspezifische) Formatierung sinnvoll. Das kann printf(…) leisten:
System.out.printf( "%f", 1000000. ); // 1000000,000000
System.out.printf( "%f", 1234.567 ); // 1234,567000
System.out.printf( "%,.3f", 1234.567 ); // 1.234,567
Der Formatspezifizierer für Fließkommazahlen ist %f. Die zusätzliche Angabe mit ,.3f im letzten Fall führt zum Tausenderpunkt und zu drei Nachkommastellen.
toString(…) als Objekt- und Klassenmethode
Liegt ein Wrapper-Objekt vor, so liefert die Objektmethode toString() die String-Repräsentation des Werts, den das Wrapper-Objekt speichert. Dass es gleichlautende statische Methoden toString(…) und eine Objektmethode toString() gibt, sollte uns nicht verwirren; während die Klassenmethode den Arbeitswert zur Konvertierung aus dem Argument zieht, nutzt die Objektmethode den gespeicherten Wert im Wrapper-Objekt.
Anweisungen, die ausschließlich zum Konvertieren über das Wrapper-Objekt gehen, wie Integer.valueOf(v).toString(), lassen sich problemlos umschreiben in Integer.toString(v). Zudem bietet sich auch die überladene statische Methode String.valueOf(v) an, die – eben weil sie überladen ist – für alle möglichen Datentypen deklariert ist (doch nutzt valueOf(v) intern auch nur WrapperKlasse.toString(v)).
10.5.3 Von einer String-Repräsentation parsen
Die Wrapper-Klassen bieten statische parseXXX(…)-Methoden, die einen String in einen primitiven Datentyp konvertieren. Dem String kann ein Minus für negative Zahlen vorangestellt sein, auch ein Plus für positive Zahlen ist erlaubt. Die Methoden wurden bereits in Abschnitt 5.8.2, »String-Inhalt in einen primitiven Wert konvertieren«, vorgestellt.
10.5.4 Die Basisklasse Number für numerische Wrapper-Objekte
Alle numerischen Wrapper-Klassen können den gespeicherten Wert in einem beliebigen anderen numerischen Typ liefern. Die Methodennamen setzen sich – wie zum Beispiel doubleValue() und intValue() – aus dem Namen des gewünschten Typs sowie Value zusammen. Technisch gesehen überschreiben die Wrapper-Klassen Byte, Short, Integer, Long, Float und Double aus einer Klasse Number[ 200 ](Zusätzlich erweitern BigDecimal und BigInteger die Klasse Number und haben damit ebenfalls die xxxValue()-Methoden. AtomicInteger und AtomicLong erweitern ebenfalls Number, sind aber nicht immutable wie die restlichen Klassen. ) die xxxValue()-Methoden[ 201 ](Nur die Methoden byteValue() und shortValue() sind nicht abstrakt und müssen nicht überschrieben werden. Diese Methoden rufen intValue() auf und konvertieren den Wert über eine Typumwandlung in byte und short. ).
abstract class java.lang.Number
implements Serializable
byte byteValue()
Liefert den Wert der Zahl als byte.abstract double doubleValue()
Liefert den Wert der Zahl als double.abstract float floatValue()
Liefert den Wert der Zahl als float.abstract int intValue()
Liefert den Wert der Zahl als int.abstract long longValue()
Liefert den Wert der Zahl als long.short shortValue()
Liefert den Wert der Zahl als short.
[»] Hinweis
Wenn die Operandentypen beim Bedingungsoperator unterschiedlich sind, gibt es ganz automatisch eine Anpassung:
boolean b = true;
System.out.println( b ? 1 : 0.1 ); // 1.0
System.out.println( !b ? 1 : 0.1 ); // 0.1
Der Ergebnistyp ist double, sodass die Ganzzahl 1 als »1.0«, also als Fließkommazahl, ausgegeben wird. Die gleiche Anpassung nimmt der Compiler bei Wrapper-Typen vor, die er unboxt und konvertiert:
Integer i = 1;
Double d = 0.1;
System.out.println( b ? i : d ); // 1.0
System.out.println( !b ? i : d ); // 0.1
Während diese Ausgabe eigentlich klar ist, kann es zu einem Missverständnis kommen, wenn das Ergebnis nicht einfach ausgegeben, sondern als Verweis auf das resultierende Wrapper-Objekt zwischengespeichert wird. Da der Typ im Beispiel entweder Integer oder Double ist, kann der Ergebnistyp nur der Obertyp Number sein:
Number n1 = b ? i : d;
System.out.println( n1 ); // 1.0
System.out.println( n1 == i ); // false
Die Programmlogik und Ausgabe sind natürlich genauso wie vorher, doch Entwickler könnten annehmen, dass der Compiler keine Konvertierung durchführt, sondern entweder das originale Integer- oder das Double-Objekt referenziert; das macht er aber nicht. Die Variable n1 referenziert hier ein Integer-ungeboxtes-double-konvertiertes-Double-geboxtes Objekt, und so sind die Referenzen von n1 und i überhaupt nicht identisch. Wenn der Compiler hier wirklich die Originalobjekte zurückliefern soll, muss entweder das Integer- oder das Double-Objekt explizit auf Number gebracht werden, sodass damit das Unboxing ausgeschaltet wird und der Bedingungsoperator nur noch von beliebigen nicht zu interpretierenden Referenzen ausgeht:
Number n2 = b ? (Number) i : d; // oder Number n2 = b ? i : (Number) d;
System.out.println( n2 ); // 1
System.out.println( n2 == i ); // true
10.5.5 Vergleiche durchführen mit compareXXX(…), compareTo(…), equals(…) und Hashwerten
Haben wir zwei Ganzzahlen 1 und 2 vor uns, so ist es trivial zu sagen, dass 1 kleiner als 2 ist. Bei Fließkommazahlen ist das ein wenig komplizierter, da es hier »Sonderzahlen« wie Unendlich oder eine negative und positive 0 gibt. Damit das Vergleichen von zwei Werten immer nach dem gleichen Schema durchgeführt werden kann, gibt es zwei Sorten von Methoden in den Wrapper-Klassen, die uns sagen, ob zwei Werte kleiner, größer oder gleich sind:
Auf der einen Seite findet sich die Objektmethode compareTo(…) in den Wrapper-Klassen. Die Methode ist nicht zufällig in der Klasse, denn Wrapper-Klassen implementieren die Schnittstelle Comparable. (Wir haben die Schnittstelle schon am Anfang des Kapitels kurz vorgestellt.)
Wrapper-Klassen besitzen zudem statische compare(x, y)- und teilweise compareUnsigned(x, y)-Methoden.
Die Rückgabe der Methoden ist ein int, und es kodiert, ob ein Wert größer, kleiner oder gleich ist.
[zB] Beispiel
Teste verschiedene Werte:
System.out.println( Integer.compare(1, 2) ); // -1
System.out.println( Integer.compare(1, 1) ); // 0
System.out.println( Integer.compare(2, 1) ); // 1
System.out.println( Double.compare(2.0, 2.1) ); // -1
System.out.println( Double.compare(Double.NaN, 0) ); // 1
System.out.println( Boolean.compare(true, false) ); // 1
System.out.println( Boolean.compare(false, true) ); // -1
Ein true ist »größer« als ein false.
Tabelle 10.3 fasst die Methoden der Wrapper-Klassen zusammen:
Klasse | Methode aus Comparable | Statische Methode compareXXX(…) |
---|---|---|
Byte | int compareTo(Byte aByte) | int compare[Unsigned](byte x, byte y) |
Short | int compareTo(Short aShort) | int compare[Unsigned](short x, short y) |
int compareTo(Float aFloat) | int compare(float f1, float f2) | |
Double | int compareTo(Double aDouble) | int compare(double d1, double d2) |
Integer | int compareTo(Integer aInteger) | int compare(int x, int y) |
Long | int compareTo(Long aLong) | int compare(long x, long y) |
Character | int compareTo(Character aChar) | int compare[Unsigned](char x, char y) |
Boolean | int compareTo(Boolean aBoolean) | int compare(boolean x, boolean y) |
Die Implementierung einer statischen Methode WrapperKlasse.compare(…) ist äquivalent zu WrapperKlasse.valueOf(x).compareTo(WrapperKlasse.valueOf(y)), nur ist die zweite Variante langsamer und auch länger in der Schreibweise.
[»] Hinweis
Nur die genannten Wrapper-Klassen besitzen eine statische compare(…)-Methode. Es ist kein allgemeingültiges Muster, dass eine Klasse, wenn sie Number erweitert und Comparable implementiert, dann auch eine statische compare(…)-Methode hat. So erweitern zum Beispiel die Klassen BigInteger und BigDecimal die Oberklasse Number und implementieren Comparable, aber eine statische compare(…)-Methode bieten sie trotzdem nicht.
Gleichwertigkeitstest über equals(…)
Alle Wrapper-Klassen überschreiben aus der Basisklasse Object die Methode equals(…). So lässt sich testen, ob zwei Wrapper-Objekte den gleichen Wert haben, auch wenn die Wrapper-Objekte nicht identisch sind.
[zB] Beispiel
Die Ergebnisse einiger Gleichwertigkeitstests:
Boolean.TRUE.equals( Boolean.TRUE ) true
Integer.valueOf( 1 ).equals( Integer.valueOf( 1 ) ) true
Integer.valueOf( 1 ).equals( Integer.valueOf( 2 ) ) false
Integer.valueOf( 1 ).equals( Long.valueOf( 1 ) ) false
Integer.valueOf( 1 ).equals( 1L ) false
Es ist wichtig zu wissen, dass der Parametertyp von equals(…) immer Object ist, aber die Typen identisch sein müssen, da andernfalls automatisch der Vergleich falsch ergibt. Das zeigen das vorletzte und das letzte Beispiel. Die equals(…)-Methode aus den Zeilen 4 und 5 lehnt jeden Vergleich mit einem Nicht-Integer ab, und ein Long ist eben kein Integer. In der letzten Zeile kommt Boxing zum Einsatz, daher sieht der Programmcode kürzer aus. Aber er entspricht dem Code aus der vorletzten Zeile.
Die Objektmethode equals(…) der Wrapper-Klassen ist auch eine kurze Alternative zu wrapperObject.compareTo(anotherWrapperObject) == 0.
[»] Ausblick
Dass die Wrapper-Klassen equals(…) implementieren, ist gut, denn so können Wrapper-Objekte problemlos in Datenstrukturen wie einer ArrayList untergebracht und wiedergefunden werden. Und dass Wrapper-Objekte auch Comparable sind, ist ebenfalls prima für Datenstrukturen wie TreeSet, die – ohne extern gegebene Comparator-Klassen für Vergleiche – eine natürliche Ordnung der Elemente erwarten.
Hashwerte
Der Hashwert eines Objekts bildet den Zustand auf eine kompakte Ganzzahl ab. Haben zwei Objekte ungleiche Hashwerte, so müssen auch die Objekte ungleich sein (mindestens, wenn die Berechnung korrekt ist). Zur Bestimmung des Hashwerts deklariert jede Klasse über die Oberklasse java.lang.Object die Methode int hashCode(). Alle Wrapper-Klassen überschreiben diese Methode. Zudem gibt es statische Methoden, sodass sich leicht der Hashwert auch ohne ein Wrapper-Objekt berechnen lässt.
Klasse | Klassenmethode | Objektmethode |
---|---|---|
Boolean | static int hashCode(boolean value) | int hashCode() |
Byte | static int hashCode(byte value) | int hashCode() |
Short | static int hashCode(short value) | int hashCode() |
Integer | static int hashCode(int value) | int hashCode() |
Long | static int hashCode(long value) | int hashCode() |
Float | static int hashCode(float value) | int hashCode() |
Double | static int hashCode(double value) | int hashCode() |
Character | static int hashCode(char value) | int hashCode() |
Zur Berechnung von Hashwerten und deren Bedeutung für Datenstrukturen lesen Sie Abschnitt 10.1.5, »Hashwerte über hashCode() liefern *«.
10.5.6 Statische Reduzierungsmethoden in Wrapper-Klassen
In den numerischen Wrapper-Klassen Integer, Long, Float und Double gibt es drei Methoden sum(…), max(…) und min(…), die genau das machen, was der Methodenname verspricht.
final class java.lang.Integer|Long|Float|Double
extends Number
implements Comparable<Integer>
static Typ sum(Typ a, Typ b)
Bildet die Summe zweier Werte und liefert sie zurück. Entspricht einem einfachen a + b. Die Angabe Typ steht dabei für den entsprechenden primitiven Typ byte, short, int, long, float oder double, etwa in int sum(int a, int b).static Typ min(Typ a, Typ b)
Liefert das Minimum der zwei Zahlen.static Typ max(Typ a, Typ b)
Liefert das Maximum der zwei Zahlen.
Des Weiteren bieten die Boolean-Klassen drei Methoden für drei boolesche Operationen:
final class java.lang.Boolean
implements Comparable<Boolean>, Serializable
static boolean logicalAnd(boolean a, boolean b)
Liefert a && b.static boolean logicalOr(boolean a, boolean b)
Liefert a || b.static boolean logicalXor(boolean a, boolean b)
Liefert a ^ b.
Die Methoden sind für sich genommen nicht spannend. Für die Summe (Addition) tut es genauso gut der +-Operator (er steckt sowieso hinter den sum(…)-Methoden), und so wird keiner auf die Idee kommen, i = Integer.sum(i, 1) statt i++ zu schreiben. Für das Maximum/Minimum bietet die Math-Klasse auch schon entsprechende Methoden min(a, b) und max(a, b). Der Grund für alle genannten Methoden ist vielmehr, dass sie im Zusammenhang mit funktionaler Programmierung interessant sind und zwei Werte auf einen Wert reduzieren.
10.5.7 Konstanten für die Größe eines primitiven Typs
Alle Wrapper-Klassen besitzen eine int-Konstante SIZE für die Anzahl der Bits und eine int-Konstante BYTES, die mit der Größe des zugehörigen primitiven Datentyps belegt ist.
Belegung SIZE | Belegung BYTES | |
---|---|---|
Byte | 8 | 1 |
Short | 16 | 2 |
Integer | 32 | 4 |
Long | 64 | 8 |
Float | 32 | 4 |
Double | 64 | 8 |
Character | 16 | 2 |
Jedem Entwickler sind die Belegungen im Grunde klar, vielmehr sind die Konstanten für Tools gedacht.
10.5.8 Behandeln von vorzeichenlosen Zahlen *
Alle Ganzzahldatentypen (char ausgenommen) sind immer vorzeichenbehaftet, und Java wird wohl nie neue Datentypen für eine Unterscheidung einführen, wie es etwa C(++) bietet. Entwickler müssen damit leben, dass ein Byte immer zwischen –128 und +127 liegt und nicht zwischen 0 und 255. Doch wenn die Sprache keine neuen Typen einführt, kann hilft doch die Java-API mit Methoden in den Wrapper-Klassen.
toUnsignedXXX(…)-Methoden
Der erste Typ von Methoden interpretiert ein Bit-Muster als vorzeichenlose Zahl. Ist zum Beispiel ein byte mit dem Bit-Muster 11111111 belegt, so steht dies nicht für 255, sondern für –1. Die statischen Konvertierungsmethoden toUnsignedXXX(…) der Wrapper-Klassen helfen dabei, das Bit-Muster ohne Vorzeichen-Bit zu interpretieren, wobei ein Wechsel zu einem nächsthöheren Datentyp nötig ist. Das ist einleuchtend, denn im Fall von 11111111 kann ein byte wegen der Beschränkung auf –127 bis 128 die Zahl 255 nicht aufnehmen, int aber schon.
Byte | static int toUnsignedInt(byte x) |
static long toUnsignedLong(byte x) | |
static int toUnsignedInt(short x) | |
static long toUnsignedLong(short x) | |
Integer | static long toUnsignedLong(int x) |
Naheliegenderweise bietet Long so eine Methode nicht, denn es gibt keinen eingebauten Datentyp größer als long.
toUnsignedString(…) und parseUnsignedString(…)
Was Long jedoch bietet, und auch Integer, sind Umwandlungen in String-Repräsentationen und auch Parse-Methoden:
Integer | static String toUnsignedString(int i) |
static String toUnsignedString(int i, int radix) | |
static int parseUnsignedInt(String s) | |
static int parseUnsignedInt(String s, int radix) | |
Long | static String toUnsignedString(long i) |
static String toUnsignedString(long i, int radix) | |
static long parseUnsignedLong(String s) | |
static long parseUnsignedLong(String s, int radix) |
Die parseXXX(…)-Methoden lösen bei falschem Format eine NumberFormatException aus.
Vergleich, Division und Rest
Es gibt eine dritte Gruppe von Methoden, die eher mathematisch sind, und zwar statische Methoden zum Vergleichen, Dividieren und zur Restwertbildung:
Integer | static int compareUnsigned(int x, int y) |
static int divideUnsigned(int dividend, int divisor) | |
static int remainderUnsigned(int dividend, int divisor) | |
static int compareUnsigned(long x, long y) | |
static long divideUnsigned(long dividend, long divisor) | |
static long remainderUnsigned(long dividend, long divisor) |
10.5.9 Die Klasse Integer
Die Klasse Integer kapselt den Wert einer Ganzzahl vom Typ int in einem Objekt und bietet
Konstanten,
statische Methoden zur Konvertierung in einen String und zurück sowie
weitere Hilfsmethoden mathematischer Natur.
Um aus dem String eine Zahl zu machen, nutzen wir Integer.parseInt(String).
[zB] Beispiel
Konvertiere die Ganzzahl 38.317, die als String vorliegt, in eine Ganzzahl:
String number = "38317";
int integer = 0;
try {
integer = Integer.parseInt( number );
}
catch ( NumberFormatException e ) {
System.err.println( "Fehler beim Konvertieren von " + number );
}
System.out.println( integer );
Die NumberFormatException ist eine nicht geprüfte Exception (Genaueres dazu liefert Kapitel 8, »Ausnahmen müssen sein«). Sie muss also nicht zwingend in einem try-Block stehen.
Die statische Methode Integer.parseInt(String) konvertiert einen String in int, und die Umkehrmethode Integer.toString(int) liefert einen String. Weitere Varianten mit unterschiedlicher Basis wurden schon in Abschnitt 5.8.2, »String-Inhalt in einen primitiven Wert konvertieren«, vorgestellt.
final class java.lang.Integer
extends Number
implements Comparable<Integer>
static int parseInt(String s)
Erzeugt aus der Zeichenkette die entsprechende Zahl. Die Basis ist 10.static int parseInt(String s, int radix)
Erzeugt die Zahl mit der gegebenen Basis.static String toString(int i)
Konvertiert die Ganzzahl in einen String und liefert sie zurück.
parseInt(…) erlaubt keine länderspezifischen Tausendertrennzeichen, etwa in Deutschland den Punkt oder im angelsächsischen Raum das Komma.
10.5.10 Die Klassen Double und Float für Fließkommazahlen
Die Klassen Double und Float haben wie die anderen Wrapper-Klassen eine Doppelfunktionalität: Sie kapseln zum einen eine Fließkommazahl als Objekt und bieten zum anderen statische Utility-Methoden. Wir kommen in Kapitel 21, »Bits und Bytes, Mathematisches und Geld«, noch genauer auf die mathematischen Objekt- und Klassenmethoden zurück.
10.5.11 Die Long-Klasse
Integer und Long sind im Prinzip API-gleich, nur ist der kleinere Datentyp int durch long ersetzt.
10.5.12 Die Boolean-Klasse
Die Klasse Boolean kapselt den Datentyp boolean.
Boolean-Objekte aufbauen
Neben dem Konstruktor und Fabrikmethoden deklariert Boolean zwei Konstanten TRUE und FALSE.
final class java.lang.Boolean
implements Serializable, Comparable<Boolean>
static final Boolean FALSE
static final Boolean TRUE
Konstanten für WahrheitswerteBoolean(boolean value)
Erzeugt ein neues Boolean-Objekt. Dieser Konstruktor sollte nicht verwendet werden, stattdessen sollten Boolean.TRUE oder Boolean.FALSE eingesetzt werden. Boolean-Objekte sind immutable, und ein new Boolean(value) ist unnötig.Boolean(String s)
Parst den String und liefert ein neues Boolean-Objekt zurück.static Boolean valueOf(String s)
Parst den String und gibt die Wrapper-Typen Boolean.TRUE oder Boolean.FALSE zurück. Die statische Methode hat gegenüber dem Konstruktor Boolean(boolean) den Vorteil, dass sie immer das gleiche immutable Wahr- oder Falsch-Objekt (Boolean.TRUE oder Boolean. FALSE) zurückgibt, anstatt neue Objekte zu erzeugen. Daher ist es selten nötig, den Konstruktor aufzurufen und immer neue Boolean-Objekte aufzubauen.static boolean parseBoolean(String s)
Parst den String und liefert entweder true oder false.
Der Konstruktor Boolean(String name) bzw. die beiden statischen Methoden valueOf(String name) und parseBoolean(String name) nehmen Strings entgegen und führen im JDK den Test name != null && name.equalsIgnoreCase("true") durch. Das heißt zum einen, dass die Groß-/Kleinschreibung unwichtig ist, und zum anderen, dass Dinge wie » false « (mit Leerzeichen), »falsch« oder »Ostereier« automatisch false ergeben, wobei »TRUE« oder »True« dann true liefern.
[+] Tipp
Würde jeder Entwickler ausschließlich die Konstanten Boolean.TRUE und Boolean.FALSE nutzen, so wären bei lediglich zwei Objekten Vergleiche mit == bzw. != in Ordnung. Da es aber einen Konstruktor für Boolean-Objekte gibt (und es ist durchaus diskussionswürdig, warum es überhaupt Konstruktoren für Wrapper-Klassen gibt), ist die sicherste Variante ein boolean1.equals(boolean2). Wir können eben nicht wissen, ob eine Bibliotheksmethode wie Boolean isNice() auf die zwei Konstanten zurückgreift oder immer wieder neue Boolean-Objekte aufbaut.
Boolean in eine Ganzzahl konvertieren
Der primitive Typ boolean lässt sich nicht über eine Typumwandlung in einen anderen primitiven Typ konvertieren. Doch in der Praxis kommt es vor, dass true auf 1 und false auf 0 abgebildet werden muss. Der übliche Weg ist:
int val = aBoolean ? 1 : 0;
Exotischer ist:
int val = Boolean.compare( aBoolean, false );
Noch exotischer ist Folgendes:
int val = 1 & Boolean.hashCode( aBoolean ) >> 1;
10.5.13 Autoboxing: Boxing und Unboxing
Autoboxing ist eine Eigenschaft von Java, bei der primitive Datentypen und Wrapper-Objekte bei Bedarf ineinander umgewandelt werden. Ein Beispiel:
int primInt = 4711;
Integer wrapInt = primInt; // steht für wrapInt = Integer.valueOf(primInt) (1)
primInt = wrapInt; // steht für primInt = wrapInt.intValue() (2)
Die Anweisung in (1) nennt sich Boxing und erstellt automatisch ein Wrapper-Objekt, sofern erforderlich. Schreibweise (2) ist das Unboxing und steht für das Beziehen des Elements aus dem Wrapper-Objekt. Das bedeutet: Überall dort, wo der Compiler ein primitives Element erwartet, aber ein Wrapper-Objekt vorhanden ist, entnimmt er den Wert mit einer passenden xxxValue(…)-Methode dem Wrapper. Der Compiler wird den Code automatisch generieren, vom Autoboxing ist später im Bytecode nichts mehr zu sehen.
Die Operatoren ++, -- *
Der Compiler konvertiert nach festen Regeln, und auch die Operatoren ++, -- sind erlaubt:
Integer i = 12;
i = i + 1; // (1)
i++; // (2)
System.out.println( i ); // 14
Wichtig ist, dass weder (1) noch (2) das Original-Integer-Objekt mit der 12 ändern (alle Wrapper-Objekte sind immutable), sondern dass i nur andere Integer-Objekte für 13 und 14 referenziert.
Boxing für dynamische Datenstrukturen (Ausblick)
Angenehm ist das Autoboxing, wenn in Datenstrukturen »primitive« Elemente abgelegt werden sollen:
ArrayList<Double> list = new ArrayList<Double>();
list.add( Math.sin(Math.PI / 4) ); // Boxing bei boolean add(Double)
double d = list.get( 0 ); // Unboxing bei Double get(int)
Leider ist es so, dass der Typ der Liste tatsächlich mit dem Wrapper-Typ Double festgelegt werden muss und nicht mit dem Primitivtyp double. Aber vielleicht ändert sich das ja noch irgendwann. Das Autoboxing macht es jedenfalls etwas erträglicher.
Keine Konvertierung der null-Referenz in 0
Beim Unboxing führt der Compiler bzw. die Laufzeitumgebung keine Konvertierung von null in 0 durch. Mit anderen Worten: Bei der folgenden versuchten Zuweisung gibt es keinen Compilerfehler, aber zur Laufzeit eine NullPointerException:
int n = (Integer) null; // java.lang.NullPointerException zur Laufzeit
[»] Hinweis
In switch-Blöcken sind int, Aufzählungen und Strings als Typen erlaubt. Bei Ganzzahlen führt der Compiler automatisch Konvertierungen und Unboxing in int durch. Beim Unboxing gibt es aber die Gefahr einer NullPointerException:
Integer integer = null;
switch ( integer ) { } // NullPointerException zur Laufzeit
Autoboxing bei Arrays?
Da primitive Datentypen und Wrapper-Objekte durch Autoboxing automatisch konvertiert werden, fällt im Alltag der Unterschied nicht so auf. Bei Arrays ist der Unterschied jedoch augenfällig, und hier kann Java keine automatische Konvertierung durchführen. Denn auch wenn zum Beispiel char und Character automatisch ineinander umgewandelt werden, so sind Arrays nicht konvertierbar. Eine Array-Initialisierung der Art
Character[] chars = { 'S', 'h', 'a' };
enthält zwar rechts dreimal Boxing von char in Character, und eine automatische Umwandlung auf der Ebene der Elemente ist gültig, sodass
char first = chars[ 0 ];
natürlich gilt, aber die Array-Objekte lassen sich nicht ineinander überführen. Folgendes ist nicht korrekt:
char[] sha = chars; // Compilerfehler!
Die Typen char[] und Character[] sind also zwei völlig unterschiedliche Typen, und eine Überführung ist nicht möglich (von den Problemen mit null-Referenzen einmal ganz abgesehen). So muss in der Praxis zwischen den unterschiedlichen Typen konvertiert werden, und bedauerlicherweise bietet die Java-Standardbibliothek hierfür keine Methoden.[ 202 ](Die Lücke füllt etwa die Open-Source-Bibliothek Apache Commons Lang (http://commons.apache.org/proper/commons-lang) mit der Klasse ArrayUtils, die mit toObject(…) und toPrimitive(…) die Konvertierungen durchführt. )
Mehr Probleme als Lösungen durch Autoboxing? (Teil 1) *
Mit dem Autoboxing ist eine Reihe von Unregelmäßigkeiten verbunden, die der Programmierer beachten muss, um Fehler zu vermeiden. Eine Unregelmäßigkeit hängt mit dem Unboxing zusammen, das der Compiler immer dann vornimmt, wenn ein Ausdruck einen primitiven Wert erwartet. Wenn kein primitives Element erwartet wird, wird auch kein Unboxing vorgenommen:
Double d1 = Double.valueOf( 1 );
Double d2 = Double.valueOf( 1 );
System.out.println( d1 >= d2 ); // true
System.out.println( d1 <= d2 ); // true
System.out.println( d1 == d2 ); // false
System.out.println( d1 == d2-0 ); // true
Der Vergleich mit == ist ein Referenzvergleich, und es findet kein Unboxing auf primitive Werte statt, sodass es auf einen Vergleich von primitiven Werten hinausliefe. Wenn der Compiler zwei Referenzvariablen vor sich sieht, gibt es für Unboxing keinen Grund, auch wenn sie Wrapper-Typen sind. Daher muss bei zwei unterschiedlichen Integer-Objekten dieser Identitätsvergleich immer falsch sein. Das ist natürlich problematisch, da die alte mathematische Regel »aus i <= j und i >= j folgt automatisch i == j« nicht mehr gilt. Wenn es die unterschiedlichen Integer-Objekte für gleiche Werte nicht gäbe, bestünde dieses Problem nicht. Mit dem Minus-null-Trick erreichen wir ein Unboxing, sodass wieder zwei Zahlen verglichen werden. Alternativ hilft eine explizite Typumwandlung in int oder ein Zugriff auf die Objektmethode intValue() zum bewussten Auspacken.
Mehr Probleme als Lösungen durch Autoboxing? (Teil 2) *
Es ist interessant zu wissen, was nun genau passiert, wenn das Boxing eine Zahl in ein Wrapper-Objekt umwandelt. In dem Moment wird nicht der Konstruktor aufgerufen, sondern die statische valueOf(…)-Methode:
Integer n1 = new Integer( 10 );
Integer n2 = Integer.valueOf( 10 );
Integer n3 = 10; // Integer.valueOf( 10 );
Integer n4 = 10; // Integer.valueOf( 10 );
System.out.println( n1 == n2 ); // false
System.out.println( n2 == n3 ); // true
System.out.println( n1 == n3 ); // false
System.out.println( n3 == n4 ); // true
In dem Beispiel vergleichen wir viermal die Identität der Wrapper-Objekte. Es fällt auf, dass einige Vergleiche falsch sind, was kein Wunder ist und durch new schnell erklärt ist, denn hier legt die Laufzeitumgebung immer neue Objekte an. Auffällig sind die identischen Objekte (n2, n3, n4), die beim Boxing durch das dahinterliegende valueOf(int) entstehen. Das Rätsel löst sich nur mit einem Blick in die von Oracle gewählte Implementierung auf. Hier zeigt sich, dass nämlich über Boxing gebildete Integer-Objekte einem Pool entstammen. Mit dem Pool versucht das JDK, das Problem mit dem == zu lösen, dass ausgewählte Wrapper-Objekte wirklich identisch sind und == sich bei Wrapper-Referenzen so verhält wie == bei primitiven Werten.
Da nicht beliebig viele Wrapper-Objekte aus einem Pool kommen können, gilt die Identität der über Boxing gebildeten Objekte nur in einem ausgewählten Wertebereich zwischen –128 und +127, also im Wertebereich eines Bytes:
Integer j1 = 2;
Integer j2 = 2;
System.out.println( j1 == j2 ); // true
Integer k1 = 127;
Integer k2 = 127;
System.out.println( k1 == k2 ); // true
Integer l1 = 128;
Integer l2 = 128;
System.out.println( l1 == l2 ); // false
Integer m1 = 1000;
Integer m2 = 1000;
System.out.println( m1 == m2 ); // false
Wir betonten bereits, dass auch bei Wrapper-Objekten der Vergleich mit == immer ein Referenzvergleich ist. Da 2 und 127 im Wertebereich zwischen –128 und +127 liegen, entstammen die entsprechenden Integer-Objekte dem Pool. Das gilt für 128 und 1.000 nicht; sie sind immer neue Objekte. Damit ergibt auch der ==-Vergleich false.