JavaRush /Java-Blog /Random-DE /Java 8-Funktionen – Der ultimative Leitfaden (Teil 2)
0xFF
Level 9
Донецк

Java 8-Funktionen – Der ultimative Leitfaden (Teil 2)

Veröffentlicht in der Gruppe Random-DE
Der zweite Teil der Übersetzung des Artikels Java 8 Features – The ULTIMATE Guide . Der erste Teil ist hier (Link kann sich ändern). Java 8-Funktionen – Der ultimative Leitfaden (Teil 2) – 1

5. Neue Funktionen in Java 8-Bibliotheken

Java 8 hat viele neue Klassen hinzugefügt und bestehende erweitert, um moderne Parallelität, funktionale Programmierung, Datum/Uhrzeit und mehr besser zu unterstützen.

5.1. Klasse optional

Die berühmte NullPointerException ist bei weitem die häufigste Ursache für Fehler in Java-Anwendungen. Vor langer Zeit stellte Googles hervorragendes Projekt GuavaOptional eine Lösung vor NullPointerException, die verhindert, dass Code durch Nullprüfungen verunreinigt wird, und dadurch das Schreiben von saubererem Code fördert. Die von Google inspirierte Guava-Klasse Optionalist jetzt Teil von Java 8. OptionalSie ist nur ein Container: Sie kann einen Wert oder einen Typ enthalten Тoder einfach null sein. Es bietet viele nützliche Methoden, sodass explizite Nullprüfungen nicht mehr gerechtfertigt sind. Weitere Informationen finden Sie in der offiziellen Dokumentation . Schauen wir uns zwei kleine Anwendungsbeispiele an Optional: mit und ohne Null.
Optional<String> fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
Die Methode gibt trueisPresent() zurück , wenn die Instanz einen Wert ungleich Null enthält, andernfalls false . Die Methode enthält einen Fallback-Mechanismus für das Ergebnis, wenn es Null enthält, und akzeptiert Funktionen zum Generieren eines Standardwerts. Die Methode „map ()“ transformiert den aktuellen Wert und gibt eine neue Instanz zurück . Die Methode ähnelt der von , akzeptiert jedoch anstelle einer Funktion einen Standardwert. Hier ist die Ausgabe dieses Programms: OptionalorElseGet()OptionalOptionalOptionalorElse()orElseGet()
Full Name is set? false
Full Name: [none]
Hey Stranger!
Werfen wir einen kurzen Blick auf ein weiteres Beispiel:
Optional<String> firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
System.out.println();
Das Ergebnis wird so aussehen:
First Name is set? true
First Name: Tom
Hey Tom!
Ausführlichere Informationen finden Sie in der offiziellen Dokumentation .

5.2. Streams

Die neu hinzugefügte Stream-API ( java.util.stream) führt eine echte funktionale Stilprogrammierung in Java ein. Es ist mit Abstand die umfangreichste Ergänzung der Java-Bibliothek und ermöglicht Java-Entwicklern eine deutlich effizientere Arbeit sowie die Erstellung von effizientem, sauberem und prägnantem Code. Die Stream-API erleichtert die Verarbeitung von Sammlungen erheblich (ist jedoch nicht darauf beschränkt, wie wir später sehen werden). Nehmen wir als Beispiel eine einfache Klasse Task.
public class Streams  {
    private enum Status {
        OPEN, CLOSED
    };

    private static final class Task {
        private final Status status;
        private final Integer points;

        Task( final Status status, final Integer points ) {
            this.status = status;
            this.points = points;
        }

        public Integer getPoints() {
            return points;
        }

        public Status getStatus() {
            return status;
        }

        @Override
        public String toString() {
            return String.format( "[%s, %d]", status, points );
        }
    }
}
Die Aufgabe hat ein gewisses Gespür für Punkte (oder Pseudoschwierigkeiten) und kann entweder OPEN oder CLOSE sein . Lassen Sie uns eine kleine Sammlung von Problemen vorstellen, mit denen Sie spielen können.
final Collection<Task> tasks = Arrays.asList(
    new Task( Status.OPEN, 5 ),
    new Task( Status.OPEN, 13 ),
    new Task( Status.CLOSED, 8 )
);
Die erste Frage, die wir herausfinden wollen, ist: Wie viele Punkte enthalten OPEN- Aufgaben derzeit? Vor Java 8 wäre die übliche Lösung hierfür die Verwendung eines Iterators gewesen foreach. Aber in Java 8 lautet die Antwort Streams: eine Folge von Elementen, die sequentielle und parallele Aggregationsoperationen unterstützen.
// Подсчет общего количества очков всех активных задач с использованием sum()
final long totalPointsOfOpenTasks = tasks
    .stream()
    .filter( task -> task.getStatus() == Status.OPEN )
    .mapToInt( Task::getPoints )
    .sum();

System.out.println( "Total points: " + totalPointsOfOpenTasks );
Und die Konsolenausgabe sieht so aus:
Total points: 18
Schauen wir uns an, was hier vor sich geht. Zunächst wird die Aufgabensammlung in eine Streaming-Darstellung umgewandelt. Der Vorgang filterfiltert dann alle Aufgaben mit dem Status GESCHLOSSEN heraus . Im nächsten Schritt mapToIntwandelt die Operation Streams mithilfe einer Methode für jede Instanz Taskin Streams um . Abschließend werden alle Punkte mit der Methode summiert , was das Endergebnis liefert. Bevor wir mit den nächsten Beispielen fortfahren, sollten Sie einige Hinweise zu Threads beachten (weitere Einzelheiten finden Sie hier ). Die Operationen werden in Zwischen- und Endoperationen unterteilt . Zwischenoperationen geben einen neuen Stream zurück. Sie sind immer faul; wenn sie Zwischenoperationen wie ausführen , führen sie keine eigentliche Filterung durch, sondern erstellen stattdessen einen neuen Stream, der nach Abschluss die Elemente des ursprünglichen Streams enthält, die mit dem angegebenen Prädikat übereinstimmen. Endliche Operationen wie und können durch einen Stream geleitet werden, um ein Ergebnis oder einen Nebeneffekt zu erzeugen. Sobald der letzte Vorgang abgeschlossen ist, gilt der Stream als verwendet und kann nicht erneut verwendet werden. In fast allen Fällen neigen die Endoperationen dazu, ihren Durchlauf durch die zugrunde liegende Datenquelle abzuschließen. Eine weitere wertvolle Funktion von Threads ist die standardmäßige Unterstützung paralleler Prozesse. Schauen wir uns dieses Beispiel an, das die Summe der Punktzahlen aller Probleme ermittelt. IntegerTask::getPointsTasksumstreamfilterforEachsum
// Calculate total points of all tasks
final double totalPoints = tasks
   .stream()
   .parallel()
   .map( task -> task.getPoints() ) // or map( Task::getPoints )
   .reduce( 0, Integer::sum );

System.out.println( "Total points (all tasks): " + totalPoints );
Dies ist dem ersten Beispiel sehr ähnlich, außer dass wir versuchen, alle Aufgaben parallel abzuarbeiten und das Endergebnis mithilfe der Methode zu berechnen reduce. Hier ist die Konsolenausgabe:
Total points (all tasks): 26.0
Oft besteht die Notwendigkeit, Elemente nach einem bestimmten Kriterium zu gruppieren. Das Beispiel zeigt, wie Threads dabei helfen können.
// Группировка задач по их статусу
final Map<Status, List<Task>> map = tasks
    .stream()
    .collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );
Die Konsolenausgabe sieht wie folgt aus:
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
Zum Abschluss der Problembeispiele berechnen wir den Gesamtprozentsatz (oder die Gewichtung) jedes Problems in der Sammlung basierend auf der Gesamtpunktzahl:
// Подсчет веса каждой задачи (Wie процент от общего количества очков)
final Collection<String> result = tasks
    .stream()                                        // Stream<String>
    .mapToInt( Task::getPoints )                     // IntStream
    .asLongStream()                                  // LongStream
    .mapToDouble( points -> points / totalPoints )   // DoubleStream
    .boxed()                                         // Stream<Double>
    .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
    .mapToObj( percentage -> percentage + "%" )      // Stream<String>
    .collect( Collectors.toList() );                 // List<String>

System.out.println( result );
Die Konsolenausgabe sieht folgendermaßen aus:
[19%, 50%, 30%]
Schließlich ist die Stream-API, wie bereits erwähnt, nicht nur für Java-Sammlungen gedacht. Ein typischer E/A-Vorgang, z. B. das zeilenweise Lesen von Textdateien, ist ein sehr guter Kandidat für die Verwendung der Stream-Verarbeitung. Hier ist ein kleines Beispiel, um dies zu beweisen.
final Path path = new File( filename ).toPath();
try( Stream<String> lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
    lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
}
Die Methode onConsole, die für einen Thread aufgerufen wird, gibt einen äquivalenten Thread mit einem zusätzlichen privaten Handler zurück. Der private Handler wird aufgerufen, wenn eine Methode close()für einen Thread aufgerufen wird. Die Stream-API zusammen mit Lambdas und Referenzmethoden sowie Standard- und statischen Methoden in Java 8 sind die Antwort auf moderne Softwareentwicklungsparadigmen. Ausführlichere Informationen finden Sie in der offiziellen Dokumentation .

5.3. Datum/Uhrzeit-API (JSR 310)

Java 8 verleiht der Datums- und Zeitverwaltung ein neues Aussehen, indem es eine neue Datums- und Uhrzeit-API (JSR 310) bereitstellt . Die Manipulation von Datum und Uhrzeit ist einer der größten Schwachpunkte für Java-Entwickler. Die Standardbefolgung java.util.Datehat java.util.Calendardie Situation im Allgemeinen nicht verbessert (vielleicht sogar verwirrender gemacht). So wurde Joda-Time geboren : eine großartige Datums-/Uhrzeit-API-Alternative für Java . Die neue Datums-/Uhrzeit-API in Java 8 (JSR 310) ist stark von Joda-Time beeinflusst und nutzt das Beste daraus. Das neue Paket java.timeenthält alle Klassen für Datum, Uhrzeit, Datum/Uhrzeit, Zeitzonen, Dauer und Zeitmanipulation . Beim API-Design wurde die Unveränderlichkeit sehr ernst genommen: Änderungen sind nicht zulässig (eine harte Lektion aus java.util.Calendar). Wenn eine Änderung erforderlich ist, wird eine neue Instanz der entsprechenden Klasse zurückgegeben. Schauen wir uns die Hauptklassen und Beispiele für ihre Verwendung an. Die erste Klasse Clock, die mithilfe einer Zeitzone Zugriff auf den aktuellen Zeitpunkt, das aktuelle Datum und die aktuelle Uhrzeit bietet. Clockkann anstelle von System.currentTimeMillis()und verwendet werden TimeZone.getDefault().
// Получить системное время Wie смещение UTC
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );
Beispielkonsolenausgabe:
2014-04-12T15:19:29.282Z
1397315969360
Weitere neue Klassen, die wir uns ansehen werden, sind LocaleDateund LocalTime. LocaleDateenthält nur den Datumsteil ohne die Zeitzone im ISO-8601-Kalendersystem. Dementsprechend LocalTimeenthält es nur einen Teil des Timecodes.
// получить местную Datum и время время
final LocalDate date = LocalDate.now();
final LocalDate dateFromClock = LocalDate.now( clock );

System.out.println( date );
System.out.println( dateFromClock );

// получить местную Datum и время время
final LocalTime time = LocalTime.now();
final LocalTime timeFromClock = LocalTime.now( clock );

System.out.println( time );
System.out.println( timeFromClock );
Beispielkonsolenausgabe:
2014-04-12
2014-04-12
11:25:54.568
15:25:54.568
LocalDateTimeverkettet LocaleDateund LocalTimeund enthält ein Datum und eine Uhrzeit, aber keine Zeitzone im ISO-8601-Kalendersystem. Nachfolgend finden Sie ein einfaches Beispiel.
// Get the local date/time
final LocalDateTime datetime = LocalDateTime.now();
final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );

System.out.println( datetime );
System.out.println( datetimeFromClock );
Beispielkonsolenausgabe:
2014-04-12T11:37:52.309
2014-04-12T15:37:52.309
Falls Sie Datum/Uhrzeit für eine bestimmte Zeitzone benötigen, ZonedDateTime. Es enthält Datum und Uhrzeit im ISO-8601-Kalendersystem. Hier sind einige Beispiele für verschiedene Zeitzonen.
// Получение даты/времени для временной зоны
final ZonedDateTime zonedDatetime = ZonedDateTime.now();
final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );

System.out.println( zonedDatetime );
System.out.println( zonedDatetimeFromClock );
System.out.println( zonedDatetimeFromZone );
Beispielkonsolenausgabe:
2014-04-12T11:47:01.017-04:00[America/New_York]
2014-04-12T15:47:01.017Z
2014-04-12T08:47:01.017-07:00[America/Los_Angeles]
Und zum Schluss werfen wir noch einen Blick auf die Klasse Duration: Zeitspanne in Sekunden und Nanosekunden. Dies macht die Berechnung zwischen zwei Daten sehr einfach. Mal sehen, wie das geht:
// Получаем разницу между двумя датами
final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );

final Duration duration = Duration.between( from, to );
System.out.println( "Duration in days: " + duration.toDays() );
System.out.println( "Duration in hours: " + duration.toHours() );
Das obige Beispiel berechnet die Dauer (in Tagen und Stunden) zwischen zwei Daten, dem 16. April 2014 und dem 16. April 2015 . Hier ist ein Beispiel für die Konsolenausgabe:
Duration in days: 365
Duration in hours: 8783
Der Gesamteindruck des neuen Datums/Uhrzeit in Java 8 ist sehr, sehr positiv. Teilweise, weil die Änderungen auf einer kampferprobten Grundlage (Joda-Time) basieren, teilweise, weil dieses Mal das Thema ernsthaft überdacht wurde und die Stimmen der Entwickler gehört wurden. Einzelheiten finden Sie in der offiziellen Dokumentation .

5.4. Nashorn-JavaScript-Engine

Java 8 wird mit der neuen Nashorn-JavaScript-Engine geliefert , mit der Sie bestimmte Arten von JavaScript-Anwendungen auf der JVM entwickeln und ausführen können. Die Nashorn-JavaScript-Engine ist lediglich eine weitere Implementierung von javax.script.ScriptEngine, die denselben Regeln folgt, um die Interaktion von Java und JavaScript zu ermöglichen. Hier ist ein kleines Beispiel.
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName( "JavaScript" );

System.out.println( engine.getClass().getName() );
System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );
Beispielkonsolenausgabe:
jdk.nashorn.api.scripting.NashornScriptEngine
Result: 2

5.5. Base64

Schließlich hielt die Unterstützung der Base64-Kodierung mit der Veröffentlichung von Java 8 Einzug in die Java-Standardbibliothek. Sie ist sehr einfach zu verwenden, das Beispiel zeigt dies.
package com.javacodegeeks.java8.base64;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class Base64s {
    public static void main(String[] args) {
        final String text = "Base64 finally in Java 8!";

        final String encoded = Base64
            .getEncoder()
            .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
        System.out.println( encoded );

        final String decoded = new String(
            Base64.getDecoder().decode( encoded ),
            StandardCharsets.UTF_8 );
        System.out.println( decoded );
    }
}
Die Konsolenausgabe des Programms zeigt sowohl kodierten als auch dekodierten Text:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
Es gibt auch Klassen für URL-freundliche Encoder/Decoder sowie MIME-freundliche Encoder/Decoder ( Base64.getUrlEncoder()/ Base64.getUrlDecoder(), Base64.getMimeEncoder()/ Base64.getMimeDecoder()).

