Prädikate und java.util.function.Predicate

Ein Prädikat ist eine Aussage über einen Gegenstand, die wahr oder falsch. Die Frage mit Character.isDigit(‚a‘), ob das Zeichen „a“ eine Ziffer ist, wird mit falsch beantwortet – isDigit ist also ein Prädikat, weil es über einen Gegenstand, einem Zeichen, eine Wahrheitsaussage fällen kann.

Flexibler sind Prädikate, wenn sie als Objekte repräsentiert werden, weil sie dann an unterschiedliche Stellen weitergegeben werden können, wenn etwa über ein Prädikat bestimmt, was aus einer Sammlung gelöscht werden soll oder ob mindestens ein Element in einer Sammlung ist, was ein Prädikat erfüllt.

Das java.util.function-Paket deklariert eine flexible funktionale Schnittstelle Predicate auf folgende Weise:

interface java.util.function.Predicate<T>

* boolean test(T t)
   Führt einen Test auf t durch und liefert true, wenn das Kriterium erfüllt ist.

Beispiel

Der Test, ob ein Zeichen eine Ziffer ist, kann durch Prädikat-Objekte nun auch anders durchgeführt werden:

Predicate<Character> isDigit = (Character c) -> Character.isDigit( c );

System.out.println( isDigit.test(‚a‘) ); // false

Hätte es die Schnittstelle Predicate schon früher in Java 1.0 gegeben, hätte es einer der Methode Character.isDigit(…) gar nicht bedurft, es hätte auch ein Predicate als statische Variable in Character geben können, so dass ein Test dann geschrieben würde als Character.IS_DIGIT.test(…) oder als Rückgabe von einer Methode isDigit(), mit der Nutzung Character.isDigit().test(…). Es ist daher gut möglich, dass sich in Zukunft die API dahingehend verändert, dass Aussagen auf Gegenständen mit Wahrheitsrückgabe nicht mehr als Methoden bei den Klassen realisiert werden, sondern als Prädikat-Objekte angeboten werden. Aber Methoden-Referenzen geben zum Glück die Flexibilität, dass problemlos Methoden als Lambda-Ausdrücke genützt werden können und so kommen wir wieder von Methoden zu Funktionen.

Typ Predicate in der API

Es gibt in der Java-API vier Stellen, an denen Prediate-Objekte genutzt werden:

· Als Argument für Lösch-Methoden, um in Sammlungen Elemente zu spezifizieren, die gelöscht werden sollen.

· Bei den Default-Methoden der Predicate-Schnittstelle selbst, um Prädikate zu verknüpfen.

· Bei regulären Ausdrücken, um ein Pattern als Predicate nutzen zu können.

· In der Stream-API, bei der Objekte beim Durchlaufen des Stroms über ein Prädikat identifiziert werden, um sie etwa auszufiltern.

Beispiel

Lösche aus einer Liste mit Zeichen alle die, die Ziffern sind (es bleiben nur Zeichen übrig, etwa Buchstaben).

Predicate<Character> isDigit = (Character c) -> Character.isDigit( c );

List<Character> list = new ArrayList( Arrays.asList( ‚a‘, ‚1‘ ) );

list.removeIf( isDigit );

Default-Methoden von Predicate

Es gibt eine Reihe von Default-Methoden, die die funktionale Schnittstelle Predicate anbietet. Zusammenfassend:

interface java.util.function.Predicate<T>

default Predicate<T> negate()

default Predicate<T> and(Predicate<? super T> p)

default Predicate<T> or(Predicate<? super T> p)

default Predicate<T> xor(Predicate<? super T> p)

Die Methodennamen sprechen für sich.

Beispiel

Lösche aus einer Liste mit Zeichen alle die, die keine Ziffern sind.

Predicate<Character> isDigit = (Character c) -> Character.isDigit( c );

Predicate<Character> isNotDigit = isDigit.negate();

List<Character> list = new ArrayList( Arrays.asList( ‚a‘, ‚1‘ ) );

list.removeIf( isNotDigit );

Funktionale Programmierung in Java am Beispiel vom Comparator

Funktionale Programmierung hat auch daher etwas akademisches, weil in den Köpfen der Entwickler oftmals dieses Programmierparadigma nur mit mathematischen Funktionen in Verbindung gebracht wird. Und die wenigsten werden tatsächlich Fakultät oder Fibonacci-Zahlen in Programmen benötigen und daher schnell funktionale Programmierung beiseite legen. Doch diese Vorurteile sind unbegründet, und es ist hilfreich, gedanklich funktionale Programmierung von der Mathematik lösen, denn die allermeisten Programme haben nichts mit mathematischen Funktionen im eigentlichen Sinne zu tun.

