Dieser Artikel wurde ursprünglich auf meinem Blog veröffentlicht.
Sie sollten auch mein Java 11-Tutorial lesen (einschließlich neuer Sprach- und API-Funktionen von Java 9, 10 und 11).
Willkommen zu meiner Einführung in Java 8. Dieses Tutorial führt Sie Schritt für Schritt durch alle neuen Sprachfunktionen. Anhand kurzer und einfacher Codebeispiele erfahren Sie, wie Sie Standardschnittstellenmethoden, Lambda-Ausdrücke, Methodenreferenzen und wiederholbare Anmerkungen verwenden. Am Ende des Artikels werden Sie mit den neuesten API-Änderungen wie Streams, Funktionsschnittstellen, Kartenerweiterungen und der neuen Datums-API vertraut gemacht. Keine Textwände, nur ein paar kommentierte Codeschnipsel. Genießen!
★★★ Gefällt Ihnen dieses Projekt? Hinterlassen Sie einen Stern, folgen Sie mir auf Twitter oder spenden Sie, um meine Arbeit zu unterstützen. Danke! ★★★
Java 8 ermöglicht es uns, nicht-abstrakte Methodenimplementierungen zu Schnittstellen hinzuzufügen, indem wir das default
verwenden. Diese Funktion wird auch als virtuelle Erweiterungsmethode bezeichnet.
Hier ist unser erstes Beispiel:
interface Formula {
double calculate ( int a );
default double sqrt ( int a ) {
return Math . sqrt ( a );
}
}
Neben der abstrakten Methode calculate
die Schnittstelle Formula
definiert auch die Standardmethode sqrt
. Konkrete Klassen müssen lediglich die abstrakte Methode calculate
implementieren. Die Standardmethode sqrt
kann standardmäßig verwendet werden.
Formula formula = new Formula () {
@ Override
public double calculate ( int a ) {
return sqrt ( a * 100 );
}
};
formula . calculate ( 100 ); // 100.0
formula . sqrt ( 16 ); // 4.0
Die Formel wird als anonymes Objekt implementiert. Der Code ist ziemlich ausführlich: 6 Codezeilen für eine so einfache Berechnung von sqrt(a * 100)
. Wie wir im nächsten Abschnitt sehen werden, gibt es in Java 8 eine viel schönere Möglichkeit, einzelne Methodenobjekte zu implementieren.
Beginnen wir mit einem einfachen Beispiel für das Sortieren einer Liste von Zeichenfolgen in früheren Java-Versionen:
List < String > names = Arrays . asList ( "peter" , "anna" , "mike" , "xenia" );
Collections . sort ( names , new Comparator < String >() {
@ Override
public int compare ( String a , String b ) {
return b . compareTo ( a );
}
});
Die statische Dienstprogrammmethode Collections.sort
akzeptiert eine Liste und einen Komparator, um die Elemente der angegebenen Liste zu sortieren. Sie müssen häufig anonyme Komparatoren erstellen und diese an die Sortiermethode übergeben.
Anstatt den ganzen Tag anonyme Objekte zu erstellen, verfügt Java 8 über eine viel kürzere Syntax, Lambda-Ausdrücke :
Collections . sort ( names , ( String a , String b ) -> {
return b . compareTo ( a );
});
Wie Sie sehen, ist der Code viel kürzer und einfacher zu lesen. Aber es kommt noch kürzer:
Collections . sort ( names , ( String a , String b ) -> b . compareTo ( a ));
Bei einzeiligen Methodenkörpern können Sie sowohl die geschweiften Klammern {}
als auch das Schlüsselwort return
überspringen. Aber es kommt noch kürzer:
names . sort (( a , b ) -> b . compareTo ( a ));
Die Liste verfügt jetzt über eine sort
. Auch der Java-Compiler kennt die Parametertypen, sodass Sie sie ebenfalls überspringen können. Lassen Sie uns genauer untersuchen, wie Lambda-Ausdrücke in freier Wildbahn verwendet werden können.
Wie passen Lambda-Ausdrücke in das Typsystem von Java? Jedes Lambda entspricht einem bestimmten Typ, der durch eine Schnittstelle angegeben wird. Eine sogenannte funktionale Schnittstelle muss genau eine abstrakte Methodendeklaration enthalten. Jeder Lambda-Ausdruck dieses Typs wird dieser abstrakten Methode zugeordnet. Da Standardmethoden nicht abstrakt sind, können Sie Ihrer funktionalen Schnittstelle Standardmethoden hinzufügen.
Wir können beliebige Schnittstellen als Lambda-Ausdrücke verwenden, solange die Schnittstelle nur eine abstrakte Methode enthält. Um sicherzustellen, dass Ihre Schnittstelle die Anforderungen erfüllt, sollten Sie die Annotation @FunctionalInterface
hinzufügen. Der Compiler kennt diese Annotation und gibt einen Compilerfehler aus, sobald Sie versuchen, der Schnittstelle eine zweite abstrakte Methodendeklaration hinzuzufügen.
Beispiel:
@ FunctionalInterface
interface Converter < F , T > {
T convert ( F from );
}
Converter < String , Integer > converter = ( from ) -> Integer . valueOf ( from );
Integer converted = converter . convert ( "123" );
System . out . println ( converted ); // 123
Beachten Sie, dass der Code auch gültig ist, wenn die Annotation @FunctionalInterface
weggelassen würde.
Der obige Beispielcode kann durch die Verwendung statischer Methodenreferenzen weiter vereinfacht werden:
Converter < String , Integer > converter = Integer :: valueOf ;
Integer converted = converter . convert ( "123" );
System . out . println ( converted ); // 123
Mit Java 8 können Sie Referenzen von Methoden oder Konstruktoren über das Schlüsselwort ::
übergeben. Das obige Beispiel zeigt, wie auf eine statische Methode verwiesen wird. Wir können aber auch auf Objektmethoden verweisen:
class Something {
String startsWith ( String s ) {
return String . valueOf ( s . charAt ( 0 ));
}
}
Something something = new Something ();
Converter < String , String > converter = something :: startsWith ;
String converted = converter . convert ( "Java" );
System . out . println ( converted ); // "J"
Sehen wir uns an, wie das Schlüsselwort ::
für Konstruktoren funktioniert. Zuerst definieren wir eine Beispielklasse mit verschiedenen Konstruktoren:
class Person {
String firstName ;
String lastName ;
Person () {}
Person ( String firstName , String lastName ) {
this . firstName = firstName ;
this . lastName = lastName ;
}
}
Als nächstes geben wir eine Personenfabrikschnittstelle an, die zum Erstellen neuer Personen verwendet werden soll:
interface PersonFactory < P extends Person > {
P create ( String firstName , String lastName );
}
Anstatt die Factory manuell zu implementieren, fügen wir alles über Konstruktorreferenzen zusammen:
PersonFactory < Person > personFactory = Person :: new ;
Person person = personFactory . create ( "Peter" , "Parker" );
Wir erstellen über Person::new
einen Verweis auf den Person-Konstruktor. Der Java-Compiler wählt automatisch den richtigen Konstruktor aus, indem er die Signatur von PersonFactory.create
abgleicht.
Der Zugriff auf Variablen außerhalb des Gültigkeitsbereichs über Lambda-Ausdrücke ist dem Zugriff auf anonyme Objekte sehr ähnlich. Sie können vom lokalen äußeren Bereich aus auf endgültige Variablen sowie auf Instanzfelder und statische Variablen zugreifen.
Wir können endgültige lokale Variablen aus dem äußeren Bereich von Lambda-Ausdrücken lesen:
final int num = 1 ;
Converter < Integer , String > stringConverter =
( from ) -> String . valueOf ( from + num );
stringConverter . convert ( 2 ); // 3
Anders als bei anonymen Objekten muss die Variable num
jedoch nicht als final deklariert werden. Dieser Code ist auch gültig:
int num = 1 ;
Converter < Integer , String > stringConverter =
( from ) -> String . valueOf ( from + num );
stringConverter . convert ( 2 ); // 3
Allerdings muss num
implizit endgültig sein, damit der Code kompiliert werden kann. Der folgende Code lässt sich nicht kompilieren:
int num = 1 ;
Converter < Integer , String > stringConverter =
( from ) -> String . valueOf ( from + num );
num = 3 ;
Das Schreiben in num
aus dem Lambda-Ausdruck heraus ist ebenfalls verboten.
Im Gegensatz zu lokalen Variablen haben wir innerhalb von Lambda-Ausdrücken sowohl Lese- als auch Schreibzugriff auf Instanzfelder und statische Variablen. Dieses Verhalten ist von anonymen Objekten bekannt.
class Lambda4 {
static int outerStaticNum ;
int outerNum ;
void testScopes () {
Converter < Integer , String > stringConverter1 = ( from ) -> {
outerNum = 23 ;
return String . valueOf ( from );
};
Converter < Integer , String > stringConverter2 = ( from ) -> {
outerStaticNum = 72 ;
return String . valueOf ( from );
};
}
}
Erinnern Sie sich an das Formelbeispiel aus dem ersten Abschnitt? Interface Formula
definiert eine Standardmethode sqrt
, auf die von jeder Formelinstanz, einschließlich anonymer Objekte, zugegriffen werden kann. Dies funktioniert nicht mit Lambda-Ausdrücken.
Auf Standardmethoden kann nicht über Lambda-Ausdrücke zugegriffen werden. Der folgende Code lässt sich nicht kompilieren:
Formula formula = ( a ) -> sqrt ( a * 100 );
Die JDK 1.8-API enthält viele integrierte Funktionsschnittstellen. Einige davon sind aus älteren Java-Versionen wie Comparator
oder Runnable
bekannt. Diese vorhandenen Schnittstellen werden erweitert, um Lambda-Unterstützung über die Annotation @FunctionalInterface
zu ermöglichen.
Aber die Java 8 API ist auch voller neuer funktionaler Schnittstellen, die Ihnen das Leben erleichtern. Einige dieser neuen Schnittstellen sind aus der Google Guava-Bibliothek bekannt. Auch wenn Sie mit dieser Bibliothek vertraut sind, sollten Sie genau im Auge behalten, wie diese Schnittstellen durch einige nützliche Methodenerweiterungen erweitert werden.
Prädikate sind boolesche Funktionen eines Arguments. Die Schnittstelle enthält verschiedene Standardmethoden zum Zusammensetzen von Prädikaten für komplexe logische Begriffe (und/oder Negieren).
Predicate < String > predicate = ( s ) -> s . length () > 0 ;
predicate . test ( "foo" ); // true
predicate . negate (). test ( "foo" ); // false
Predicate < Boolean > nonNull = Objects :: nonNull ;
Predicate < Boolean > isNull = Objects :: isNull ;
Predicate < String > isEmpty = String :: isEmpty ;
Predicate < String > isNotEmpty = isEmpty . negate ();
Funktionen akzeptieren ein Argument und erzeugen ein Ergebnis. Standardmethoden können verwendet werden, um mehrere Funktionen miteinander zu verketten (compose und Then).
Function < String , Integer > toInteger = Integer :: valueOf ;
Function < String , String > backToString = toInteger . andThen ( String :: valueOf );
backToString . apply ( "123" ); // "123"
Lieferanten erzeugen ein Ergebnis eines bestimmten generischen Typs. Im Gegensatz zu Funktionen akzeptieren Lieferanten keine Argumente.
Supplier < Person > personSupplier = Person :: new ;
personSupplier . get (); // new Person
Verbraucher stellen Operationen dar, die für ein einzelnes Eingabeargument ausgeführt werden sollen.
Consumer < Person > greeter = ( p ) -> System . out . println ( "Hello, " + p . firstName );
greeter . accept ( new Person ( "Luke" , "Skywalker" ));
Komparatoren sind aus älteren Java-Versionen bekannt. Java 8 fügt der Schnittstelle verschiedene Standardmethoden hinzu.
Comparator < Person > comparator = ( p1 , p2 ) -> p1 . firstName . compareTo ( p2 . firstName );
Person p1 = new Person ( "John" , "Doe" );
Person p2 = new Person ( "Alice" , "Wonderland" );
comparator . compare ( p1 , p2 ); // > 0
comparator . reversed (). compare ( p1 , p2 ); // < 0
Optionals sind keine funktionalen Schnittstellen, sondern praktische Dienstprogramme, um NullPointerException
zu verhindern. Dies ist ein wichtiges Konzept für den nächsten Abschnitt. Werfen wir also einen kurzen Blick auf die Funktionsweise von Optionals.
Optional ist ein einfacher Container für einen Wert, der null oder ungleich null sein kann. Stellen Sie sich eine Methode vor, die möglicherweise ein Ergebnis ungleich Null zurückgibt, manchmal aber auch nichts. Anstatt null
zurückzugeben, geben Sie in Java 8 ein Optional
zurück.
Optional < String > optional = Optional . of ( "bam" );
optional . isPresent (); // true
optional . get (); // "bam"
optional . orElse ( "fallback" ); // "bam"
optional . ifPresent (( s ) -> System . out . println ( s . charAt ( 0 ))); // "b"
Ein java.util.Stream
stellt eine Folge von Elementen dar, für die eine oder mehrere Operationen ausgeführt werden können. Stream-Operationen sind entweder Zwischen- oder Endoperationen . Während Terminaloperationen ein Ergebnis eines bestimmten Typs zurückgeben, geben Zwischenoperationen den Stream selbst zurück, sodass Sie mehrere Methodenaufrufe hintereinander verketten können. Streams werden auf einer Quelle erstellt, z. B. einer java.util.Collection
wie Listen oder Sets (Karten werden nicht unterstützt). Stream-Operationen können entweder sequentiell oder parallel ausgeführt werden.
Streams sind äußerst leistungsfähig, daher habe ich ein separates Java 8 Streams-Tutorial geschrieben. Sie sollten sich auch Sequency als ähnliche Bibliothek für das Web ansehen.
Schauen wir uns zunächst an, wie sequentielle Streams funktionieren. Zuerst erstellen wir eine Beispielquelle in Form einer Liste von Zeichenfolgen:
List < String > stringCollection = new ArrayList <>();
stringCollection . add ( "ddd2" );
stringCollection . add ( "aaa2" );
stringCollection . add ( "bbb1" );
stringCollection . add ( "aaa1" );
stringCollection . add ( "bbb3" );
stringCollection . add ( "ccc" );
stringCollection . add ( "bbb2" );
stringCollection . add ( "ddd1" );
Sammlungen in Java 8 sind erweitert, sodass Sie Streams einfach erstellen können, indem Sie Collection.stream()
oder Collection.parallelStream()
aufrufen. In den folgenden Abschnitten werden die häufigsten Stream-Vorgänge erläutert.
Filter akzeptiert ein Prädikat, um alle Elemente des Streams zu filtern. Diese Operation ist eine Zwischenoperation , die es uns ermöglicht, eine weitere Stream-Operation ( forEach
) für das Ergebnis aufzurufen. ForEach akzeptiert die Ausführung eines Consumers für jedes Element im gefilterten Stream. ForEach ist eine Terminaloperation. Es ist void
, daher können wir keinen weiteren Stream-Vorgang aufrufen.
stringCollection
. stream ()
. filter (( s ) -> s . startsWith ( "a" ))
. forEach ( System . out :: println );
// "aaa2", "aaa1"
Sorted ist eine Zwischenoperation , die eine sortierte Ansicht des Streams zurückgibt. Die Elemente werden in natürlicher Reihenfolge sortiert, es sei denn, Sie übergeben einen benutzerdefinierten Comparator
.
stringCollection
. stream ()
. sorted ()
. filter (( s ) -> s . startsWith ( "a" ))
. forEach ( System . out :: println );
// "aaa1", "aaa2"
Beachten Sie, dass sorted
nur eine sortierte Ansicht des Streams erstellt, ohne die Reihenfolge der gesicherten Sammlung zu manipulieren. Die Reihenfolge von stringCollection
bleibt unverändert:
System . out . println ( stringCollection );
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
Die Zwischenoperationskarte map
jedes Element über die angegebene Funktion in ein anderes Objekt um. Das folgende Beispiel konvertiert jede Zeichenfolge in eine Zeichenfolge in Großbuchstaben. Sie können map
aber auch verwenden, um jedes Objekt in einen anderen Typ umzuwandeln. Der generische Typ des resultierenden Streams hängt vom generischen Typ der Funktion ab, die Sie an map
übergeben.
stringCollection
. stream ()
. map ( String :: toUpperCase )
. sorted (( a , b ) -> b . compareTo ( a ))
. forEach ( System . out :: println );
// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Mithilfe verschiedener Matching-Operationen kann überprüft werden, ob ein bestimmtes Prädikat mit dem Stream übereinstimmt. Alle diese Operationen sind terminal und geben ein boolesches Ergebnis zurück.
boolean anyStartsWithA =
stringCollection
. stream ()
. anyMatch (( s ) -> s . startsWith ( "a" ));
System . out . println ( anyStartsWithA ); // true
boolean allStartsWithA =
stringCollection
. stream ()
. allMatch (( s ) -> s . startsWith ( "a" ));
System . out . println ( allStartsWithA ); // false
boolean noneStartsWithZ =
stringCollection
. stream ()
. noneMatch (( s ) -> s . startsWith ( "z" ));
System . out . println ( noneStartsWithZ ); // true
Count ist eine Terminaloperation , die die Anzahl der Elemente im Stream als long
zurückgibt.
long startsWithB =
stringCollection
. stream ()
. filter (( s ) -> s . startsWith ( "b" ))
. count ();
System . out . println ( startsWithB ); // 3
Diese Terminaloperation führt eine Reduzierung der Elemente des Streams mit der angegebenen Funktion durch. Das Ergebnis ist ein Optional
das den reduzierten Wert enthält.
Optional < String > reduced =
stringCollection
. stream ()
. sorted ()
. reduce (( s1 , s2 ) -> s1 + "#" + s2 );
reduced . ifPresent ( System . out :: println );
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
Wie oben erwähnt können Streams entweder sequentiell oder parallel sein. Vorgänge an sequentiellen Streams werden in einem einzelnen Thread ausgeführt, während Vorgänge an parallelen Streams gleichzeitig in mehreren Threads ausgeführt werden.
Das folgende Beispiel zeigt, wie einfach es ist, die Leistung durch die Verwendung paralleler Streams zu steigern.
Zuerst erstellen wir eine große Liste einzigartiger Elemente:
int max = 1000000 ;
List < String > values = new ArrayList <>( max );
for ( int i = 0 ; i < max ; i ++) {
UUID uuid = UUID . randomUUID ();
values . add ( uuid . toString ());
}
Jetzt messen wir die Zeit, die zum Sortieren eines Streams dieser Sammlung benötigt wird.
long t0 = System . nanoTime ();
long count = values . stream (). sorted (). count ();
System . out . println ( count );
long t1 = System . nanoTime ();
long millis = TimeUnit . NANOSECONDS . toMillis ( t1 - t0 );
System . out . println ( String . format ( "sequential sort took: %d ms" , millis ));
// sequential sort took: 899 ms
long t0 = System . nanoTime ();
long count = values . parallelStream (). sorted (). count ();
System . out . println ( count );
long t1 = System . nanoTime ();
long millis = TimeUnit . NANOSECONDS . toMillis ( t1 - t0 );
System . out . println ( String . format ( "parallel sort took: %d ms" , millis ));
// parallel sort took: 472 ms
Wie Sie sehen, sind beide Codeschnipsel nahezu identisch, die parallele Sortierung ist jedoch etwa 50 % schneller. Sie müssen lediglich stream()
in parallelStream()
ändern.
Wie bereits erwähnt, unterstützen Karten Streams nicht direkt. Auf der Map
Schnittstelle selbst ist keine stream()
Methode verfügbar. Sie können jedoch über map.keySet().stream()
, map.values().stream()
und spezielle Streams für die Schlüssel, Werte oder Einträge einer Map erstellen map.entrySet().stream()
.
Darüber hinaus unterstützen Karten verschiedene neue und nützliche Methoden zur Erledigung allgemeiner Aufgaben.
Map < Integer , String > map = new HashMap <>();
for ( int i = 0 ; i < 10 ; i ++) {
map . putIfAbsent ( i , "val" + i );
}
map . forEach (( id , val ) -> System . out . println ( val ));
Der obige Code sollte selbsterklärend sein: putIfAbsent
verhindert, dass wir zusätzliche if null-Prüfungen schreiben; forEach
akzeptiert einen Verbraucher, der Operationen für jeden Wert der Karte ausführt.
Dieses Beispiel zeigt, wie Code auf der Karte mithilfe von Funktionen berechnet wird:
map . computeIfPresent ( 3 , ( num , val ) -> val + num );
map . get ( 3 ); // val33
map . computeIfPresent ( 9 , ( num , val ) -> null );
map . containsKey ( 9 ); // false
map . computeIfAbsent ( 23 , num -> "val" + num );
map . containsKey ( 23 ); // true
map . computeIfAbsent ( 3 , num -> "bam" );
map . get ( 3 ); // val33
Als Nächstes lernen wir, wie man Einträge für einen bestimmten Schlüssel nur dann entfernt, wenn er aktuell einem bestimmten Wert zugeordnet ist:
map . remove ( 3 , "val3" );
map . get ( 3 ); // val33
map . remove ( 3 , "val33" );
map . get ( 3 ); // null
Eine weitere hilfreiche Methode:
map . getOrDefault ( 42 , "not found" ); // not found
Das Zusammenführen von Einträgen einer Karte ist ganz einfach:
map . merge ( 9 , "val9" , ( value , newValue ) -> value . concat ( newValue ));
map . get ( 9 ); // val9
map . merge ( 9 , "concat" , ( value , newValue ) -> value . concat ( newValue ));
map . get ( 9 ); // val9concat
Beim Zusammenführen wird entweder der Schlüssel/Wert in die Karte eingefügt, wenn kein Eintrag für den Schlüssel vorhanden ist, oder die Zusammenführungsfunktion wird aufgerufen, um den vorhandenen Wert zu ändern.
Java 8 enthält eine brandneue Datums- und Uhrzeit-API unter dem Paket java.time
. Die neue Date-API ist mit der Joda-Time-Bibliothek vergleichbar, jedoch nicht dieselbe. Die folgenden Beispiele decken die wichtigsten Teile dieser neuen API ab.
Uhr bietet Zugriff auf das aktuelle Datum und die aktuelle Uhrzeit. Uhren kennen eine Zeitzone und können anstelle von System.currentTimeMillis()
verwendet werden, um die aktuelle Zeit in Millisekunden seit Unix EPOCH abzurufen. Ein solcher Momentanpunkt auf der Zeitlinie wird auch durch die Klasse Instant
repräsentiert. Instants können zum Erstellen älterer java.util.Date
Objekte verwendet werden.
Clock clock = Clock . systemDefaultZone ();
long millis = clock . millis ();
Instant instant = clock . instant ();
Date legacyDate = Date . from ( instant ); // legacy java.util.Date
Zeitzonen werden durch eine ZoneId
dargestellt. Der Zugriff auf sie ist über statische Factory-Methoden einfach. Zeitzonen definieren die Offsets, die für die Konvertierung zwischen Zeitpunkten und lokalen Datums- und Uhrzeitangaben wichtig sind.
System . out . println ( ZoneId . getAvailableZoneIds ());
// prints all available timezone ids
ZoneId zone1 = ZoneId . of ( "Europe/Berlin" );
ZoneId zone2 = ZoneId . of ( "Brazil/East" );
System . out . println ( zone1 . getRules ());
System . out . println ( zone2 . getRules ());
// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]
LocalTime stellt eine Zeit ohne Zeitzone dar, z. B. 22 Uhr oder 17:30:15. Das folgende Beispiel erstellt zwei Ortszeiten für die oben definierten Zeitzonen. Dann vergleichen wir beide Zeiten und berechnen die Differenz in Stunden und Minuten zwischen beiden Zeiten.
LocalTime now1 = LocalTime . now ( zone1 );
LocalTime now2 = LocalTime . now ( zone2 );
System . out . println ( now1 . isBefore ( now2 )); // false
long hoursBetween = ChronoUnit . HOURS . between ( now1 , now2 );
long minutesBetween = ChronoUnit . MINUTES . between ( now1 , now2 );
System . out . println ( hoursBetween ); // -3
System . out . println ( minutesBetween ); // -239
LocalTime verfügt über verschiedene Factory-Methoden, um die Erstellung neuer Instanzen zu vereinfachen, einschließlich der Analyse von Zeitzeichenfolgen.
LocalTime late = LocalTime . of ( 23 , 59 , 59 );
System . out . println ( late ); // 23:59:59
DateTimeFormatter germanFormatter =
DateTimeFormatter
. ofLocalizedTime ( FormatStyle . SHORT )
. withLocale ( Locale . GERMAN );
LocalTime leetTime = LocalTime . parse ( "13:37" , germanFormatter );
System . out . println ( leetTime ); // 13:37
LocalDate stellt ein bestimmtes Datum dar, z. B. 2014-03-11. Es ist unveränderlich und funktioniert genau analog zu LocalTime. Das Beispiel zeigt, wie neue Daten durch Addition oder Subtraktion von Tagen, Monaten oder Jahren berechnet werden. Beachten Sie, dass jede Manipulation eine neue Instanz zurückgibt.
LocalDate today = LocalDate . now ();
LocalDate tomorrow = today . plus ( 1 , ChronoUnit . DAYS );
LocalDate yesterday = tomorrow . minusDays ( 2 );
LocalDate independenceDay = LocalDate . of ( 2014 , Month . JULY , 4 );
DayOfWeek dayOfWeek = independenceDay . getDayOfWeek ();
System . out . println ( dayOfWeek ); // FRIDAY
Das Parsen eines LocalDate aus einer Zeichenfolge ist genauso einfach wie das Parsen einer LocalTime:
DateTimeFormatter germanFormatter =
DateTimeFormatter
. ofLocalizedDate ( FormatStyle . MEDIUM )
. withLocale ( Locale . GERMAN );
LocalDate xmas = LocalDate . parse ( "24.12.2014" , germanFormatter );
System . out . println ( xmas ); // 2014-12-24
LocalDateTime stellt ein Datum und eine Uhrzeit dar. Es kombiniert Datum und Uhrzeit, wie in den obigen Abschnitten gezeigt, in einer Instanz. LocalDateTime
ist unveränderlich und funktioniert ähnlich wie LocalTime und LocalDate. Wir können Methoden zum Abrufen bestimmter Felder aus einem Datum/Uhrzeit-Wert verwenden:
LocalDateTime sylvester = LocalDateTime . of ( 2014 , Month . DECEMBER , 31 , 23 , 59 , 59 );
DayOfWeek dayOfWeek = sylvester . getDayOfWeek ();
System . out . println ( dayOfWeek ); // WEDNESDAY
Month month = sylvester . getMonth ();
System . out . println ( month ); // DECEMBER
long minuteOfDay = sylvester . getLong ( ChronoField . MINUTE_OF_DAY );
System . out . println ( minuteOfDay ); // 1439
Mit der Zusatzinformation einer Zeitzone kann diese auf einen Zeitpunkt umgerechnet werden. Augenblicke können problemlos in ältere Datumsangaben vom Typ java.util.Date
konvertiert werden.
Instant instant = sylvester
. atZone ( ZoneId . systemDefault ())
. toInstant ();
Date legacyDate = Date . from ( instant );
System . out . println ( legacyDate ); // Wed Dec 31 23:59:59 CET 2014
Das Formatieren von Datums- und Uhrzeitangaben funktioniert genauso wie das Formatieren von Datums- und Uhrzeitangaben. Anstatt vordefinierte Formate zu verwenden, können wir Formatierer aus benutzerdefinierten Mustern erstellen.
DateTimeFormatter formatter =
DateTimeFormatter
. ofPattern ( "MMM dd, yyyy - HH:mm" );
LocalDateTime parsed = LocalDateTime . parse ( "Nov 03, 2014 - 07:13" , formatter );
String string = formatter . format ( parsed );
System . out . println ( string ); // Nov 03, 2014 - 07:13
Im Gegensatz zu java.text.NumberFormat
ist das neue DateTimeFormatter
unveränderlich und threadsicher .
Einzelheiten zur Mustersyntax finden Sie hier.
Anmerkungen in Java 8 sind wiederholbar. Schauen wir uns direkt ein Beispiel an, um das herauszufinden.
Zuerst definieren wir eine Wrapper-Annotation, die ein Array der tatsächlichen Annotationen enthält:
@interface Hints {
Hint [] value ();
}
@ Repeatable ( Hints . class )
@interface Hint {
String value ();
}
Mit Java 8 können wir mehrere Annotationen desselben Typs verwenden, indem wir die Annotation @Repeatable
deklarieren.
@ Hints ({ @ Hint ( "hint1" ), @ Hint ( "hint2" )})
class Person {}
@ Hint ( "hint1" )
@ Hint ( "hint2" )
class Person {}
Mit Variante 2 richtet der Java-Compiler implizit die @Hints
-Annotation unter der Haube ein. Das ist wichtig für das Lesen von Anmerkungsinformationen durch Reflexion.
Hint hint = Person . class . getAnnotation ( Hint . class );
System . out . println ( hint ); // null
Hints hints1 = Person . class . getAnnotation ( Hints . class );
System . out . println ( hints1 . value (). length ); // 2
Hint [] hints2 = Person . class . getAnnotationsByType ( Hint . class );
System . out . println ( hints2 . length ); // 2
Obwohl wir die @Hints
-Annotation nie für die Person
-Klasse deklariert haben, ist sie dennoch über getAnnotation(Hints.class)
lesbar. Die bequemere Methode ist jedoch getAnnotationsByType
, die direkten Zugriff auf alle mit Anmerkungen versehenen @Hint
-Annotationen gewährt.
Darüber hinaus wird die Verwendung von Annotationen in Java 8 auf zwei neue Ziele erweitert:
@ Target ({ ElementType . TYPE_PARAMETER , ElementType . TYPE_USE })
@interface MyAnnotation {}
Mein Programmierleitfaden zu Java 8 endet hier. Wenn Sie mehr über alle neuen Klassen und Funktionen der JDK 8 API erfahren möchten, schauen Sie sich meinen JDK8 API Explorer an. Es hilft Ihnen, alle neuen Klassen und versteckten Schätze von JDK 8 herauszufinden, wie Arrays.parallelSort
, StampedLock
und CompletableFuture
– um nur einige zu nennen.
Ich habe auch eine Reihe von Folgeartikeln auf meinem Blog veröffentlicht, die für Sie interessant sein könnten:
Du solltest mir auf Twitter folgen. Danke fürs Lesen!