5.6. Parallele Arrays

Die Java 8-Version fügt viele neue Methoden für die parallele Array-Verarbeitung hinzu. Das vielleicht wichtigste davon ist parallelSort(), das die Sortierung auf Multi-Core-Maschinen erheblich beschleunigen kann. Das kleine Beispiel unten zeigt die neue Methodenfamilie ( parallelXxx) in Aktion.
package com.javacodegeeks.java8.parallel.arrays;

import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;

public class ParallelArrays {
    public static void main( String[] args ) {
        long[] arrayOfLong = new long [ 20000 ];

        Arrays.parallelSetAll( arrayOfLong,
            index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();

        Arrays.parallelSort( arrayOfLong );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();
    }
}
Dieser kleine Codeabschnitt verwendet eine Methode parallelSetAll(), um ein Array mit 20.000 Zufallswerten zu füllen. Danach wird es aufgetragen parallelSort(). Das Programm gibt die ersten 10 Elemente vor und nach der Sortierung aus, um anzuzeigen, dass das Array tatsächlich sortiert ist. Eine Beispielprogrammausgabe könnte so aussehen (Beachten Sie, dass die Elemente des Arrays zufällig sind).
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793

5.7. Parallelität

Der Klasse wurden neue Methoden hinzugefügt, java.util.concurrent.ConcurrentHashMapum Aggregationsoperationen basierend auf den neu hinzugefügten Stream-Objekten und Lambda-Ausdrücken zu unterstützen. Außerdem wurden der Klasse neue Methoden hinzugefügt, java.util.concurrent.ForkJoinPoolum Shared Pooling zu unterstützen (siehe auch unseren kostenlosen Kurs zur Java-Parallelität ). Es wurde eine neue Klasse java.util.concurrent.locks.StampedLockhinzugefügt, um fähigkeitsbasiertes Sperren mit drei Zugriffsmodi für die Lese-/Schreibsteuerung bereitzustellen (sie kann als bessere Alternative zur nicht so guten Klasse angesehen werden java.util.concurrent.locks.ReadWriteLock). Neue Klassen, die dem Paket hinzugefügt wurden java.util.concurrent.atomic:
  • Doppelakkumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

6. Neue Funktionen in der Java-Laufzeitumgebung (JVM)

Der Bereich PermGenwurde stillgelegt und durch Metaspace (JEP 122) ersetzt. JVM-Optionen -XX:PermSizeund -XX:MaxPermSizewurden jeweils durch -XX:MetaSpaceSizeund ersetzt -XX:MaxMetaspaceSize.

7. Fazit

Die Zukunft ist da: Java 8 hat seine Plattform durch die Bereitstellung von Funktionen weiterentwickelt, die es Entwicklern ermöglichen, produktiver zu arbeiten. Es ist noch zu früh, Produktionssysteme auf Java 8 umzustellen, aber die Akzeptanz dürfte in den nächsten Monaten langsam zunehmen. Jetzt ist es jedoch an der Zeit, Ihre Codebasis auf die Kompatibilität mit Java 8 vorzubereiten und bereit zu sein, Java 8-Änderungen zu integrieren, wenn sie sicher und stabil genug ist. Als Beweis für die Akzeptanz von Java 8 in der Community hat Pivotal kürzlich das Spring Framework mit Produktionsunterstützung für Java 8 veröffentlicht . Sie können in den Kommentaren Ihren Beitrag zu den spannenden neuen Funktionen in Java 8 abgeben.

8. Quellen

Einige zusätzliche Ressourcen, die verschiedene Aspekte der Java 8-Funktionen ausführlich besprechen:
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION