- Core Java Platform (69 videos)
- Development Tools and Techniques (75 videos)
- Emerging Languages on the JVM (35 videos)
- Enterprise Service Architectures and the Cloud (66 videos)
- Java EE Web Profile and Platform Technologies (81 videos)
- Java ME, Java Card, Embedded and Devices (78 videos)
- JavaFX and Rich User Experiences (48 videos)
NetBeans 7.3 final und aus der Beta-Phase
Details und Download hier: http://netbeans.org/community/releases/73/index.html ,http://wiki.netbeans.org/NewAndNoteworthyNB73. Im Java-Bereich hat sich gar nicht so viel getan, eher im Umfeld der Web-Entwicklung mit HTML5. Das zeigt irgendwie für mich klar, wohin die Reise in der Entwicklung geht. Ich hätte es schön gefunden, wenn die neuen Sprachfeatures von Java 8 schon integriert würde, aber hier muss man auf die nächste Version warten. Bei Eclipse passiert gefühlt nicht so viel, IntelliJ und NB legen gutes Tempo vor.
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).
GAEJ unterstützt nun auch Java 7
Siehe http://googleappengine.blogspot.com/2013/02/java-7-and-cloud-endpoints-preview.html. Die Java 7 Sprachfeatures sind eher uninteressant, da der generierte Bytecode nichts neues bedarf, aber invokedynamic wird unterstützt, was Skriptsprachen performanter auf der JVM der Google-Cloud ermöglicht.
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
109 Enhance Core Libraries with Lambda
118 Access to Parameter Names at Runtime
119 javax.lang.model Implementation Backed by Core Reflection
126 Lambda Expressions & Virtual Extension Methods
135 Base64 Encoding & Decoding
138 Autoconf-Based Build System
139 Enhance javac to Improve Build Speed
142 Reduce Cache Contention on Specified Fields
147 Reduce Class Metadata Footprint
149 Reduce Core-Library Memory Usage
160 Lambda-Form Representation for Method Handles
162 Prepare for Modularization
164 Leverage CPU Instructions for AES Cryptography
166 Overhaul JKS-JCEKS-PKCS12 Keystores
173 Retire Some Rarely-Used GC Combinations
So wie ich das überblicke, sind die meisten Punkte realisiert.
Funktionale Schnittstellen mit @FunctionalInterface dokumentierten
In Java 8 kommt dafür eine Markierung hinzu:
package java.lang; import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
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/