Betrachten wir die Sortierung von Strings. Ein Comparator ist eine einfache Funktion, mit zwei Parametern und einer Rückgabe. Diese Funktion wiederum wird an die sort(…)-Methode übergeben. Alles das ist funktionale Programmierung, denn wir programmieren Funktionen und übergeben sie. Drei Beispiele (Generics ausgelassen):

Code

Bedeutung

Comparator c = (c1, c2) -> …

Implementiert eine Funktion

Arrays.sort(T[] a, Comparator c)

Nimmt eine Funktion als Argument an

Collections.reverseOrder(Comparator cmp)

Nimmt eine Funktion an und liefert auch eine zurück

Funktionen selbst können in Java nicht übergeben werden, also helfen sich Java-Entwickler mit der Möglichkeit, die Funktionalität in eine Methode zu kapseln, sodass die Funktion zum Objekt mit einer Methode wird, was die Logik realisiert. Lambda-Ausdrücke bzw. Methoden/Konstruktor-Referenzen geben eine kompakte Syntax.

Der Typ Comparator ist eine funktionale Schnittstelle und steht für eine besondere Funktion mit zwei Parametern gleichen Typs und einer Ganzzahl-Rückgabe. Es gibt weitere funktionale Schnittstellen, die etwas flexibler sind als Comparator, in der Weise, dass etwa die Rückgabe statt int auch double oder etwas anderes sein können.

Thema der Woche: Paketierung mit <fx:deploy>

Seit Neustem kann man mit Java auch ausführbare Dateien bzw. Installer bauen. Ließ dazu http://docs.oracle.com/javafx/2/deployment/self-contained-packaging.htm bzw. suche nach weiterer Dokumentation im Netz.

  • Teste das an einer eigenen kleinen Hello-World-Anwendung.
  • Wie groß ist das Ergebnis mit JRE?
  • Welche Zielformate sind möglich und kann man alle etwa auf einem Linux-Build-Server bauen?
  • Nutze log4j und nimm die Jar mit in das Zielformat mit auf. Lassen sich auch native Dateien einbinden?
  • Gilt diese Möglichkeit nur für JavaFX oder auch für für AWT/Swing oder SWT?

Java 8 könnte auf 18. März 2014 verschoben werden, wäre dann aber “Der Profi”

Bisher Vorschläge!

  • http://mail.openjdk.java.net/pipermail/jdk8-dev/2013-April/002336.html
  • http://mreinhold.org/blog/secure-the-train
  •   2013/05/09  M7  Feature Complete
      2013/07/18      Rampdown start
      2013/09/05  M8  Developer Preview
      2013/09/12      All Tests Run
      2013/10/10      API/Interface Freeze
      2013/10/24      Zero Bug Bounce
      2013/11/21      Rampdown phase 2
      2014/01/23  M9  Final Release Candidate
      2014/03/18  GA  General Availability

    Der 18.03. ist bestimmt kein Zufall, denn Luc Besson hat Geburtstag, und der ist uns ja in Erinnerung mit dem 1994er Klassiker “Der Profi”mit Jean Reno (nicht der mit Jean-Paul Belmondo, der war 1981). Damit wird Java 8 bestimmt auch so ein Profi im Bereich Sicherheit (naja, der Profi stirbt am Ende), sodass wir alle sagen können “Hier wird es uns gut gehen, Java”.

    Java 9 kommt dann in der ersten Hälfte von 2016. Oder irgendwann.

    Klassen mit einer abstrakten Methode als funktionale Schnittstelle in Java 8?

    Als die Entwickler Lambda-Ausdrücke diskutierten, stand auch die Frage im Raum, ob abstrakte Klassen, die nur über eine abstrakte Methode verfügen – früher wurde hier die Abkürzung SAM (Single Abstract Method) genutzt –, ebenfalls für Lambda-Ausdrücke genutzt werden können. Sie entschieden sich dagegen, da bei Implementierung von Schnittstellen die JVM weitreichende Optimierungen vornehmen kann. Und bei Klassen wir das schwierig, was auch daran liegt, dass ein Konstruktor umfangreiche Initialisierungen mit Seiteneffekten vornehmen (die Konstruktoren aller Oberklassen nicht zu vergessen) sowie Ausnahmen auslösen könnte. Gewünscht ist aber nur die Ausführung einer Implementierung der funktionalen Schnittstelle und kein anderer Code.

    Es gibt nun im JDK einige abstrakte Klassen, die genau eine abstrakte Methode vorschreiben, etwa jva.util.TimerTask. Solche Klassen können nicht über einen Lambda-Ausdruck realisiert werden; hier müssen Entwickler weiterhin zu Klassenimplementierungen greifen, und das kürzeste ist eine innere anonyme Klasse. Eigene Hilfsklassen können natürlich den Code etwas abkürzen, aber eben nur mit eigener Implementierung. Zwei Strategien bieten sich an: durch Delegation oder Vererbung. Nehmen wir das Beispiel für TimerTask und gehen beide Varianten durch:

    import java.util.*;

    class TimerTaskLambda {

    public static TimerTask createTimerTask( Runnable runnable ) {

    return new TimerTask() {

    @Override public void run() { runnable.run(); }

    };

    }

    public static void main( String[] args ) {

    new Timer().schedule( createTimerTask( () -> System.out.println("Hi") ), 500 );

    }

    }

    Oder mit Vererbung:

    public class LambdaTimerTask extends TimerTask {

    private final Runnable runnable;

    public LambdaTimerTask( Runnable runnable ) {

    this.runnable = runnable;

    }

    @Override public void run() { runnable.run(); }

    }

    Der Aufruf ist dann statt createTimerTask(…) der des Konstruktors:

    new Timer().schedule( new LambdaTimerTask( () -> System.out.println("Hi") ), 500 );

    Spaß mit Generics und funktionalen Schnittstellen

    Funktionale Schnittstellen müssen auf genau eine zu implementierende Methode hinauslaufen, auch wenn aus Oberschnittstellen mehrere Operationen vorgeschrieben werden, die aber durch Einsatz von Generics auf eine Operation verdichten:

    interface I<S, T extends CharSequence> {
    
      void len( S text );
    
      void len( T text );
    
    }
    
    interface FI extends I<String, String> { }

    FI ist unsere funktionale Schnittstelle, da die Signatur der Methode eindeutig ist: len(String).

    Rückgabetypen und Typ-Inferenz bei Lambda-Deklarationen

    Typinferenz spielt bei Lambda-Ausdrücken eine große Rolle – das gilt insbesondere für die Rückgabetypen, die überhaupt nicht in der Deklaration auftauchen. Bei unserem Beispiel

    Comparator<String> c = (String s1, String s2) -> { return s1.trim().compareTo( s2.trim() ); };

    ist String als Parametertyp der Comparator-Methode ausdrücklich gegeben, aber int taucht nicht auf.

    Mitunter muss dem Compiler etwas geholfen werden: Nehmen wir die funktionale Schnittstelle Supplier<T>, die eine Methode T get() deklariert, für ein Beispiel. Die Zuweisung

    Supplier<Long> two = () -> 2; // Compilerfehler

    ist nicht korrekt und führt zum Compilerfehler „incompatible types: bad return type in lambda expression“. 2 ist ein Literal vom Typ int und der Compiler kann es nicht an Long anpassen. Wir müssen schreiben:

    Supplier<Long> two = () -> 2L;

    oder

    Supplier<Long> two = () -> (long) 2;

    Bei Lambda-Ausdrücken gelten keine wirklich neuen Regeln im Vergleich zu Methodenrückgaben, denn auch

    public static Long two() { return 2; } // Compilerfehler

    wird vom Compiler angemeckert. Doch weil Generics bei funktionalen Schnittstellen viel häufiger sind, treten diese Besonderheiten öfters zu Tage auf als bei Methodendeklarationen.

    Rekursive Lambda-Ausdrücke

    Lambda-Ausdrücke können auf sich selbst verweisen, doch da ein this zur Selbstreferenz nicht möglich ist, ist ein kleiner Umweg nötig. Erstes muss eine Objekt- oder Klassenvariable deklariert werden, zweitens dann dieser Variablen ein Lambda-Ausdruck zugewiesen werden und dann kann drittens dieser Lambda-Ausdruck auf diese Variable zugreifen und einen rekursiven Aufruf starten. Für den Klassiker der Fakultät sieht das so aus:

    public class RecursiveFactLambda {

    public static IntFunction<Integer> fact = n -> (n == 0) ? 1 : n * fact.apply(n-1);

    public static void main( String[] args ) {

       System.out.println( fact.apply( 5 ) ); // 120

    }

    }

    IntFunction ist eine funktionale Schnittstelle mit den der Operation T apply(int i), und T ist ein generischer Rückgabetyp, den wir hier mit Integer belegt haben.

    fact hätte genauso gut als normale Methode deklariert werden können – großartige Vorteile bietet die Schreibweise mit Lambda-Ausdrücken nicht. Zumal jetzt auch der Begriff „anonyme Funktion“ nicht mehr so richtig schlüssig ist, da der Lambda-Ausdruck ja doch einen Namen hat, nämlich fact.

    Annotation @FunctionalInterface

    Zwar eignet sich jede Schnittstelle mit einer abstrakten Methode als funktionale Schnittstelle und damit für einen Lambda-Ausdruck, doch nicht jede Schnittstelle, die im Moment nur eine abstrakte Methode deklariert, soll auch für Lambda-Ausdrücke verwendet werden. Der Compiler kann das jedoch nicht wissen, ob sich vielleicht Schnittstellen weiterentwickeln, und daher gibt es zur Dokumentation die Annotation FunctionalInterface im java.lang-Paket.

    Beispiel: Eine eigene funktionale Schnittstelle soll FunctionalInterface markieren:

    @FunctionalInterface

    interface MyFunctionalInterface {

    void foo();

    }

    Der Compiler prüft, ob die Schnittstelle mit einer solchen Annotation tatsächlich nur exakt eine abstrakte Methode enthält und löst einen Fehler aus, wenn dem nicht so ist. Aus Kompatibilitätsgründen erzwingt der Compiler nicht diese Annotation bei funktionalen Schnittstellen, um inneren Klassen von alten Schnittstellen einfach in Lambda-Ausdrücke umzuschreiben zu können. Die Annotation ist also keine Voraussetzung für die Nutzung in einem Lambda-Ausdruck und dient bisher nur der Dokumentation.

    Die Umgebung der Lambda-Ausdrücke

    Ein Lambda-Ausdruck „sieht“ seine Umgebung genauso wie der Code, der vor oder nach dem Lambda-Ausdruck steht. Insbesondere hat ein Lambda-Ausdruck vollständigen Zugriff auf alle Eigenschaften der Klasse, genauso wie auch der einschließende äußere Block sie hat. Es gibt keinen besonderen Namensraum (nur neue und vielleicht überdeckte Variablen durch die Parameter), und das ist einer der grundlegenden Unterschiede zwischen Lambda-Ausdrücken und inneren Klassen, bei denen this und super eine etwas andere Bedeutung haben.

    Namensräume

    Deklariert eine innere anonyme Klasse in der Methode Variablen, so ist der Satz immer „neu“, beziehungsweise die neuen Variablen überschatten vorhandene lokale Variablen aus dem äußeren Kontext. Die Variable compareIgnoreCase kann im Rumpf von compare(…) zum Beispiel problemlos neu deklariert werden:

    boolean compareIgnoreCase = true;

    Comparator<String> c = new Comparator<String>() {

    @Override public int compare( String s1, String s2 ) {

    boolean compareIgnoreCase = false; // völlig ok

    return …

    }

    };

    In einem Lambda-Ausdruck ist das nicht möglich und folgendes führt zu einer Compilermeldung „variable compareIgnoreCase ist already defined“.

    boolean compareIgnoreCase = true;

    Comparator<String> c = (s1, s2) -> {

    boolean compareIgnoreCase = false; // Compilerfehler

    return …

    }

    this-Referenz

    Lambda-Ausdrücke unterscheiden sich von inneren (anonymen) Klassen auch in dem, worauf die this-Referenz verweist: Bei Lambda-Ausdrücke zeigt this auf das Objekt, in dem der Lambda-Ausdruck eingebettet ist, bei inneren Klassen referenziert this die inneren Klasse.

    class Application {

    Application() {

    Runnable run1 = () -> { System.out.println( this.getClass().getName() ); };

    Runnable run2 = new Runnable() {

    @Override public void run() { System.out.println( this.getClass().getName()); } };

    run1.run(); // app.Application

    run2.run(); // app.Application$1

    }

    public static void main( String[] args ) {

    new Application();

    }

    }

    Das Programm nutzt this einmal im Lambda-Ausdruck und einmal in der inneren Klasse. Im Fall vom Lambda-Ausdruck bezieht sich ausdrücklich auf das Application-Exemplar, was sich im Klassenamen niederschlägt. Bei der inneren Klasse bekommen wir den Anhang $1, weil es sich um ein anderes Exemplar handelt.

    Base64-Kodierung (unter Java 8)

    Für die Übertragung von Binärdaten hat sich im Internet die Base64-Kodierung durchgesetzt, die zum Beispiel bei E-Mail-Anhängen und SOAP-Nachrichten zu finden ist. Auch bei der HTTP-Authentifizierung Basic Authentication kommt Base64 zum Tragen, denn die Konkatenation von Benutzername + „:“ + Passwort wird über Base64 codiert und so zum Server gesendet – der Sicherheitsgewinn ist natürlich null.

    Die Base64-Kodierung wird im RFC 4648[1] beschriebene. Drei Bytes (24 Bit) werden in vier Base64-kodierte Zeichen (vier Zeichen mit jeweils sechs repräsentativen Bits) umgesetzt. Die Konsequenz dieser Umformung ist, dass Binärdaten rund 33  % größer werden. Die Base64-Zeichen bestehen aus den Buchstaben des lateinischen Alphabets, den Ziffern 0 bis 9 sowie (im Normalfall) »+«, »/« und »=«.

    Das JDK liefert seit Java 8 direkte Unterstützung für diese Base64-Umsetzung mit der Klasse java.util.BASE64 aus. Zwei inneren Klassen Base64.Decoder bzw. Base64.Encoder kümmern sich um die Umwandlung. Zur Erzeugung der Exemplare gibt es statische Methoden in BASE64, und zwar nicht nur zwei, sieben. Der Grund ist, dass es neben der Standard-Konvertierung „Base“ noch MIME und URL/Dateiname-sicher gibt:

    · getEncoder() und getDecoder() liefern Exemplare vom Typ Base64.Encoder und Base64.Decoder bzw. für den normalen Basic-Typ.

    · getEncoder(int lineLength, byte[] lineSeparator), getMimeEncoder() und getMimeDecoder() liefern Encoder/Decoder für MIME-Nachrichten, bei der Zeilen mit einem „\r“ getrennt sind.

    · getUrlEncoder() und getUrlDecoder() nutzt zur Kodierung nur Zeichen, die für URL und Dateinamen gültig sind, und ersetzt „+“ durch „-“ und „/“ durch „_“.

    Beispiel

    Das folgende Beispiel erzeugt zuerst ein Byte-Feld mit Zufallszahlen. Die Base64-Klasse kodiert das Byte-Feld in einen String, der auf dem Bildschirm ausgegeben wird. Nachdem der String wieder zurückkodiert wurde, werden die Byte-Felder verglichen und liefern natürlich true:

    byte[] bytes1 = SecureRandom.getSeed( 20 );

    // byte[] -> String

    String s = Base64.getEncoder().encodeToString( bytes1 );

    System.out.println( s ); // z.B. TVST9v+JMk/vVUOSENmIcriXFLo=

    // String -> byte[]

    byte[] bytes2 = Base64.getDecoder().decode( s );

    System.out.println( Arrays.equals(bytes1, bytes2) ); // true

    Wer nicht mit Java 8 arbeiten kann, aber mit älteren Versionen vom Oracle JDK, der kann BASE64Encoder/BASE64Decoder aus dem nicht-öffentlichen Paket sun.misc nutzen.[2] Wem das nicht ganz geheuer ist, der kann javax.mail.internet.MimeUtility von der JavaMail-API nutzen[3] oder unter http://jakarta.apache.org/commons/codec/ die Commons Codec-Bibliothek beziehen.


    [1] http://tools.ietf.org/html/rfc4648

    [2] Siehe dazu http://java.sun.com/products/jdk/faq/faq-sun-packages.html. Bisher existieren sie aber seit über zehn Jahren, und wer Oracles Philosophie kennt, der weiß, dass die Abwärtskompatibilität oberste Priorität hat.

    [3] http://www.rgagnon.com/javadetails/java-0598.html gibt ein Beispiel. Die JavaMail-API ist Teil von Java EE 5 und muss sonst für das Java SE als Bibliothek hinzugenommen werden.

    Statische ausprogrammierte Methoden in Schnittstellen

    In der Regel deklariert eine Schnittstelle Operationen, also abstrakte Objektmethoden, die eine Klasse später implementieren muss. Die in Klassen implementiere Schnittstellenmethode kann später wieder überschrieben werden, nimmt also ganz normal an der dynamischen Bindung teil. Einen Objektzustand kann die Schnittstelle nicht deklarieren, denn Objektvariablen sind in Schnittstellen tabu – jede deklarierte Variable ist automatisch statisch, also eine Klassenvariable.

    Ab Java 8 lassen sich in Schnittstellen statische Methoden unterbringen und als Utility-Methoden neben Konstanten stellen. Als statische Methoden werden sie nicht dynamisch gebunden und der Aufruf ist ausschließlich über den Namen der Schnittstelle möglich. (Bei statischen Methoden von Klassen ist im Prinzip auch der Zugriff über eine Referenz erlaubt, wenn auch unerwünscht.)

    Beispiel:

    interface Buyable {
    int MAX_PRICE = 10_000_000;

    static boolean isValidPrice( double price ) { return price >= 0 && price < MAX_PRICE; }
    double price();
    }

    Von außen ist dann der Aufruf Buyable.isValidPrice(123) möglich.

    Alle deklarieren Eigenschaften sind immer implizit public, sodass dieser Sichtbarkeitsmodifizierer redundant ist.

    Fassen wir die erlaubten Eigenschaften einer Schnittstelle zusammen:

     

    Attribut

    Methode

    Objekt-

    Nein, nicht erlaubt

    Ja, üblicherweise abstrakt

    Statische(s)

    Ja, als Konstante

    Ja, immer mit Implementierung

    Erlaubte Eigenschaften einer Schnittstelle

     

    Design: Eine Schnittstelle mit nur statischen Methoden ist ein Zeichen für ein Designproblem und sollte durch eine finale Klasse mit privaten Konstruktor ersetzt werden. Schnittstellen sind immer als Vorgaben zum Implementieren gedacht und wenn nur statische Methoden vorgekommen, erfüllt die Schnittstelle nicht ihren Zweck, dass sie Vorgaben macht, die unterschiedlich umgesetzt werden können.

    Kalender-Exemplare bauen über den Calendar.Builder

    Java 8 führte in Calendar die neue statische innere Klasse Builder ein, mit der sich leicht Calendar-Exemplare mit gesetzten Feldern aufbauen lassen. Die allgemeine Schreibweise ist wie folgt:

    Calendar cal = new Calendar.Builder().setXXX( … ).setXXX( … ).setXXX( … ).build();

    Zum Setzen von Feldern gibt es setXXX(…)-Methoden, am Ende folgt ein Aufruf von build(), der ein fertiges Calendar-Objekt liefert.

    static class java.util.Calendar.Builder

    § Calendar.Builder setDate(int year, int month, int dayOfMonth)

    § Calendar.Builder set(int field, int value)

    § Calendar.Builder setFields(int… fieldValuePairs)

    § Calendar.Builder setInstant(Date instant)

    § Calendar.Builder setInstant(long instant)

    § Calendar.Builder setTimeOfDay(int hourOfDay, int minute, int second)

    § Calendar.Builder setTimeOfDay(int hourOfDay, int minute, int second, int millis)

    § Calendar.Builder setWeekDate(int weekYear, int weekOfYear, int dayOfWeek)

    § Calendar.Builder setTimeZone(TimeZone zone)

    Etwas weniger gebräuchliche Mehtoden sind weiterhin setCalendarType(String type) – was Rückgaben von Calendar.getAvailableCalendarTypes() erlaubt und alternativ zu „gregory“ auch “gregorian“ bzw. “ iso8601“ –, setLenient(boolean lenient), setLocale(Locale locale) und setWeekDefinition(int firstDayOfWeek, int minimalDaysInFirstWeek).

    Inselraus: Kalender-Typen getCalendarType()/getAvailableCalendarTypes()

    Welcher Kalendertyp ein konkreter Kalender repräsentiert, ermittelt die Calendar-Objektmethode getCalendarType(); die Rückgabe ist ein String und lautet bei dem typischen Gregorianischen Kalender „gregory“, könnte aber auch „buddhist“ oder „japanese“ heißen. Welche Kalendertypen Java unterstützt, liefert die statische Methode Calendar.getAvailableCalendarTypes() als Set<String>. Im Moment sind es genau die drei genannten.

    Würde geloggt werden?

    Das Logging-Framwork versucht so schnell wie möglich zu entscheiden, ob eine Nachricht bei einem eingestellten Log-Levels geloggt werden soll oder nicht. Ist die Stufe in der Produktion zum Beispiel auf WARNING, sind INFO-Meldungen zu ignorieren. Problematisch aus Performance-Sicht sind zum Beispiel aufwändig aufgebaute Log-Nachrichten, die dann sowieso nicht geloggt werden. Der Plus-Operator bei Strings gehört nicht zu den beachtlichen Zeitfressern, doch ein

    log.info( "Open file: " + filename );

    führt zur Laufzeit immer zu einer String-Konkatenation, egal, ob die erzeugte Nachricht später geloggt wird oder nicht.

    JUL bietet zur Umgehung des Problems zwei Lösungen. Als erstes bietet die Logger-Klasse eine Testmethode boolean isLoggable(Level level), über die ein schneller Test durchgeführt werden kann:

    if ( log.isLoggable(Level.INFO) )

      log.info( "Open file: " + filename );

    Natürlich kann info(…) nicht wissen, dass es auf jeden Fall loggen soll, daher findet der Test noch einmal statt. Eine allgemeine Überprüfung für alle Logging-Ausgaben bietet sich daher nicht an, sondern nur dann, wenn eine aufwändige Operation im Logging-Fall ausgeführt werden soll.

    Die zweite Möglichkeit ist neu in Java 8. Sie nutzt Objekte vom Typ Supplier, die eine Implementierung enthalten, also etwa die Konkatenation. Im Prinzip hätte Oracle das auch schon vor Java 8 integrieren können, doch erst Lambda-Ausdrücke führen zu einer kompakten Schreibweise. Das sieht zum Beispiel so aus:

    log.info( () -> { "Open file: " + filename } );

    Hashwerte von Wrapper-Objekten mit neuen Methoden ab Java 8

    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 (mindest, wenn die Berechnung korrekt ist). Zur Bestimmung des Hashwertes deklariert jede Klasse über die Oberklasse java.lang.Object die Methode int hashCode(). Alle Wrapper-Klassen überschreiben diese Methode. Zudem kommen in Java 8 statische Methoden hinzu, sodass sich leicht der Hashwert berechnen lässt, ohne extra ein Wrapper-Objekte zu bilden.

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

    Abbildung 4 Statische Mehtoden hashCode(…) und Objektmethoden im Vergleich

     

    Um den Hashwert eines ganzen Objekts zu errechnen, müssen folglich alle einzelnen Hashwerte berechnet werden und diese dann zu einer Ganzzahl verknüpft werden. Schematisch sieht das so aus:

    int h1 = WrapperClass.hashCode( value1 );

    int h2 = WrapperClass.hashCode( value2 );

    int h3 = WrapperClass.hashCode( value3 );

    Eclipse nutzt zur Verknüpfung der Hashwerte folgendes Muster, welches eine guter Ausgangspunkt ist:

    int result = h1;

    result = 31 * result + h2;

    result = 31 * result + h3;

    Doch erst mal keine privaten Interface-Methoden

    So schreibt Brian Goetz:

    > We would like to pull back two small features from the JSR-335 feature plan:
    >
    >  - private methods in interfaces
    >  - "package modifier" for package-private visibility
    >
    > The primary reason is resourcing; cutting some small and inessential
    > features made room for deeper work on more important things like type
    > inference (on which we've made some big improvements lately!)  Private
    > methods are also an incomplete feature; we'd like the full set of
    > visibilities, and limiting to public/private was already a compromise based
    > on what we thought we could get done in the timeframe we had.  But it would
    > still be a rough edge that protected/package were missing.
    >
    > The second feature, while trivial (though nothing is really trivial), loses
    > a lot of justification without at least a move towards the full set of
    > accessibilities.  As it stands, it is pretty far afield of lambda, nothing
    > else depends on it, and not doing it now does not preclude doing it later.
    > (The only remaining connection to lambda is accelerating the death of the
    > phrase "default visibility" to avoid confusion with default methods.)
    >

    Die nächsten beiden Tage werden für Java 8 spannend, denn …

    … am 31.01.2013 muss das JDK 8 http://openjdk.java.net/projects/jdk8/milestones#Feature_Complete sein. Date & Time hat es noch geschafft. Dann müssen wir den M6 bekommen, das laut Vorgaben enthält:

    101 Generalized Target-Type Inference

    103 Parallel Array Sorting

    104 Annotations on Java Types

    109 Enhance Core Libraries with Lambda

    115 AEAD CipherSuites

    118 Access to Parameter Names at Runtime

    119 javax.lang.model Implementation Backed by Core Reflection

    120 Repeating Annotations

    126 Lambda Expressions & Virtual Extension Methods

    135 Base64 Encoding & Decoding

    138 Autoconf-Based Build System

    139 Enhance javac to Improve Build Speed

    140 Limited doPrivileged

    142 Reduce Cache Contention on Specified Fields

    143 Improve Contended Locking

    147 Reduce Class Metadata Footprint

    148 Small VM

    149 Reduce Core-Library Memory Usage

    150 Date & Time API

    160 Lambda-Form Representation for Method Handles

    161 Compact Profiles

    162 Prepare for Modularization

    164 Leverage CPU Instructions for AES Cryptography

    165 Compiler Control

    166 Overhaul JKS-JCEKS-PKCS12 Keystores

    170 JDBC 4.2

    172 DocLint

    173 Retire Some Rarely-Used GC Combinations

    So wie ich das überblicke, sind die meisten Punkte realisiert.

    Java 8 und JSR-308, was ist Stand der Dinge?

    Die Idee bei JSR-308: Annotationen an allem möglichen Typen dranmachen (daher auch der Name “Type Annotations”). Z.B. so:

    Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;

    Mehr auch unter http://jcp.org/aboutJava/communityprocess/ec-public/materials/2012-01-1011/jsr308-201201.pdf oder in der Spec http://types.cs.washington.edu/jsr308/specification/java-annotation-design.html.

    Mit dem aktuellen JDK 8 können zwar Annotationen deklariert werden, die für neue “Orte” stehen (http://download.java.net/jdk8/docs/api/java/lang/annotation/ElementType.html hat seit 1.8 TYPE_PARAMETER und TYPE_USE), aber sonst ist mit Standardcompiler nicht viel los. Testet man oberes Beispiel, gibt es nur Fehler:

    @Target(value= ElementType.TYPE_USE)
    @interface NonNull { }

    @Target(value= ElementType.TYPE_USE)
    @interface NonEmpty { }

    @Target(value= ElementType.TYPE_USE)
    @interface Readonly { }

    class Document {}

    class Main {
        Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
    }

    Dann rappelt es nur:
    C:\Users\Christian\Documents\NetBeansProjects\App\src\app\Main.java:53: error: illegal start of type
        Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
    C:\Users\Christian\Documents\NetBeansProjects\App\src\app\Main.java:53: error: ‚;‘ expected
        Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
    C:\Users\Christian\Documents\NetBeansProjects\App\src\app\Main.java:53: error: <identifier> expected
        Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
    C:\Users\Christian\Documents\NetBeansProjects\App\src\app\Main.java:53: error: <identifier> expected
        Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
    C:\Users\Christian\Documents\NetBeansProjects\App\src\app\Main.java:53: error: ‚;‘ expected
        Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
    C:\Users\Christian\Documents\NetBeansProjects\App\src\app\Main.java:53: error: <identifier> expected
        Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
    C:\Users\Christian\Documents\NetBeansProjects\App\src\app\Main.java:53: error: ‚(‚ expected
        Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
    C:\Users\Christian\Documents\NetBeansProjects\App\src\app\Main.java:53: error: <identifier> expected
        Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;

    Wenn man JSR-308 wirklich nutzen möchte, greift man zur experimentelle Version http://openjdk.java.net/projects/type-annotations/. Wenn Oracle hier alles für rund hält, wandert das in das normale OpenJDK 8 Projekt. Das ist wie mit Lambda und dem OpenJDK 8.

    Wenn man dann einen funktionierenden Compiler und eine Unterstützung hat, die Annotationen auszulesen, kann ein Checker diverse Sachen testen. Interessant sind @NonNull-Dinger oder auch Test für Immutibility. Hier ist http://types.cs.washington.edu/jsr308/ interessant, ein Checker-Framework, das in den Compiler integriert wird.

    JSR-308 ist schon ewig im Gespräch, 2007 (!) hatte ich das schon im Blog: http://www.tutego.de/blog/javainsel/2007/05/erste-implementierung-fur-jsr-308/