JavaRush /Java Blog /Random-IT /Funzionalità di Java 8: la guida definitiva (parte 2)
0xFF
Livello 9
Донецк

Funzionalità di Java 8: la guida definitiva (parte 2)

Pubblicato nel gruppo Random-IT
La seconda parte della traduzione dell'articolo Java 8 Features – The ULTIMATE Guide . La prima parte è qui (il link potrebbe cambiare). Funzionalità di Java 8 – La guida definitiva (Parte 2) - 1

5. Nuove funzionalità nelle librerie Java 8

Java 8 ha aggiunto molte nuove classi ed esteso quelle esistenti per supportare meglio la concorrenza moderna, la programmazione funzionale, la data/ora e altro ancora.

5.1. Classe Facoltativo

La famosa NullPointerException è di gran lunga la causa più comune di errori delle applicazioni Java. Molto tempo fa, l'eccellente progetto Guava di Google è stato presentato Optionalcome una soluzione NullPointerExceptionche impedisce che il codice venga inquinato da controlli nulli e di conseguenza incoraggia la scrittura di un codice più pulito. La classe Guava ispirata a Google Optionalfa ora parte di Java 8. OptionalÈ solo un contenitore: può contenere un valore o qualche tipo Тo semplicemente essere null. Fornisce molti metodi utili in modo che i controlli null espliciti non siano più giustificati. Fare riferimento alla documentazione ufficiale per informazioni più dettagliate. Vediamo due piccoli esempi di utilizzo Optional: con e senza 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!" ) );
Il metodo isPresent()restituisce true se l'istanza Optionalcontiene un valore non nullo e false altrimenti. Il metodo orElseGet()contiene un meccanismo di fallback per il risultato se Optionalcontiene null, accettando funzioni per generare un valore predefinito. Il metodo map () trasforma il valore corrente Optionale restituisce una nuova istanza Optional. Il metodo orElse()è simile a orElseGet(), ma invece di una funzione accetta un valore predefinito. Ecco l'output di questo programma:
Full Name is set? false
Full Name: [none]
Hey Stranger!
Diamo una rapida occhiata a un altro esempio:
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();
Il risultato sarà così:
First Name is set? true
First Name: Tom
Hey Tom!
Per informazioni più dettagliate vi invitiamo a fare riferimento alla documentazione ufficiale .

5.2. Flussi

La nuova API Stream ( java.util.stream) introduce una vera programmazione in stile funzionale in Java. È di gran lunga l'aggiunta più completa alla libreria Java e consente agli sviluppatori Java di essere significativamente più efficienti e consente loro anche di creare codice efficiente, pulito e conciso. L'API Stream rende l'elaborazione delle raccolte molto più semplice (ma non limitata ad esse, come vedremo più avanti). Prendiamo come esempio una classe semplice 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 );
        }
    }
}
Il compito ha un senso di punti (o pseudo-difficoltà) e può essere APERTO o CHIUSO . Introduciamo una piccola raccolta di problemi con cui giocare.
final Collection<Task> tasks = Arrays.asList(
    new Task( Status.OPEN, 5 ),
    new Task( Status.OPEN, 13 ),
    new Task( Status.CLOSED, 8 )
);
La prima domanda che intendiamo porci è: quanti punti contengono attualmente i task OPEN ? Prima di Java 8, la soluzione abituale a questo problema sarebbe stata quella di utilizzare un iterator foreach. Ma in Java 8 la risposta sono i flussi: una sequenza di elementi che supportano operazioni di aggregazione sequenziali e parallele.
// Подсчет общего количества очков всех активных задач с использованием sum()
final long totalPointsOfOpenTasks = tasks
    .stream()
    .filter( task -> task.getStatus() == Status.OPEN )
    .mapToInt( Task::getPoints )
    .sum();

System.out.println( "Total points: " + totalPointsOfOpenTasks );
E l'output della console sarà simile a:
Total points: 18
Diamo un'occhiata a cosa sta succedendo qui. Innanzitutto, la raccolta di attività viene convertita in una rappresentazione in streaming. L'operazione quindi filterfiltra tutte le attività con stato CHIUSO . Nel passaggio successivo, l'operazione mapToIntconverte gli stream Taskin stream Integerutilizzando un metodo Task::getPointsper ogni istanza Task. Infine, tutti i punti vengono sommati utilizzando il metodo sum, che fornisce il risultato finale. Prima di passare agli esempi successivi, ci sono alcune note sui thread da tenere a mente (maggiori dettagli qui ). Le operazioni streamsi dividono in operazioni intermedie e finali . Le operazioni intermedie restituiscono un nuovo flusso. Sono sempre pigri; quando eseguono operazioni intermedie come filter, in realtà non eseguono il filtraggio, ma creano invece un nuovo flusso che, una volta completato, contiene gli elementi del flusso originale che corrispondono al predicato specificato. Operazioni finite , come forEache sum, possono essere passate attraverso un flusso per produrre un risultato o un effetto collaterale. Una volta completata l'operazione finale, lo stream è considerato utilizzato e non può essere riutilizzato. In quasi tutti i casi, le operazioni finali tendono a completare il loro attraversamento attraverso l'origine dati sottostante. Un'altra caratteristica preziosa dei thread è il supporto immediato per processi paralleli. Diamo un'occhiata a questo esempio, che trova la somma dei punteggi di tutti i problemi.
// 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 );
Questo è molto simile al primo esempio, tranne per il fatto che stiamo cercando di elaborare tutte le attività in parallelo e calcolare il risultato finale utilizzando il metodo reduce. Ecco l'output della console:
Total points (all tasks): 26.0
Spesso è necessario raggruppare gli elementi secondo un determinato criterio. L'esempio dimostra come i thread possono aiutare in questo.
// Группировка задач по их статусу
final Map<Status, List<Task>> map = tasks
    .stream()
    .collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );
L'output della console sarà il seguente:
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
Per finire con gli esempi di problemi, calcoliamo la percentuale complessiva (o il peso) di ciascun problema della raccolta in base ai punti totali:
// Подсчет веса каждой задачи (How процент от общего количества очков)
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 );
L'output della console sarà così:
[19%, 50%, 30%]
Infine, come notato in precedenza, l'API Stream non è solo per le raccolte Java. Una tipica operazione di I/O, come la lettura di file di testo riga per riga, è un ottimo candidato per l'utilizzo dell'elaborazione del flusso. Ecco un piccolo esempio per dimostrarlo.
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 );
}
Il metodo onConsole, che viene chiamato su un thread, restituisce un thread equivalente con un gestore privato aggiuntivo. Il gestore privato viene chiamato quando close()viene chiamato un metodo su un thread. L'API Stream insieme a lambda e metodi di riferimento insieme ai metodi predefiniti e statici in Java 8 sono la risposta ai moderni paradigmi di sviluppo software. Per informazioni più dettagliate vi invitiamo a fare riferimento alla documentazione ufficiale .

5.3. API data/ora (JSR 310)

Java 8 apporta un nuovo aspetto alla gestione di data e ora fornendo una nuova API di data e ora (JSR 310) . La manipolazione di data e ora è uno dei peggiori punti dolenti per gli sviluppatori Java. Lo standard java.util.Dateseguito java.util.Calendargeneralmente non ha migliorato la situazione (forse l'ha addirittura resa più confusa). È così che è nato Joda-Time : un'ottima alternativa API data/ora per Java . La nuova API Data/Ora in Java 8 (JSR 310) è fortemente influenzata da Joda-Time e ne prende il meglio. Il nuovo pacchetto java.timecontiene tutte le classi per data, ora, data/ora, fusi orari, durate e manipolazione dell'ora . La progettazione dell'API ha preso molto sul serio l'immutabilità: le modifiche non sono consentite (una dura lezione appresa da java.util.Calendar). Se è necessaria una modifica, verrà restituita una nuova istanza della classe corrispondente. Diamo un'occhiata alle classi principali ed esempi del loro utilizzo. La prima classe Clock, che fornisce l'accesso all'istante corrente, alla data e all'ora utilizzando un fuso orario. Clockpuò essere utilizzato al posto di System.currentTimeMillis()e TimeZone.getDefault().
// Получить системное время How смещение UTC
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );
Esempio di output della console:
2014-04-12T15:19:29.282Z
1397315969360
Altre nuove classi che esamineremo sono LocaleDatee LocalTime. LocaleDatecontiene solo la parte della data senza il fuso orario nel sistema di calendario ISO-8601. Di conseguenza, LocalTimecontiene solo una parte del timecode>.
// получить местную date и время время
final LocalDate date = LocalDate.now();
final LocalDate dateFromClock = LocalDate.now( clock );

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

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

System.out.println( time );
System.out.println( timeFromClock );
Esempio di output della console:
2014-04-12
2014-04-12
11:25:54.568
15:25:54.568
LocalDateTimeconcatena LocaleDatee LocalTimee contiene una data e un'ora, ma nessun fuso orario, nel sistema di calendario ISO-8601. Di seguito è riportato un semplice esempio.
// Get the local date/time
final LocalDateTime datetime = LocalDateTime.now();
final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );

System.out.println( datetime );
System.out.println( datetimeFromClock );
Esempio di output della console:
2014-04-12T11:37:52.309
2014-04-12T15:37:52.309
Nel caso in cui sia necessaria la data/ora per un fuso orario specifico, ZonedDateTime. Contiene la data e l'ora nel sistema di calendario ISO-8601. Ecco alcuni esempi per diversi fusi orari.
// Получение даты/времени для временной зоны
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 );
Esempio di output della console:
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]
E infine, diamo un'occhiata alla classe Duration: intervallo di tempo in secondi e nanosecondi. Ciò rende il calcolo tra due date molto semplice. Vediamo come fare:
// Получаем разницу между двумя датами
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() );
L'esempio precedente calcola la durata (in giorni e ore) tra due date, 16 aprile 2014 e 16 aprile 2015 . Ecco un esempio dell'output della console:
Duration in days: 365
Duration in hours: 8783
L'impressione generale della nuova data/ora in Java 8 è molto, molto positiva. In parte perché le modifiche si basano su basi collaudate (Joda-Time), in parte perché questa volta la questione è stata riconsiderata seriamente e le voci degli sviluppatori sono state ascoltate. Per i dettagli vi invitiamo a fare riferimento alla documentazione ufficiale .

5.4. Motore JavaScript Nashorn

Java 8 viene fornito con il nuovo motore JavaScript Nashorn , che consente di sviluppare ed eseguire determinati tipi di applicazioni JavaScript sulla JVM. Il motore JavaScript Nashorn è semplicemente un'altra implementazione di javax.script.ScriptEngine che segue lo stesso insieme di regole per consentire a Java e JavaScript di interagire. Ecco un piccolo esempio.
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;" ) );
Esempio di output della console:
jdk.nashorn.api.scripting.NashornScriptEngine
Result: 2

5.5. Base64

Infine, il supporto per la codifica Base64 è stato introdotto nella libreria standard Java con il rilascio di Java 8. È molto facile da usare, l'esempio lo dimostra.
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 );
    }
}
L'output della console del programma mostra sia il testo codificato che quello decodificato:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
Esistono anche classi per codificatori/decodificatori compatibili con URL e codificatori/decodificatori compatibili con MIME ( Base64.getUrlEncoder()/ Base64.getUrlDecoder(), Base64.getMimeEncoder()/ Base64.getMimeDecoder()).

5.6. Array paralleli

La versione Java 8 aggiunge molti nuovi metodi per l'elaborazione di array paralleli. Forse il più importante di questi è parallelSort(), che può velocizzare notevolmente l'ordinamento su macchine multi-core. Il piccolo esempio seguente mostra la nuova famiglia di metodi ( parallelXxx) in azione.
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();
    }
}
Questo piccolo pezzo di codice utilizza un metodo parallelSetAll()per riempire un array con 20.000 valori casuali. Successivamente viene applicato parallelSort(). Il programma stampa i primi 10 elementi prima e dopo l'ordinamento per mostrare che l'array è effettivamente ordinato. L'output di un programma di esempio potrebbe assomigliare a questo (nota che gli elementi dell'array sono casuali).
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793

5.7. Parallelismo

Nuovi metodi sono stati aggiunti alla classe java.util.concurrent.ConcurrentHashMapper supportare operazioni di aggregazione basate sugli oggetti stream e sulle espressioni lambda appena aggiunti. Alla classe sono stati aggiunti anche nuovi metodi java.util.concurrent.ForkJoinPoolper supportare il pooling condiviso (vedi anche il nostro corso gratuito sulla concorrenza Java ). È stata aggiunta una nuova classe java.util.concurrent.locks.StampedLockper fornire un blocco basato sulle capacità con tre modalità di accesso per il controllo di lettura/scrittura (può essere considerata un'alternativa migliore a quella non così buona java.util.concurrent.locks.ReadWriteLock). Nuove classi aggiunte al pacchetto java.util.concurrent.atomic:
  • Doppio accumulatore
  • DoubleAdder
  • LongAccumulatore
  • LongAdder

6. Nuove funzionalità nell'ambiente runtime Java (JVM)

L'area PermGenè stata ritirata e sostituita da Metaspace (JEP 122). Le opzioni JVM -XX:PermSizee -XX:MaxPermSizesono state sostituite rispettivamente da -XX:MetaSpaceSizee -XX:MaxMetaspaceSize.

7. Conclusione

Il futuro è qui: Java 8 ha fatto avanzare la sua piattaforma offrendo funzionalità che consentono agli sviluppatori di essere più produttivi. È ancora troppo presto per spostare i sistemi di produzione su Java 8, ma l'adozione dovrebbe iniziare lentamente a crescere nei prossimi mesi. Tuttavia, ora è il momento di iniziare a preparare la base di codice per la compatibilità con Java 8 ed essere pronti a incorporare le modifiche di Java 8 quando sarà sufficientemente sicuro e stabile. A testimonianza dell'accettazione di Java 8 da parte della comunità, Pivotal ha recentemente rilasciato Spring Framework con supporto di produzione per Java 8 . Puoi fornire il tuo contributo sulle nuove entusiasmanti funzionalità di Java 8 nei commenti.

8. Fonti

Alcune risorse aggiuntive che trattano in modo approfondito vari aspetti delle funzionalità di Java 8:
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION