Rheinwerk Computing < openbook >


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


Download:

- Listings, ca. 2,7 MB


Buch bestellen
Ihre Meinung?



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

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


Java ist auch eine Insel

Pfeil10 Besondere Typen der Java SE
Pfeil10.1 Object ist die Mutter aller Klassen
Pfeil10.1.1 Klassenobjekte
Pfeil10.1.2 Objektidentifikation mit toString()
Pfeil10.1.3 Objektgleichwertigkeit mit equals(…) und Identität
Pfeil10.1.4 Klonen eines Objekts mit clone() *
Pfeil10.1.5 Hashwerte über hashCode() liefern *
Pfeil10.1.6 System.identityHashCode(…) und das Problem der nicht eindeutigen Objektverweise *
Pfeil10.1.7 Aufräumen mit finalize() *
Pfeil10.1.8 Synchronisation *
Pfeil10.2 Schwache Referenzen und Cleaner
Pfeil10.3 Die Utility-Klasse java.util.Objects
Pfeil10.3.1 Eingebaute null-Tests für equals(…)/hashCode()
Pfeil10.3.2 Objects.toString(…)
Pfeil10.3.3 null-Prüfungen mit eingebauter Ausnahmebehandlung
Pfeil10.3.4 Tests auf null
Pfeil10.3.5 Indexbezogene Programmargumente auf Korrektheit prüfen
Pfeil10.4 Vergleichen von Objekten und Ordnung herstellen
Pfeil10.4.1 Natürlich geordnet oder nicht?
Pfeil10.4.2 compareXXX()-Methode der Schnittstellen Comparable und Comparator
Pfeil10.4.3 Rückgabewerte kodieren die Ordnung
Pfeil10.4.4 Beispiel-Comparator: den kleinsten Raum einer Sammlung finden
Pfeil10.4.5 Tipps für Comparator und Comparable-Implementierungen
Pfeil10.4.6 Statische und Default-Methoden in Comparator
Pfeil10.5 Wrapper-Klassen und Autoboxing
Pfeil10.5.1 Wrapper-Objekte erzeugen
Pfeil10.5.2 Konvertierungen in eine String-Repräsentation
Pfeil10.5.3 Von einer String-Repräsentation parsen
Pfeil10.5.4 Die Basisklasse Number für numerische Wrapper-Objekte
Pfeil10.5.5 Vergleiche durchführen mit compareXXX(…), compareTo(…), equals(…) und Hashwerten
Pfeil10.5.6 Statische Reduzierungsmethoden in Wrapper-Klassen
Pfeil10.5.7 Konstanten für die Größe eines primitiven Typs
Pfeil10.5.8 Behandeln von vorzeichenlosen Zahlen *
Pfeil10.5.9 Die Klasse Integer
Pfeil10.5.10 Die Klassen Double und Float für Fließkommazahlen
Pfeil10.5.11 Die Long-Klasse
Pfeil10.5.12 Die Boolean-Klasse
Pfeil10.5.13 Autoboxing: Boxing und Unboxing
Pfeil10.6 Iterator, Iterable *
Pfeil10.6.1 Die Schnittstelle Iterator
Pfeil10.6.2 Wer den Iterator liefert
Pfeil10.6.3 Die Schnittstelle Iterable
Pfeil10.6.4 Erweitertes for und Iterable
Pfeil10.6.5 Interne Iteration
Pfeil10.6.6 Eine eigene Iterable implementieren *
Pfeil10.7 Die Spezial-Oberklasse Enum
Pfeil10.7.1 Methoden auf Enum-Objekten
Pfeil10.7.2 Aufzählungen mit eigenen Methoden und Initialisierern *
Pfeil10.7.3 enum mit eigenen Konstruktoren *
Pfeil10.8 Annotationen in der Java SE
Pfeil10.8.1 Orte für Annotationen
Pfeil10.8.2 Annotationstypen aus java.lang
Pfeil10.8.3 @Deprecated
Pfeil10.8.4 Annotationen mit zusätzlichen Informationen
Pfeil10.8.5 @SuppressWarnings
Pfeil10.9 Zum Weiterlesen
 

Zum Seitenanfang

10.5    Wrapper-Klassen und Autoboxing Zur vorigen ÜberschriftZur nächsten Überschrift

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

Wrapper-Klasse

Primitiver Typ

Byte

byte

Short

short

Integer

int

Long

long

Double

double

Float

float

Boolean

boolean

Character

char

Tabelle 10.2    Wrapper-Klassen und primitive Datentypen

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

Essenzielle Typbeziehungen der Wrapper-Klassen

Abbildung 10.5    Essenzielle Typbeziehungen der Wrapper-Klassen

 

Zum Seitenanfang

10.5.1    Wrapper-Objekte erzeugen Zur vorigen ÜberschriftZur nächsten Überschrift

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.

 

Zum Seitenanfang

10.5.2    Konvertierungen in eine String-Repräsentation Zur vorigen ÜberschriftZur nächsten Überschrift

Alle Wrapper-Klassen bieten statische toString(value)-Methoden zur Konvertierung des primitiven Elements in einen String an:

Listing 10.26    src/main/java/com/tutego/insel/wrapper/WrapperToString.java, main()

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

 

Zum Seitenanfang

10.5.3    Von einer String-Repräsentation parsen Zur vorigen ÜberschriftZur nächsten Überschrift

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.

 

Zum Seitenanfang

10.5.4    Die Basisklasse Number für numerische Wrapper-Objekte Zur vorigen ÜberschriftZur nächsten Überschrift

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

UML-Diagramm von Number

Abbildung 10.6    UML-Diagramm von Number

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
 

Zum Seitenanfang

10.5.5    Vergleiche durchführen mit compareXXX(…), compareTo(…), equals(…) und Hashwerten Zur vorigen ÜberschriftZur nächsten Überschrift

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:

Listing 10.27    src/main/java/com/tutego/insel/wrapper/CompareToDemo.java, main()

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)

Float

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)

Tabelle 10.3    Methoden der Wrapper-Klassen

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

Tabelle 10.4    Die statische Methode »hashCode(…)« und Objektmethoden im Vergleich

Zur Berechnung von Hashwerten und deren Bedeutung für Datenstrukturen lesen Sie Abschnitt 10.1.5, »Hashwerte über hashCode() liefern *«.

 

Zum Seitenanfang

10.5.6    Statische Reduzierungsmethoden in Wrapper-Klassen Zur vorigen ÜberschriftZur nächsten Überschrift

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.

 

Zum Seitenanfang

10.5.7    Konstanten für die Größe eines primitiven Typs Zur vorigen ÜberschriftZur nächsten Überschrift

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.

Wrapper-Klasse

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

Tabelle 10.5    »BYTES«- und »SIZE«-Konstanten in den Wrapper-Klassen

Jedem Entwickler sind die Belegungen im Grunde klar, vielmehr sind die Konstanten für Tools gedacht.

 

Zum Seitenanfang

10.5.8    Behandeln von vorzeichenlosen Zahlen * Zur vorigen ÜberschriftZur nächsten Überschrift

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)

Short

static int toUnsignedInt(short x)

static long toUnsignedLong(short x)

Integer

static long toUnsignedLong(int x)

Tabelle 10.6    »toUnsignedXXX(…)«-Methoden in »Byte«, »Short« und »Integer«

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)

Tabelle 10.7    String-Repräsentationen von vorzeichenlosen Zahlen erstellen und parsen

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)

Long

static int compareUnsigned(long x, long y)

static long divideUnsigned(long dividend, long divisor)

static long remainderUnsigned(long dividend, long divisor)

Tabelle 10.8    Mathematische Methoden

 

Zum Seitenanfang

10.5.9    Die Klasse Integer Zur vorigen ÜberschriftZur nächsten Überschrift

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.

 

Zum Seitenanfang

10.5.10    Die Klassen Double und Float für Fließkommazahlen Zur vorigen ÜberschriftZur nächsten Überschrift

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.

 

Zum Seitenanfang

10.5.11    Die Long-Klasse Zur vorigen ÜberschriftZur nächsten Überschrift

Integer und Long sind im Prinzip API-gleich, nur ist der kleinere Datentyp int durch long ersetzt.

 

Zum Seitenanfang

10.5.12    Die Boolean-Klasse Zur vorigen ÜberschriftZur nächsten Überschrift

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 Wahrheitswerte

  • Boolean(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;
 

Zum Seitenanfang

10.5.13    Autoboxing: Boxing und Unboxing Zur vorigen ÜberschriftZur nächsten Überschrift

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.

Autoboxing von »int/Integer«

Abbildung 10.7    Autoboxing von »int/Integer«

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;          // inline image 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 ) { } // inline image 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;   // inline image 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:

Listing 10.28    src/main/java/com/tutego/insel/wrapper/Autoboxing.java, Ausschnitt

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:

Listing 10.29    src/main/java/com/tutego/insel/wrapper/Autoboxing.java, Ausschnitt

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:

Listing 10.30    src/main/java/com/tutego/insel/wrapper/Autoboxing.java, Ausschnitt

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.

[»]  Abschlussfrage

Welche Ausgabe kommt auf den Bildschirm? Ändert sich etwas, wenn i und j auf 222 stehen?

Integer i = 1, j = 1;

boolean b = (i <= j && j <= i && i != j);

System.out.println( b );

 


Ihre Meinung?

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

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

Jetzt Buch bestellen


 Buchempfehlungen
Zum Rheinwerk-Shop: Captain CiaoCiao erobert Java

Captain CiaoCiao erobert Java




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Objektorientierte Programmierung

Objektorientierte Programmierung




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

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2021

Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.

Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 

[Rheinwerk Computing]



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



Cookie-Einstellungen ändern