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 } );

Die Default-Falle

Insbesondere bei Kodierungen und zeitgebundenen Eigenschaften müssen sich Entwickler zu jeder Zeit bewusst sein, welche Einstellung gerade verwendet wird. Neulinge greifen oft auf Default-Einstellungen zurück und String-Parsing mit Scanner und Ausgaben mit Formatter funktionieren in der Entwicklung, doch spätestens wenn die Software halb um den Globus wandert, läuft nichts mehr, weil die Default-Werte plötzlich anders sind.

Wenn Konstruktoren oder Methoden es nicht explizit verlangen, greift das JDK auf Standardwerte unter anderen für

· Zeilenendezeichen

· Zeichenkodierung

· Sprache (Locale)

· Zeitzone (TimeZone)

zurück.

Ein Beispiel: Der Konstruktor Scanner(File) öffnet eine Datei zum Lesen und konvertiert die Bytes in Unicodes mit einem Konverter, den die Default-Zeichenkodierung bestimmt. Wird aus dem Scanner eine Zahl gelesen, etwa mit nextDouble(), greift die voreingestellte Default-Locale, die dem Scanner sagt, ob Dezimalzahlen mit „,“ oder „.“ interpretiert werden muss. Verarbeitet ein Java-Programm die gleiche Textdatei einmal in den USA und Deutschland, ist das Ergebnis unterschiedlich und in der Regel sollte das nicht so sein.

Default-Werte sind eine gute Sache, allerdings sollten Entwickler sich bewusst sein, an welchen Stellen das JDK auf sie zurückgreift, um keine Überraschungen zu erleben. Es lohnt sich, immer konkrete Belegungen anzugeben, auch wenn als Argument zum Beispiel Locale.getDefault() steht. Das dokumentiert das gewollte Nutzen der Default-Werte.

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;

LinkedHashMap und LRU-Implementierungen

Da die Reihenfolge der eingefügten Elemente bei einem Assoziatspeicher verloren geht, gibt es mit LinkedHashMap eine Mischung, also ein schneller Assoziativspeicher mit gleichzeitiger Speicherung der Reihenfolge der Objekte. Die Bauart vom Klassename LinkedHashMap macht schon deutlich, dass es eine Map ist, und die Reihenfolge der Objekte liefert ein Iterator; es gibt keine listenähnliche Schnittstelle mit get(int). LinkedHashMap ist für Assoziativspeicher das, was LinkedHashSet für HashSet ist.

Im Gegensatz zur normalen HashMap ruft LinkedHashMap immer genau dann die besondere Methode boolean removeEldestEntry(Map.Entry<K,V> eldest) auf, wenn intern ein Element der Sammlung hinzugenommen wird. Die Standardimplementierung dieser Methode liefert immer false, was bedeutet, dass das älteste Element nicht gelöscht werden soll, wen ein neues hinzukommt. Doch bietet das JDK die Methode aus Absicht protected an, denn sie kann von uns überschrieben werden, um eine Datenstruktur aufzubauen, die eine maximal Anzahl Elemente hat. So sieht das aus:

package com.tutego.insel.util.map;

import java.util.*;

public class LRUMap<K,V> extends LinkedHashMap<K, V> {
  private final int capacity;

  public LRUMap( int capacity ) {
    super( capacity, 0.75f, true );
    this.capacity = capacity;
  }

  @Override
  protected boolean removeEldestEntry( Map.Entry<K, V> eldest ) {
    return size() > capacity;
  }
}

LinkedHashSet bietet eine vergleichbare Methode removeEldestEntry(…) nicht. Wer dies benötigt, muss eine eigene Mengenklasse auf der Basis von LinkedHashMap realisieren.

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/

Erste Libs springen auf Java 8 auf

So etwa http://www.jdbi.org/.

JDBI is a SQL convenience library for Java.

Beispiel von der Seite in herkömmlicher Notation:

DataSource ds = JdbcConnectionPool.create("jdbc:h2:mem:test",
                                          "username",
                                          "password");
DBI dbi = new DBI(ds);
Handle h = dbi.open();
h.execute("create table something (id int primary key, name varchar(100))");

h.execute("insert into something (id, name) values (?, ?)", 1, "Brian");

String name = h.createQuery("select name from something where id = :id")
                    .bind("id", 1)
                    .map(StringMapper.FIRST)
                    .first();
                    
assertThat(name, equalTo("Brian"));

h.close();

http://skife.org/jdbi/2012/12/10/some-jdbi3.html schreibt nun, dass JDBI 3 Lambda-Ausdrücke nutzen wird und gibt folgendes Beispiel an:

Set<Something> things = jdbi.withHandle(h -> {
    h.execute("insert into something (id, name) values (?, ?)", 1, "Brian");
    h.execute("insert into something (id, name) values (?, ?)", 2, "Steven");

    return h.query("select id, name from something")
            .map(rs -> new Something(rs.getInt(1), rs.getString(2)))
            .into(new HashSet<Something>());
});

assertThat(things).isEqualTo(ImmutableSet.of(new Something(1, "Brian"),
                                             new Something(2, "Steven")));

Das geht sicherlich noch etwas kürzer, warten wir’s ab.

Mit welchen Java-Open-Source Libs Java besser lernen?

Die Apache Commons Lang Lib finde ich für den Einstieg gut geeignet. (Guava ist echt hart für jmd. der gerade seine Java-Gewässer erkundet; ist was für nach dem ersten Hügel.) Nicht zu vergessen die Sourcen zu den Java-Libs selbst. Oder vorbereiten auf Java 8, in etwa mit einem Blick auf die Date-Time-API JSR-310 (SourceForge.net: threeten) wirft.
Was sonst noch?
* log4j bwz. diverse Nachfolger.
* FreeMarker
* Apache Commons CLI oder Alternativen
Dann noch spezielle Libs, wenn man in diversen Technologien einsteigt
* Code aus SwingX (sehr gut, wenn man Swing-Entwicklung macht)
* Tag-Libs
* JavaFX-Extentions
* Eclipse-Plugins
Sonst kann man noch die Java Certification Exams durchgehen für ein besseres Verständnis der Sprache an sich.
Und meine RTFLib jrtf hat noch ein paar Issues offen