Java 8 bekommt auch eine Optional-Klasse (wie Guava und Scala)

+/*

+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.

+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.

+ *

+ * This code is free software; you can redistribute it and/or modify it

+ * under the terms of the GNU General Public License version 2 only, as

+ * published by the Free Software Foundation. Oracle designates this

+ * particular file as subject to the "Classpath" exception as provided

+ * by Oracle in the LICENSE file that accompanied this code.

+ *

+ * This code is distributed in the hope that it will be useful, but WITHOUT

+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or

+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License

+ * version 2 for more details (a copy is included in the LICENSE file that

+ * accompanied this code).

+ *

+ * You should have received a copy of the GNU General Public License version

+ * 2 along with this work; if not, write to the Free Software Foundation,

+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.

+ *

+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA

+ * or visit www.oracle.com if you need additional information or have any

+ * questions.

+ */

+package java.util;

+

+import java.util.functions.Factory;

+import java.util.functions.Mapper;

+

+/**

+ * Optional

+ *

+ * @author Brian Goetz

+ */

+public class Optional<T> {

+ private final static Optional<?> EMPTY = new Optional<>();

+

+ private final T value;

+ private final boolean present;

+

+ public Optional(T value) {

+ this.value = value;

+ this.present = true;

+ }

+

+ private Optional() {

+ this.value = null;

+ this.present = false;

+ }

+

+ public static<T> Optional<T> empty() {

+ return (Optional<T>) EMPTY;

+ }

+

+ public T get() {

+ if (!present)

+ throw new NoSuchElementException();

+ return value;

+ }

+

+ public boolean isPresent() {

+ return present;

+ }

+

+ public T orElse(T other) {

+ return present ? value : other;

+ }

+

+ public T orElse(Factory<T> other) {

+ return present ? value : other.make();

+ }

+

+ public<V extends Throwable> T orElseThrow(Factory<V> exceptionFactory) throws V {

+ if (present)

+ return value;

+ else

+ throw exceptionFactory.make();

+ }

+

+ public<V extends Throwable> T orElseThrow(Class<V> exceptionClass) throws V {

+ if (present)

+ return value;

+ else

+ try {

+ throw exceptionClass.newInstance();

+ }

+ catch (InstantiationException | IllegalAccessException e) {

+ throw new IllegalStateException("Unexpected exception: " + e, e);

+ }

+ }

+

+ public<V> Optional<V> map(Mapper<T, V> mapper) {

+ return present ? new Optional<>(mapper.map(value)) : Optional.<V>empty();

+ }

+

+ @Override

+ public boolean equals(Object o) {

+ if (this == o) return true;

+ if (o == null || getClass() != o.getClass()) return false;

+

+ Optional optional = (Optional) o;

+

+ if (present != optional.present) return false;

+ if (value != null ? !value.equals(optional.value) : optional.value != null) return false;

+

+ return true;

+ }

+

+ @Override

+ public int hashCode() {

+ int result = value != null ? value.hashCode() : 0;

+ result = 31 * result + (present ? 1 : 0);

+ return result;

+ }

+}


Änderungen an Schnittstellen: Code-Kompatibilität und Binär-Kompatibilität

Sind Schnittstellen einmal deklariert und in einer großen Anwendung verbreitet, so sind Änderungen nur schwer möglich, da sie schnell die Kompatibilität brechen. Wird der Name einer Parametervariablen umbenannt, ist das kein Problem, aber bekommt eine Schnittstelle eine neue Operation, führt das zu einem Übersetzungsfehler, wenn nicht automatisch alle implementierenden Klassen diese neue Methode implementieren. Framework-Entwickler müssen also sehr drauf achten, wie sie Schnittstellen modifizieren, doch sie haben es in der Hand, wie weit die Kompatibilität gebrochen wird.

Geschichtsstunde

Schnittstellen später zu ändern, wenn schon viele Klassen die Schnittstelle implementieren, ist eine schlechte Idee. Denn erneuert sich die Schnittstelle, etwa wenn nur eine Operation hinzukommt oder sich ein Parametertyp ändert, dann sind plötzlich alle implementierenden Klassen kaputt. Sun selbst hat dies bei der Schnittstelle java.sql.Connection riskiert. Beim Übergang von Java 5 auf Java 6 wurde die Schnittstelle erweitert, und keine Treiberimplementierungen konnten mehr compiliert werden.

Code-Kompatibilität und Binär-Kompatibilität

Es gibt Änderungen, die führen zwar zu Compilerfehlern, wie neu eingeführten Operationen, sind aber zur Laufzeit in Ordnung. Bekommt eine Schnittstelle eine neue Methode, so ist das für die JVM überhaupt kein Problem. Die Laufzeitumgebung arbeitet auf den Klassendateien selbst und sie interessiert es nicht, ob eine Klasse brav alle Methoden der Schnittstelle implementiert; sie löst nur Methodenverweise auf. Wenn eine Schnittstelle plötzlich „mehr“ vorschreibt, hat sie damit kein Problem.

Während also fast alle Änderungen an Schnittstellten zum Bruch der Codebasis führen, sind doch einige Änderungen für die JVM in Ordnung. Wir nennen das Binär-Kompatibilität. Zu den binär-kompatiblen Änderungen zählen:

  • Neue Methode hinzufügen
  • Schnittstelle erbt von einer zusätzlichen Schnittstelle
  • Hinzufügen/Löschen einer throws-Ausnahme
  • Letzten Parametertyp von T[] in T… ändern
  • Neue Konstanten, also statische Variablen hinzufügen
  • Die Anzahl der binär-inkompatiblen Änderungen sind jedoch gradierender. Verboten sind:

  • Ändern des Methodennamens
  • Ändern der Parametertypen und Umsortieren der Parameter
  • Formalen Parameter hinzunehmen oder entfernen
  • Strategien zum Ändern von Schnittstellen

    Falls die Schnittstelle nicht groß veröffentlicht wurde, so lassen sich einfacher Änderungen vornehmen. Ist der Name einer Operation zum Beispiel schlecht gewählt, wird ein Refactoring in der IDE den Namen in der Schnittstelle genauso ändern wie auch alle Bezeichner in den implementierenden Klassen. Problematischer ist es, wenn externe Nutzer sich auf die Schnittstelle verlassen. Eine Lösung ist, diese Klienten ebenfalls zur Änderung zu zwingen, oder auf „Schönheitsänderungen“, wie dem Ändern des Methodenamens, einfach zu zu verzichten.

    Kommen Operationen hinzu, hat sich eine Konvention etabliert, die im Java-Universum oft anzutreffen ist: Soll eine Schnittstelle um Operationen erweitert werden, so gibt es eine neue Schnittstelle, die die alte erweitert, und auf „2“ endet; java.awt.LayoutManager2 ist so ein Beispiel aus dem Bereich der grafischen Oberflächen, Attributes2, EntityResolver2, Locator2 für XML-Verarbeitung sind weitere. Ein Blick auf die API vom Eclipse-Framework (http://help.eclipse.org/juno/topic/org.eclipse.platform.doc.isv/reference/api/index.html?overview-summary.html) zeigt, dass bei mehr als 3500 Typen dieses Muster um die 70 Mal angewendet wurde.

    Seit Java 8 gibt es eine weitere Möglichkeit Operationen in Schnittstellen hinzuzufügen, sogenannte Virtuelle Erweiterungsmethoden. Sie erweitern die Schnittstelle, fügen aber gleich schon eine vorgefertigte Implementierung mit, sodass Unterklassen nicht zwingend eine Implementierung anbieten müssen.

    Ressourcen aus dem Klassenpfad und aus Jar‑Archiven laden

    Um Ressourcen wie Grafiken oder Konfigurationsdateien aus Jar-Archiven zu laden gibt es eine Methoden am Class-Objekt: getResourceAsStream().

     

    class java.lang.Class

    implements Serializable, GenericDeclaration, Type, AnnotatedElement

    – InputStream getResourceAsStream(String name)
    Gibt einen Eingabestrom auf die Datei mit dem Namen name zurück, oder null, falls es keine Ressource mit dem Namen im Klassepfad gibt.

     

    Da der Klassenlader die Ressource findet, entdeckt er alle Dateien, die im Pfad des Klassenladers eingetragen sind. Das gilt auch für Jar-Archive, weil dort vom Klassenlader alles verfügbar ist. Die Methode getResourceAsStream() liefert auch null, wenn die Sicherheitsrichtlinien das Lesen verbieten. Da die Methode keine Ausnahme auslöst, muss auf jeden Fall getestet werden, ob die Rückgabe ungleich null war.

    Das folgende Programm liest ein Byte ein und gibt es auf dem Bildschirm aus:

     

    package com.tutego.insel.io.stream;

    import java.io.*;

    import java.util.Objects;

    public class GetResourceAsStreamDemo {

    public static void main( String[] args ) {

      String filename = "onebyte.txt";

      InputStream is = Objects.requireNonNull(

       GetResourceAsStreamDemo.class.getResourceAsStream( filename ),

        "Datei gibt es nicht!" );

      try {

       System.out.println( is.read() ); // 49

      }

      catch ( IOException e ) {

       e.printStackTrace();

      }

    }

    }

    Die Datei onebyte.txt befindet sich im gleichen Pfad wie auch die Klasse, liegt also in com/tutego/insel/io/stream/onebyte.txt. Liegt sie zum Beispiel im Wurzelverzeichnis des Pakets, muss sie mit "/onebyte.txt" angegeben werden. Liegen die Ressourcen außerhalb des Klassenpfades, können sie nicht gelesen werden. Der große Vorteil ist aber, dass die Methode alle Ressourcen anzapfen kann, die über den Klassenlader zugänglich sind, und das ist insbesondere der Fall, wenn die Dateien aus Jar-Archiven kommen – hier gibt es keinen üblichen Pfad im Dateisystem, der hört in der Regel beim Jar-Archiv selbst auf.

    Zum Nutzen der getResourceAsStream()-Methoden ist ein Class-Objekt nötig, was wir in unserem Fall über Klassenname.class besorgen. Das ist nötig, weil die Methode main() statisch ist. Andernfalls kann innerhalb von Objektmethoden auch getClass() eingesetzt werden, eine Methode, die jede Klasse aus der Basisklasse java.lang.Object erbt.

    Lesen aus Dateien und Schreiben in Dateien

    Um Daten aus Dateien lesen, oder sie schreiben zu können, ist eine Strom-Klasse nötig, die es schafft, die Operationen von Reader, Writer, InputStream und OutputStream auf Dateien abzubilden. Um an solche Implementierungen zu kommen gibt es drei verschiedene Ansätze:

    · Die Utility-Klasse Files bietet vier newXXX()-Methoden, um Lese-/Schreib-Datenströme für Zeichen- und Byte-orientierte Dateien zu bekommen.

    · Ein Class-Objekt bietet getResourceAsStream() und liefert einen InputStream, um Bytes aus Dateien im Klassenpfad zu lesen. Zum Schreiben gibt es nichts Vergleichbares und falls keine Bytes sondern Unicode-Zeichen gelesen werden sollen, muss der InputStream in einen Reader konvertiert werden.

    · Die speziellen Klassen FileInputStream, FileReader, FileOutputStream, FileWriter sind Strom-Klassen, die read()/write()-Methoden auf Dateien abbilden.

    Jede der Varianten hat Vor-/und Nachteile.

    Primitiv- und Verweis-Typ und der Vergleich mit Smalltalk und .NET

    Die Datentypen in Java zerfallen in zwei Kategorien:

    • Primitive Typen: Die primitiven (einfachen) Typen sind die eingebauten Datentypen für Zahlen, Unicode-Zeichen und Wahrheitswerte.
    • Referenztypen: Mit diesem Datentyp lassen sich Objektverweise etwa auf Zeichenketten, Datenstrukturen oder Zwergpinscher verwalten.

    Warum sich damals Sun für diese Teilung entschieden hat, lässt sich mit einem einfachen Grund erklären: Java ist als Programmiersprache entworfen worden, die kleine schwache Geräte unterstützen sollte, und auf denen musste die Java-Software, die am Anfang noch interpretierte wurde, so schnell wie möglich laufen. Unterscheidet der Compiler zwischen primitiven Typen und Referenztypen, so kann der er relativ leicht Bytecode erzeugen, der ebenfalls zwischen den beiden Typen unterscheidet. Damit kann die Laufzeitumgebung auch den Programmcode viel schneller ausführen, und das mit einem relativen einfachen Compiler. Das war für die Anfangszeit ein wichtiges Kriterium.

    Sprachvergleich mit Smalltalk und .NET

    In Smalltalk ist alles ein Objekt, auch die eingebauten Sprachdatentypen. Für Zahlen gibt es einen Basistyp Number und Integer, Float, Fraction als Untertypen. Immer noch gibt es arithmetische Operatoren (+, -, *, /, //, \\ um sie alle aufzuzählen), aber das sind nur Methoden der Klasse Number.[1] Für Java-Entwickler sind Methodennamen wie + oder – ungewöhnlich, doch in Smalltalk ist es das nicht. Syntaktisch unterscheidet sich ein 1 + 2 in Java und Smalltalk nicht, nur in Smalltalk ist die Addition ein Nachrichtenaufruf an das Integer-Objekt 1 an die Methode + mit dem Argument 2, was wiederum ein Integer-Objekt ist – die Objekte baut der Compiler selbständig aus den Literalen auf. Eine Klasse Integer für Ganzzahlen besitzt weitere Methoden wie asCharacter, floor usw.[2] Es ist wichtig zu verstehen, dass dies nur das semantische Modell auf der Sprachseite ist; das hat nichts damit zu tun, wie später die Laufzeitumgebung diese speziellen Nachrichtenaufrufe optimiert. Moderne Smalltalk-Laufzeitumgebungen mit Just-In-Time-Compilation sind bei arithmetischen Operationen auf einen ähnlichen Level wie C oder Java. Durch die Einteilung von Java in primitive Datentypen und Referenztypen haben die Sprachschöpfer einen objektorientierten Bruch in Kauf genommen, um die interpretierte Laufzeit Anfang der 1990er zu optimieren – eine Optimierung, die aus heutiger Sicht unnötig war.

    In .NET ist es eher wie in Java. Der Compiler kennt eingebauten Datentypen und gibt ihnen eine Sonderbehandlung, es sind keine Methodenaufrufe. Auch im Bytecode (Common Intermediate Language, kurz CIL in .NET genannt) finden sich Anweisungen wie Addition, Subtraktion wieder. Doch es gibt noch einen Unterschied zu Java. Der Compilder bildet Datentypen der .NET-Sprachen auf .NET-Klassen ab, und diese Klassen haben Methoden. In C# ist der eingebaute Datentyp float mit dem Datentyp Single (aus dem .NET-Paket System) identisch und es ist egal, ob Entwickler float f oder Single f schreiben. Doch Single (respektive float) hat im Vergleich zu Smalltalk keine mathematischen Operationen, aber dennoch ein paar wenige Methoden wie ToString()[3]. In .NET verhalten sich folglich die eingebauten Datentypen wie Objekte, sie haben Methoden, haben aber die gleiche Wertsemantik zum Beispiel bei Methodenaufrufen wie in Java, und sehen auch im Bytecode ähnlich aus, was ihnen die gleiche gute Performance gibt.


    [1] Die Dokumentation für das GNU Smalltalk zeigt auf: http://www.gnu.org/software/smalltalk/manual-base/html_node/Number_002darithmetic.html#Number_002darithmetic

    [2] http://www.gnu.org/software/smalltalk/manual-base/html_node/Integer.html.

    [3] Siehe http://msdn.microsoft.com/en-us/library/system.int32_members(v=vs.71).aspx.

    Besondere Rückgaben oder Ausnahmen?

    Nicht immer ist eine Ausnahme nötig, doch wann es eine Rückgabe wie null oder -1 gibt, und wann eine Ausnahme ausgelöst werden soll, ist nicht immer einfach zu beantworten und hängt vom Kontext ab. Ein Beispiel: Eine Methode liest eine Datei ein und führt eine Suche durch. Wenn eine bestimmte Teilzeichenkette nicht vorhanden ist, soll die Methode dann eine Ausnahme werfen oder nicht? Hier kommt es drauf an.

    1. Wenn das Dokument in der ersten Zeile eine Kennung tragen muss und der Test prüft auf diese Kennung, dann liegt ein Protokollfehler vor, wenn diese Kennung nicht vorhanden ist.

    2. Im Dokument gibt es eine einfache Textsuche. Ein Suchwort kann enthalten sein, muss aber nicht.

    Im ersten Fall passt eine Ausnahme gut, da ein interner Fehler vorliegt. Muss die Kennung in der Datei sein, ist sie aber nicht, darf dieser Fehler nicht untergehen und eine Ausnahme zeigt das perfekt an. Ob geprüft oder ungeprüft steht auf einem anderen Blatt. Im zweiten Fall ist eine Ausnahme unangebracht, da es kein Fehler ist, wenn der Suchstring nicht im Dokument ist; das kann vorkommen. Das ist das gleiche wie bei indexOf() oder matches() von String – die Methoden würden ja auch keine Ausnahmen werfen, wenn es keine Übereinstimmung gibt.

    Überlaufe

    Bei einigen mathematischen Fragestellungen muss sich feststellen lassen, ob Operationen wie die Addition, Subtraktion oder Multiplikation den Zahlenbereich sprengen, also etwa den Ganzzahlenbereich eines Integers von 32 Bit verlassen. Passt das Ergebnis einer Berechnung nicht in den Wertebereich einer Zahl, so wird dieser Fehler standardmäßig nicht von Java angezeigt; weder der Compiler noch die Laufzeitumgebung melden dieses Problem. Es gibt auch keine Ausnahme, Java hat keine eingebaute Überlaufkontrolle.

    Beispiel

    Mathematisch gilt a × a / a = a, also zum Beispiel 100 000 × 100 000 / 100 000 = 100 000. In Java ist das anders, da wir bei 100 000 × 100 000 einen Überlauf im int haben.

    System.out.println( 100000 * 100000 / 100000 );     // 14100

    liefert daher 14100. Wenn wir den Datentyp auf long erhöhen, indem wir hinter ein 100 000 ein L setzen, sind wir bei dieser Multiplikation noch sicher, da ein long das Ergebnis aufnehmen kann.

    System.out.println( 100000L * 100000 / 100000 );    // 100000

    Hinweis

    Ein Test auf Überlauf könnte aussehen: boolean canMultiply(int a, int b) { return a * b / b == a; } reichen. Doch eine JVM kann das zu return a == a; optimieren und somit zu return true; machen, sodass der Test nicht funktioniert.

    Überlauf erkennen

    Für eine Operation wie die Addition oder Subtraktion lässt sich relativ leicht erkennen, ob das Ergebnis über das Ziel hinausschießt. Eine Möglichkeit ist, bei der Addition zweier ints diese erst auf long zu bringen und dann den long mit der Konstanten Integer.MAX_VALUE/Integer.MIN_VALUE zu vergleichen. Aber über die Interna brauchen wir uns keine großen Gedanken machen, denn ab Java 8 kommen neue Methoden hinzu, die eine Überlauferkennung ermöglichen. Die Methoden gibt es in Math und StrictMath:

    · static int addExact(int x, int y)

    · static long addExact(long x, long y)

    · static int subtractExact(int x, int y)

    · static long subtractExact(long x, long y)

    · static int multiplyExact(int x, int y)

    · static long multiplyExact(long x, long y)

    · static int toIntExact(long value)

    Alle Methoden werfen eine ArithmeticException, falls die Operation nicht durchführbar ist, die letzte, wenn (int)value != value ist. Leider deklariert Java keine Unterklassen wie UnderflowException oder OverflowException, und Java meldet nur alles vom Typ ArithmeticException mit der Fehlermeldung „xxx overflow“, auch wenn es eigentlich ein Unterlauf ist:

    System.out.println( subtractExact( Integer.MIN_VALUE, 1 ) ); // ArithmeticException

    Inselupdate: Vorzeichenlos arbeiten

    Bis auf char sind in Java alle integralen Datentypen vorzeichenbehaftet und kodiert im Zweierkomplement. Bei einem byte stehen 8 Bit für die Kodierung eines Wertes zur Verfügung, jedoch sind es eigentlich nur 7 Bit, denn über ein Bit erfolgt die Kodierung des Vorzeichens. Der Wertebereich ist von -128 bis +127. Über einen Umweg ist es möglich, den vollen Wertebereich auszuschöpfen und so zu tun, als ob Java vorzeichenlose Datentypen hätte.

    byte als vorzeichenlosen Datentyp nutzen

    Eine wichtige Eigenschaft der expliziten Typanpassung bei Ganzzahltypen ist es, dass die überschüssigen Bytes einfach abgeschnitten werden. Betrachten wir die Typanpassung an einem Beispiel:

    int l = 0xABCD6F;

    byte b = (byte) 0xABCD6F;

    System.out.println( Integer.toBinaryString( l ) ); // 101010111100110101101111

    System.out.println( Integer.toBinaryString( b ) ); // 1101111

    Liegt eine Zahl im Bereich von 0 bis 255, so kann ein byte diese durch seine 8 Bit grundsätzlich speichern. Java muss jedoch mit der expliziten Typanpassung gezwungen werden, das Vorzeichenbit zu ignorieren. Erst dann entspricht die Zahl 255 acht gesetzten Bits, denn sie mit byte b = 255; zu belegen, funktioniert nicht.

    Damit die Weiterverarbeitung gelingt, muss noch eine andere Eigenschaft berücksichtigt werden. Sehen wir uns dazu folgende Ausgabe an:

    byte b1 = (byte) 255;

    byte b2 = -1;

    System.out.println( b1 ); // -1

    System.out.println( b2 ); // -1

    Das Bitmuster ist in beiden Fällen gleich, alle Bits sind gesetzt. Dass die Konsolenausgabe aber negativ ist, hat mit einer anderen Java-Eigenschaft zu tun: Java konvertiert das Byte, welches vorzeichenbehaftet ist, in ein int (der Parametertyp bei toBinaryString() ist int) und bei dieser Konvertierung wandert das Vorzeichen weiter. Das folgende Beispiel zeigt das bei der Binärausgabe:

    byte b = (byte) 255;
    int  i = 255;
    System.out.printf( "%d %s%n", b, Integer.toBinaryString(b) );

    // –1  11111111111111111111111111111111
    System.out.printf( "%d %s%n", i, Integer.toBinaryString(i) );

    // 255                         11111111

    Die Belegung der unteren 8 Bit vom byte b und int i ist identisch. Aber während beim int die oberen 3 Byte wirklich null sind, füllt Java durch die automatische Anpassung des Vorzeichens bei der Konvertierung von byte nach int im Zweierkomplement auf die oberen drei Byte mit 255 auf. Soll ohne Vorzeichen weitergerechnet werden stört das. Diese Automatisch Anpassung nimmt Java immer vor, wenn mit byte/short gerechnet wird und nicht nur wie in unserem Beispiel, wenn eine Methoden den Datentyp int fordert.

    Um bei der Weiterverarbeitung einen Datenwert zwischen 0 und 255 zu bekommen, also das Byte eines int vorzeichenlos zu sehen, schneiden wir mit der Und-Verknüpfung die unteren 8 Bit heraus – alle anderen Bits bleiben also ausgenommen:

    byte b = (byte) 255;

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

    System.out.println( b & 0xff ); // 255

    Bibliotheksmethoden für vorzeichenlose Behandlung

    Immer ein & 0xff an einen Ausdruck zu setzen um die oberen Bytes auszublenden ist zwar nicht sonderlich aufwändig, aber schön ist das auch nicht. Hübscher sind Methoden wie toUnsignedInt(byte), die mit dem Namen deutlich dokumentieren, was hier eigentlich passiert. In Java 8 gibt es daher einige Neuerungen.

    Neue Methoden in Byte:

  • static int toUnsignedInt(byte x)
  • static long toUnsignedLong(byte x)
  • In Integer:

  • static long toUnsignedLong(int x)
  • static String toUnsignedString(int i, int radix)
  • static String toUnsignedString(int i)
  • static int parseUnsignedInt(String s, int radix)
  • static int compareUnsigned(int x, int y)
  • static int divideUnsigned(int dividend, int divisor)
  • static int remainderUnsigned(int dividend, int divisor)
  • In Long:

  • String toUnsignedString(long i, int radix)
  • static String toUnsignedString(long i)
  • static long parseUnsignedLong(String s, int radix)
  • static int compareUnsigned(long x, long y)
  • static long divideUnsigned(long dividend, long divisor)
  • static long remainderUnsigned(long dividend, long divisor)
  • In Short:

  • static int toUnsignedInt(short x)
  • static long toUnsignedLong(short x)
  • Neben den einfachen Methoden toUnsignedXXX()-Methoden in den Wrapper-Klassen gesellen sich Methoden hinzu, die auch die Konvertierung in einem String bzw. das Parsen eines Strings ermöglichen. Bei Integer und Long lassen sich ebenfalls neue Methoden ablesen, die Vergleiche, Division und Restwertbildung vorzeichenlos durchführen.

    Echte typsichere Container

    Verwenden Entwickler die Sammlungen untypisiert, also mit dem Raw-Typ, so lässt sich nicht verhindern, dass ein ungewünschter Typ die Sammlung betritt und beim Entnehmen zu einer Ausnahme führen kann. Der Grund liegt in der internen Umsetzung, dass nur der Compiler selbst die Typsicherheit sicherstellt, aber nicht die Laufzeitumgebung. Um die Typsicherheit zu erhöhen, bietet die Collections-Klasse ein paar Wrapper-Methoden, die eine Sammlung nehmen und Operationen nur eines gewissen Typs durchlassen – verstoßt ein Aufrufer dagegen, gibt es eine ClassCastException.

    class java.util.Collections

    § static <E> Collection<E> checkedCollection(Collection<E> c, Class<E> type)

    § static <E> List<E> checkedList(List<E> list, Class<E> type)

    § static <K,V> Map<K,V> checkedMap(Map<K,V> m, Class<K> keyType, Class<V> valueType)

    § static <E> Queue<E> checkedQueue(Queue<E> queue, Class<E> type)

    § static <E> Set<E> checkedSet(Set<E> s, Class<E> type)

    § static <K,V> SortedMap<K,V> checkedSortedMap(SortedMap<K,V> m, Class<K> keyType, Class<V> valueType)

    § static <E> SortedSet<E> checkedSortedSet(SortedSet<E> s, Class<E> type)

    Beispiel: Lasse in eine Menge nur Strings, aber nichts anderes.

    Set<String> set = Collections.checkedSet( new HashSet<String>(),

    String.class );

    set.add( "xyz" ); // Compiler OK

    Set rawset = set;

    rawset.add( "abc" ); // Compiler OK

    rawset.add( new Object() ); // Compiler OK,  N Laufzeitfehler

    Die Ausnahme ist: “Exception in thread "main" java.lang.ClassCastException: Attempt to insert class java.lang.Object element into collection with element type class java.lang.String”

    Auch wenn kein Programmierer freiwillig falsche Typen in eine Sammlung platziert, ist es doch besser, eine absolute Sicherheit zu bekommen, die nicht auf dem Compiler beruht. Wenn etwa ein Programm einer Skriptsprache die Möglichkeit eröffnet Elemente in eine Datenstruktur zu setzen, so lässt sich für die Skriptsprache eine geprüfte Sammlung zur Ablage nach außen geben. Achtet das Skript nicht auf den richtigen Typ, so knallt es im Skript. Das ist gewünscht, denn andernfalls würde viel später erst der falsche Typ bei der Bearbeitung auffallen und dann knallt es an der ganz falschen Stelle.

    JDK-Interna: Removes the use of shared character array buffers by String along with the two fields needed to support the use of shared buffers.

    Eine recht fette Änderung, die großen Einfluss auf die Performace von substring-Operationen hat. Denn die ist nun nicht mehr so schnell wir früher O(1), sondern O(length()). Dafür spart man Speicher bei jedem String-Objekt, und davon gibt es zur Laufzeit einer normalen Anwendung wirklich viele. Reduktion: zwei ints, also 2 x 4 Byte pro String-Objekt.

    Implementierung: http://hg.openjdk.java.net/jdk8/tl/jdk/rev/2c773daa825d

    File-Klassen auf NIO.2 umstellen

    Um sich von der Klasse java.io.File zu lösen und in Richtung Path zu migrieren, ist Handarbeit angesagt. Zunächst gilt es, alle Stellen zu finden, die im Workspace oder Projekt die Klasse File referenzieren. Am Einfachsten ist es, eine Anweisung wie File f; in den Code zu setzen, dann zum Beispiel in Eclipse Strg+Shift+G zu aktivieren. Es folgt eine Liste aller Vorkommen. Diese Liste muss nun abgearbeitet werden und der Code auf NIO.2 konvertiert werden. Nicht immer ist es so einfach

    Scanner s = new Scanner( new File(dateiname) );

    in

    Scanner s = new Scanner( Paths.get(dateiname) );

    umzusetzen oder

    new File( dateiname ).delete();

    in

    Files.delete( Paths.get( dateiname ) );

    Eine andere Stelle, an der Konvertierungen möglich sind, betreffen FileReader und FileWriter. Diese Klassen sind gefährlich, weil sie standardmäßig die im System voreingestellte Kodierung verwenden. Eine Kodierung wie UTF-8 im Konstruktor explizit vorzugeben ist jedoch nicht möglich. NIO.2 bietet eine bessere Methode, um an einen Reader/Writer aus einer Datei zu kommen und Entwickler werden gezwungen, die Kodierung immer sichtbar anzugeben.

    Deklaration

    Klassisch

    Mit NIO.2

    Reader r =

    new FileReader(f);

    Files.newBufferedReader(Paths.get(f),

    StandardCharsets.ISO_8859_1);

    Writer w =

    new FileWriter(f);

    Files.newBufferedWriter(Paths.get(f),

    StandardCharsets.ISO_8859_1);

    Beziehen eines Dateistroms klassisch und mit NIO.2

    Der Quellcode wird erst einmal länger, doch der Gewinn ist, dass die Kodierung nicht verschwindet und auch die Ein-/Ausgabe gleich gepuffert ist. Freunde der statischen Import-Anweisung komprimieren weiterhin zu Anweisungen wie

    Reader r = newBufferedReader( get( f ), ISO_8859_1 );

    Es ist auf den Fall Handarbeit angesagt. Eigene Blöcke, etwa zum Schreiben in Dateien können komplett zusammengestrichen werden auf einfache Methodenaufrufe wie

    Files.write( Paths.get( f ), bytes );

    List<String> lines = Files.readAllLines( Paths.get( f ), StandardCharsets.ISO_8859_1 );

    Hilfe bei der Migration auf Java 7 mit NetBeans und Eclipse

    Wer eine große Codebasis auf Java 7 migrieren möchte, steht vor dem Programm, wie das ohne großen manuellen Aufwand funktionieren kann. Als erstes steht die Umsetzung auf die neuen Syntax-Möglichkeiten an. Das ist ein echter Mehrwert, bei den Bibliotheken gibt es in dem Sinne keine Umsetzung von alt nach neu, die File-Klasse ist nicht als veraltet markiert, wobei es durchaus interessant ist, auch File-Operationen durch die NIO.2-Operationen zu ersetzen. Schauen wir uns an, welche Möglichkeiten die modernen IDEs bieten.

    Migration mit NetBeans

    Die aktuelle NetBeans IDE bringt einen Code-Transformer mit, der elegant die Code-Basis durchsucht und Transformationen automatisch durchführt. Das Menü Refactor bietet den Unterpunkt Inspect and Transform…, der zu einem Dialog führt.

    Hier lässt sich bei Configuration auswählen, ob imports organisiert, oder in Java 7 transformiert werden soll. Unter Manage… öffnet sich ein weiter Dialog, der im Baum JDK 1.5 and later die erkannten Muster anzeigt. Für Java 7 ist dies Can use Diamond, Convert to try-with-resources, Join catch sections using mulitcatch, Use specific catch und Use switch over Strings when possible.

    Wer neu Programmcode mit NetBeans schreibt wird gleich auf die neue Syntax hingewiesen. Vervollständigungen nutzen ganz natürlich zum Beispiel den Diamond-Operator.

    Migration mit Eclipse

    Eclipse integriert kein Werkzeug, um die Code-Basis automatisch in die Syntax von Java 7 zu konvertieren. Seit der Version Eclipse 3.7 sind jedoch Transformationen über die Quick-Fix/Quick-Assist möglich, was es erlaubt, Generics zu mit dem Diamanten zu verkürzen, Ausnahme-Anweisungen mit einem Multi-Catch zu umrahmen, gleiche Catch-Blöcke mit einem Multi-Catch zusammenzufassen und AutoCloseable-Resourcen mit einem try-mit-Resourcen zu schließen. Allerdings setzt Eclipse bis auf eine mögliche Diamanten-Umsetzung keine Warnung ein. Und diese Warnung ist standardmäßig abgeschaltet und muss aktiviert werden. Dazu wählte Window > Preferences im Baum Java > Compiler > Errors/Warning rechts in der Rubrik Generic Type bei Redundant type arguments statt Ignore dann Warning.

    Jobangebot Java-Entwickler

    Für den Einsatz bei einem Kunden wird benötigt ein versierter Java-Entwickler (m/w) (bereits mit ausreichender Projekterfahrung, Erfahrungen in Anwendungsdesign und mit Architektur-Verständnis) mit folgenden techn. Kenntnissen:

    • Java (logisch)
    • Hibernate
    • Subversion (Erfahrungen wären gut, nicht zwingend)
    • Ant
    • Swing
    • Framework: Wings (Erfahrungen wären gut, nicht zwingend)
    • Framework: Vaadin (Erfahrungen wären gut, nicht zwingend)
    • Datenbank MySQL
    • Einsatz: süddeutscher Raum
    • ab sofort, ½ Jahr bis länger

    Bewerbungen bitte direkt an elke.roetzer<ÄT>md-consulting<PUNKT>de schicken.

    Inselraus: Swing/Auswahlmenü mit Separator

    Standardmäßig unterstützt die JList und auch JComboBox keine Separatoren, doch die Unterteilung in Segmente ist in der Praxis sehr nützlich. Microsoft Word und PowerPoint benutzen sie zum Beispiel, um die zuletzt vom Benutzer ausgewählten Zeichensätze prominent oben in der Liste zu haben (Excel dagegen tut dies nicht).

    Abbildung

    Abbildung 9.42: Separator in Words Zeichensatzauswahlliste

    Wir wollen diese Möglichkeit nachbilden und dabei noch einiges über Modelle und Renderer lernen.

    Abbildung

    Abbildung 9.43: Eine eigene Variante mit JSeparator in JComboBox

    Bei der Umsetzung gibt es unterschiedliche Varianten, die sich außer in der technischen Implementierung darin unterscheiden, ob das Modell eine Markierung für den Separator enthält oder nicht. Wir stellen beide Ansätze vor und beginnen mit der ersten Variante, also damit, einem Zellen-Renderer Positionen mitzugeben, die sagen, wo ein Trennstrich zu zeichnen ist.

    package com.tutego.insel.ui.list;
    
    
    import javax.swing.*;
    
    
    public class JComboBoxWithSeparator1 {
      public static void main( String[] args ) throws Exception {
        UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        String[] items = { "Cambria", "Arial", "Verdana", "Times" };
        JComboBox<String> comboBox = new JComboBox<>( items );
        ListCellRenderer<String> renderer = new SeparatorAwareListCellRenderer1<>(
                                              comboBox.getRenderer(), 0 );
        comboBox.setRenderer( renderer );
        frame.add( comboBox );
        frame.pack();
        frame.setVisible( true );
      }
    }

    Die eigene Klasse SeparatorAwareListCellRenderer1 ist ein ListCellRenderer, den die JComboBox zur Darstellung der Komponenten nutzt. Im Konstruktor des Renderers geben wir den Original-Renderer mit – es kann ein bestimmter Renderer schon vorinstalliert sein, den wollen wir dekorieren – sowie eine variable Argumentliste von Positionen. Das Beispiel übergibt nur 0, da nach dem ersten Element (Index = 0) ein Trennzeichen zu setzen sein soll, sodass Cambria und Arial abgetrennt sind.

    package com.tutego.insel.ui.list;
    
    
    import java.awt.*;
    import java.util.Arrays;
    
    
    import javax.swing.*;
    
    
    public class SeparatorAwareListCellRenderer1<E> implements ListCellRenderer<E>
    {
      private final ListCellRenderer<? super E> delegate;
      private final int[] indexes;
      private final JPanel panel = new JPanel( new BorderLayout() );
    
    
      public SeparatorAwareListCellRenderer1( ListCellRenderer<? super E> delegate,
                                              int... indexes )
      {
        Arrays.sort( indexes );
        this.delegate = delegate;
        this.indexes  = indexes;
      }
    
    
      @Override
      public Component getListCellRendererComponent( JList<? extends E> list, E value,
                                                     int index, boolean isSelected,
                                                     boolean cellHasFocus )
      {
        panel.removeAll();
        panel.add( delegate.getListCellRendererComponent( list, value, index,
                                                          isSelected, cellHasFocus ) );
        if ( Arrays.binarySearch( indexes, index ) >= 0 )
          panel.add( new JSeparator(), BorderLayout.PAGE_END );
    
    
        return panel;
      }
    }

    Die Implementierung basiert auf der Idee, jede Komponente in einen Container (JPanel) zu setzen und in diesem Container dann je nach Bedarf ein JSeparatorunten an diesem Container anzufügen. Statt JPanel mit einem JSeparator auszustatten, kann ebenfalls auch ein Border unten gezeichnet werden. Die AnweisungArrays.binarySearch(indexes, index) >= 0 ist als »contains« zu verstehen, also als ein Test, ob der Index im Feld ist – leider gibt es so eine Methode nicht in der Java API. Wenn der Index im Feld ist, soll der Separator unter der Komponente erscheinen. Dass sich eine Trennlinie auch am Anfang befinden kann, berücksichtigt die Lösung nicht; diese Variante bleibt als Übungsaufgabe für die Leser.

    Diese Lösung ist einfach und funktioniert gut, denn vorhandene Renderer werden weiterverwendet, was sehr wichtig ist, denn größere Swing-Anwendungen nutzen viele eigene Renderer, etwa um Icons und Text zusammenzufassen. Ein Nachteil ist, dass der Separator zu einen Element gehört, und wenn das Element etwa in der Liste ausgewählt wird, steht der Separator mit in der Selektion und ist nicht abgetrennt (das ist aber bei Word genauso).

    Während die vorgestellte Variante in der Praxis gut funktioniert, wollen wir uns noch mit einer alternativen Umsetzung beschäftigen. Sie ist deutlich komplexer und auch nicht so flexibel. Die Lösung basiert auf der Idee, dass die Modelldaten eine Markierung für den Separator enthalten – die folgende Lösung nutzt null dafür. DaJList oder JComboBox eine null problemlos verträgt, ist die Basis schnell umgesetzt:

    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    String[] items = { "Cambria", null, "Arial", "Cambria", "Verdana", "Times" };
    JComboBox<String> comboBox = new JComboBox<String>( items );
    frame.add( comboBox );
    frame.pack();
    frame.setVisible( true );

    Wenn wir das Programm starten, zeigt es dort, wo das Model eine null liefert, einfach nichts an. Die Selektion dieses null-Elements ist auch möglich und führt zu keiner Ausnahme.

    Damit Swing das null-Element nicht als Leereintrag anzeigt, verpassen wir unserer JComboBox einen Zellen-Renderer. Der soll immer dann, wenn das Element nullist, ein Exemplar von JSeparator zeichnen. Vom Design ist es das beste, wenn der Zell-Renderer selbst die null-Elemente darstellt, aber das Zeichnen der Nicht-null-Elemente an einen anderen Zell-Renderer abgibt, anstatt diese etwa durch einen BasicComboBoxRenderer (ein JLabel, das ListCellRendererimplementiert) selbst zu renderen – das würde die Flexibilität der Lösung massiv einschränken.

    package com.tutego.insel.ui.list;
    
    
    import java.awt.Component;
    import javax.swing.*;
    
    
    public class SeparatorAwareListCellRenderer2<E> implements ListCellRenderer<E>
    {
      private final ListCellRenderer<? super E> delegate;
    
    
      public SeparatorAwareListCellRenderer2( ListCellRenderer<? super E> delegate )
      {
        this.delegate = delegate;
      }
    
    
      @Override
      public Component getListCellRendererComponent( JList<? extends E> list, E value,
                                                     int index, boolean isSelected,
                                                     boolean cellHasFocus )
      {
        if ( value == null )
          return new JSeparator();
    
    
        return delegate.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );
      }
    }

    Die eigene Klasse SeparatorAwareListCellRenderer2 bekommt einen anderen ListCellRenderer übergeben und liefert die Komponenten dieses Delegate-Renderers immer dann, wenn das Element ungleich null ist. Ist es dagegen gleich null, liefert getListCellRendererComponent() ein Exemplar des JSeparators.

    Im Beispielprogramm muss der Renderer nun angemeldet werden, und dazu ist Folgendes zu ergänzen:

    comboBox.setRenderer(
      new SeparatorAwareListCellRenderer2<String>(comboBox.getRenderer()) );

    Nach dem Start des Programms ist das Ergebnis schon viel besser: Dort, wo vorher die JComboBox eine leere Zeile darstellte, ist nun ein Strich.

    Die Lösung ist jedoch nur ein Etappensieg, denn die Navigation mit der Tastatur durch die Liste zeigt eine Schwachstelle: Das null-Element lässt sich auswählen und erscheint auch als Linie im Editor/Textfeld. Beheben lässt sich das Problem nicht mit dem Renderer, denn der ist nur mit der Darstellung in der Liste beauftragt. Hier muss in die Interna der Swing-Komponente eingegriffen werden. Jede Swing-Komponente hat ein korrespondierendes UI-Delegate, das für das Verhalten und die Darstellung der Komponente verantwortlich ist. Für die JComboBox sind das Unterklassen von ComboBoxUI, und zwei Methoden sind besonders interessant:selectNextPossibleValue() und selectPreviousPossibleValue().

    Da jede UI-Implementierung ihr eigenes Look and Feel (LAF) mitbringt, müssen wir hier eigentlich einen Dekorator bauen und jede Methode bis auf die beiden genannten an das Original weiterleiten, doch das ist jetzt zu viel Arbeit, und so nehmen wir die Basisklasse WindowsComboBoxUI als Basisklasse, denn unser Beispiel nutzt das Windows-LAF. In der Unterklasse implementieren wir eigene Versionen der Methoden selectNextPossibleValue() undselectPreviousPossibleValue(), die so lange die Liste nach oben/unten laufen müssen, bis sie ein Element ungleich null finden.

    package com.tutego.insel.ui.list;
    
    
    import com.sun.java.swing.plaf.windows.WindowsComboBoxUI;
    
    
    public class SeparatorAwareComboBoxUI extends WindowsComboBoxUI
    {
      @Override
      protected void selectNextPossibleValue()
      {
        for ( int index = comboBox.getSelectedIndex() + 1;
              index < comboBox.getItemCount();
              index++ )
          if ( comboBox.getItemAt( index ) != null )
          {
            comboBox.setSelectedIndex( index );
            break;
          }
      }
    
    
      @Override
      protected void selectPreviousPossibleValue()
      {
        for ( int index = comboBox.getSelectedIndex() - 1;
              index >= 0;
              index-- )
          if ( comboBox.getItemAt( index ) != null )
          {
            comboBox.setSelectedIndex( index );
            break;
          }
      }
    }

    Das UI-Objekt muss ebenfalls im main()-Programm angemeldet werden:

    comboBox.setUI( new SeparatorAwareComboBoxUI() );

    Enthält die Liste null-Elemente, überspringt die Tastennavigation über die Cursor-Taste diese. Doch auch mit diesem Teilstück fehlt ein weiteres Detail: Mit einem Klick lässt sich die Linie doch noch auswählen. Das ist keine Frage des Renderers und auch keine Frage der Tastaturnavigation – es muss untersagt werden, dass bei der Aktivierung ein null-Element zum Editor kommen kann. Hier ist die Methode setSelectedItem() von JComboBox entscheidend. Denn jedes Element, das selektiert wird – und dadurch auch in das Textfeld kommt –, geht durch die Methode hindurch. Wenn wir die Methode überschreiben und bei null-Elementen einfach nichts tun, wird auch das null-Element nicht selektiert, und im Textfeld bleibt das letzte Element stehen.

    Damit auch spezielle Implementierungen von JComboBox von diesem Verhalten profitieren können, müssen wir wieder einen Dekorator schreiben, doch das kostet zu viel Mühe, und so überschreibt eine einfache Unterklasse die setSelectedItem()-Methode. (Im Prinzip wäre auch eine überschriebene Methode vonsetSelectedIndex() sinnvoll, denn das könnte eine programmierte Aktivierung von null vermeiden.)

    package com.tutego.insel.ui.list;
    
    
    import javax.swing.*;
    
    
    public class SeparatorAwareJComboBox<E> extends JComboBox<E>
    {
      @SafeVarargs
      public SeparatorAwareJComboBox( E... items )
      {
        super( items );
      }
    
    
      @Override
      public void setSelectedItem( Object anObject )
      {
        if ( anObject != null )
          super.setSelectedItem( anObject );
      }
    }

    Im Hauptprogramm muss nur diese spezielle Klasse zum Einsatz kommen und so folgt:

    package com.tutego.insel.ui.list;
    
    
    import javax.swing.*;
    
    
    public class JComboBoxWithSeparator2
    {
      public static void main( String[] args ) throws Exception
      {
        UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
    //    UIManager.setLookAndFeel( new NimbusLookAndFeel() );
    
    
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        String[] items = { "Cambria", null, "Arial", "Verdana", "Times" };
        JComboBox<String> comboBox = new SeparatorAwareJComboBox<>( items );
        comboBox.setRenderer( new SeparatorAwareListCellRenderer2<>(comboBox.getRenderer()) );
        comboBox.setUI( new SeparatorAwareComboBoxUI() );
        comboBox.setSelectedIndex( 1 );
        frame.add( comboBox );
        frame.pack();
        frame.setVisible( true );
      }
    }

    Inselraus: Variablen mit Xor vertauschen

    Eine besonders trickreiche Idee für das Vertauschen von Variableninhalten arbeitet mit dem Xor-Operator und benötigt keine temporäre Zwischenvariable. Die Zeilen zum Vertauschen von x und y lauten wie folgt:

    int x = 12, 
      
        y = 49;
    x ^= y; // x = x ^ y = 001100bin ^ 110001bin = 111101bin
    y ^= x; // y = y ^ x = 110001bin ^ 111101bin = 001100bin
    x ^= y; // x = x ^ y = 111101bin ^ 001100bin = 110001bin
    System.out.println( x + " " + y );  // Ausgabe ist: 49 12

    Der Trick funktioniert, da wir mit Xor etwas »hinein- und herausrechnen« können. Zuerst rechnet die erste Zeile das y in das x. Wenn wir anschließend die Zuweisung an das y machen, dann ist das der letzte schreibende Zugriff auf y, also muss hier schon das vertauschte Ergebnis stehen. Das stimmt auch, denn expandieren wir die zweite Zeile, steht dort: »y ^ x wird zugewiesen an y«, und dies ist y ^ (x ^ y). Der letzte Ausdruck verkürzt sich zu y = x, da aus der Definition des Xor-Operators für einen Wert a hervorgeht: a ^ a = 0. Die Zuweisung hätten wir zwar gleich so schreiben können, aber dann wäre der Wert von y verloren gegangen. Der steckt aber noch in x aus der ersten Zuweisung. Betrachten wir daher die letzte Zeile x ^ y: y hat den Startwert von x, doch in x steckt ein Xor-y. Daher ergibt x ^ y den Wert x ^ x ^ y, und der verkürzt sich zu y. Demnach haben wir den Inhalt der Variablen vertauscht. Im Übrigen können wir für die drei Xor-Zeilen alternativ schreiben:

    y ^= x ^= y;   // Auswertung automatisch y ^= (x ^= y) 
      
    x ^= y;

    Da liegt es doch nahe, die Ausdrücke weiter abzukürzen zu x ^= y ^= x ^= y. Doch leider ist das falsch (es kommt für x immer null heraus). Motivierten Lesern bleibt dies als Denksportaufgabe überlassen.

    GMail-URLs generieren

    private static String createGMailUrl( String to, String cc, String subject, String body )
    
    {
    
      StringBuilder result = new StringBuilder( "https://mail.google.com/mail/?view=cm&tf=0" );
    
      
    
      if ( to != null && ! to.isEmpty() )
    
        result.append( "&to=" ).append( to );
    
      if ( cc != null && ! cc.isEmpty() )
    
        result.append( "&cc=" ).append( cc );
    
      if ( subject != null && ! subject.isEmpty() ) {
    
        try {
    
          result.append( "&su=" ).append( encode( subject ) );
    
        }
    
        catch ( UnsupportedEncodingException e ) {
    
          e.printStackTrace();
    
        }
    
      }
    
      if ( body != null && ! body.isEmpty() ) {
    
        try {
    
          result.append( "&body=" ).append( encode( body ) );
    
        }
    
        catch ( UnsupportedEncodingException e ) {
    
          e.printStackTrace();
    
        }
    
      }
    
      return result.toString();
    
    }
    
    private static String encode( String param ) throws UnsupportedEncodingException
    
    {    
    
      byte[] utf8 = param.getBytes( "utf-8" );
    
      StringBuilder result = new StringBuilder( param.length() * 3 );
    
      for ( int i = 0; i < utf8.length; i++ )
    
      {
    
        String c = Integer.toString( utf8[ i ] & 0xFF, 16 );
    
        result.append( '%' ).append( c.length() == 1 ? "0" + c : c );
    
      }
    
      return result.toString();
    
    }