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

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

Pubblicato nel gruppo Random-IT
La prima parte della traduzione dell'articolo Java 8 Features – The ULTIMATE Guide . La seconda parte è qui (il link può cambiare). Funzionalità di Java 8 – La guida definitiva (Parte 1) - 1 Nota dell'editore : questo articolo è stato pubblicato mentre Java 8 era disponibile al pubblico e tutte le indicazioni indicano che si tratta effettivamente di una versione principale. Qui abbiamo fornito guide Java Code Geeks in abbondanza come Playing with Java 8 – Lambdas and Concurrency , Java 8 Date and Time API Guide: LocalDateTime e Abstract Class vs. Interface in the Java 8 Era . Ci colleghiamo anche a 15 tutorial Java 8 da leggere da altre fonti . Naturalmente esamineremo alcuni degli aspetti negativi, come il lato oscuro di Java 8 . Quindi, è tempo di raccogliere tutte le funzionalità principali di Java 8 in un unico posto per la tua comodità. Godere!

1. Introduzione

Senza dubbio, il rilascio di Java 8 è l'evento più importante dai tempi di Java 5 (rilasciato molto tempo fa, nel 2004). Ha portato molte nuove funzionalità a Java sia nel linguaggio, nel compilatore, nelle librerie, negli strumenti e nella JVM (Java Virtual Machine). In questo tutorial, daremo un'occhiata a questi cambiamenti e dimostreremo diversi casi d'uso con esempi reali. La guida è composta da più parti, ognuna delle quali affronta un aspetto specifico della piattaforma:
  • Lingua
  • Compilatore
  • Biblioteche
  • Utensili
  • Ambiente di runtime (JVM)

2. Nuove funzionalità in Java 8

In ogni caso, Java 8 è una versione importante. Possiamo dire che ci è voluto così tanto tempo per l'implementazione delle funzionalità che ogni sviluppatore Java stava cercando. In questa sezione ne tratteremo la maggior parte.

2.1. Lambda e interfacce funzionali

I Lambda (noti anche come metodi privati ​​o anonimi) rappresentano il cambiamento linguistico più grande e più atteso nell'intera versione di Java 8. Ci consentono di specificare la funzionalità come argomento del metodo (dichiarando una funzione attorno ad esso) o di specificare il codice come dati : concetti con cui ogni sviluppatore funzionale ha familiarità programmazione _ Molti linguaggi sulla piattaforma JVM (Groovy, Scala , ...) avevano lambda fin dal primo giorno, ma gli sviluppatori Java non avevano altra scelta che rappresentare lambda attraverso classi anonime. Il dibattito sul design della lambda ha richiesto molto tempo e impegno da parte del pubblico. Ma alla fine furono trovati dei compromessi che portarono alla nascita di nuovi progetti concisi. Nella sua forma più semplice, un lambda può essere rappresentato come un elenco di parametri separati da virgole, un –> simbolo e un corpo. Per esempio:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) )
Si noti che il tipo dell'argomento e è determinato dal compilatore. Inoltre, è possibile specificare in modo esplicito il tipo di un parametro racchiudendolo tra parentesi. Per esempio:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
Nel caso in cui il corpo lambda sia più complesso, può essere racchiuso tra parentesi graffe, in modo simile alla normale definizione di funzione in Java. Per esempio:
Arrays.asList( "a", "b", "d" ).forEach( e -< {
    System.out.print( e );
    System.out.print( e );
} );
Una lambda può fare riferimento a membri della classe e variabili locali (rendendo implicitamente effettiva la chiamata indipendentemente dal fatto che finalsi acceda o meno al campo). Ad esempio, questi 2 frammenti sono equivalenti:
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );
E:
final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );
Lambda può restituire un valore. Il tipo restituito sarà determinato dal compilatore. Non è necessaria una dichiarazione returnse il corpo della lambda è costituito da una riga. I due frammenti di codice seguenti sono equivalenti:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
E:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );
Gli sviluppatori del linguaggio hanno pensato a lungo a come rendere le funzioni esistenti lambda-friendly. Di conseguenza, è emerso il concetto di interfaccia funzionale. Un'interfaccia funzionale è un'interfaccia con un solo metodo. Di conseguenza, può essere convertito implicitamente in un'espressione lambda. java.lang.Runnablee java.util.concurrent.Callabledue ottimi esempi di interfacce funzionali. In pratica, le interfacce funzionali sono molto fragili: se qualcuno aggiunge anche un solo altro metodo alla definizione dell'interfaccia, questo non sarà più funzionale e il processo di compilazione non verrà completato. Per evitare questa fragilità e definire esplicitamente l'intento di un'interfaccia come funzionale, in Java 8 è stata aggiunta un'annotazione speciale @FunctionalInterface(tutte le interfacce esistenti nella libreria Java hanno ricevuto l'annotazione @FunctionalInterface). Diamo un'occhiata a questa semplice definizione di interfaccia funzionale:
@FunctionalInterface
public interface Functional {
    void method();
}
C'è una cosa da tenere a mente: i metodi predefiniti e i metodi statici non violano il principio di un'interfaccia funzionale e possono essere dichiarati:
@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();

    default void defaultMethod() {
    }
}
I Lambda sono la funzionalità più popolare di Java 8. Hanno tutto il potenziale per attirare più sviluppatori su questa meravigliosa piattaforma e fornire un supporto intelligente per le funzionalità in Java puro. Per informazioni più dettagliate vi invitiamo a fare riferimento alla documentazione ufficiale .

2.2. Interfacce predefinite e metodi statici

Java 8 ha ampliato la definizione di interfacce con due nuovi concetti: metodo predefinito e metodo statico. I metodi predefiniti rendono le interfacce in qualche modo simili ai tratti, ma hanno uno scopo leggermente diverso. Consentono di aggiungere nuovi metodi alle interfacce esistenti senza interrompere la compatibilità con le versioni precedenti di queste interfacce. La differenza tra metodi predefiniti e metodi astratti è che i metodi astratti devono essere implementati mentre i metodi predefiniti no. Invece, ciascuna interfaccia deve fornire una cosiddetta implementazione predefinita e tutti i discendenti la riceveranno per impostazione predefinita (con la possibilità di sovrascrivere questa implementazione predefinita, se necessario). Diamo un'occhiata all'esempio qui sotto.
private interface Defaulable {
    // Интерфейсы теперь разрешают методы по умолчанию,
    // клиент может реализовывать  (переопределять)
    // or не реализовывать его
    default String notRequired() {
        return "Default implementation";
    }
}

private static class DefaultableImpl implements Defaulable {
}

private static class OverridableImpl implements Defaulable {
    @Override
    public String notRequired() {
        return "Overridden implementation";
    }
}
L'interfaccia Defaulabledichiara un metodo predefinito notRequired()utilizzando una parola chiave defaultcome parte della definizione del metodo. Una delle classi, DefaultableImpl, implementa questa interfaccia lasciando il metodo predefinito così com'è. Un'altra classe, OverridableImpl, sovrascrive l'implementazione predefinita e ne fornisce la propria. Un'altra caratteristica interessante introdotta in Java 8 è che le interfacce possono dichiarare (e offrire implementazioni di) metodi statici. Ecco un esempio:
private interface DefaulableFactory {
    // Interfaces now allow static methods
    static Defaulable create( Supplier<Defaulable> supplier ) {
        return supplier.get();
    }
}
Una piccola porzione di codice combina il metodo predefinito e il metodo statico dell'esempio precedente:
public static void main( String[] args ) {
    Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
    System.out.println( defaulable.notRequired() );

    defaulable = DefaulableFactory.create( OverridableImpl::new );
    System.out.println( defaulable.notRequired() );
}
L'output della console di questo programma è simile al seguente:
Default implementation
Overridden implementation
L'implementazione del metodo predefinito nella JVM è molto efficiente e l'invocazione del metodo è supportata dalle istruzioni bytecode. I metodi predefiniti consentivano alle interfacce Java esistenti di evolversi senza interrompere il processo di compilazione. Buoni esempi sono i numerosi metodi aggiunti all'interfaccia java.util.Collection: stream(), parallelStream(), forEach(), removeIf(), ... Sebbene siano potenti, i metodi predefiniti dovrebbero essere usati con cautela: prima di dichiarare un metodo predefinito, dovresti pensarci due volte se questo è davvero necessario in quanto ciò può portare ad ambiguità ed errori di compilazione in gerarchie complesse. Per informazioni più dettagliate fare riferimento alla documentazione .

2.3. Metodi di riferimento

I metodi di riferimento implementano una sintassi utile per fare riferimento a metodi esistenti o costruttori di classi o oggetti Java (istanze). Insieme alle espressioni lambda, i metodi di riferimento rendono i costrutti del linguaggio compatti e concisi, rendendoli basati su modelli. Di seguito è riportata una classe Carcome esempio di diverse definizioni di metodi, evidenziamo i quattro tipi supportati di metodi di riferimento:
public static class Car {
    public static Car create( final Supplier<Car> supplier ) {
        return supplier.get();
    }

    public static void collide( final Car car ) {
        System.out.println( "Collided " + car.toString() );
    }

    public void follow( final Car another ) {
        System.out.println( "Following the " + another.toString() );
    }

    public void repair() {
        System.out.println( "Repaired " + this.toString() );
    }
}
Il primo metodo di riferimento è un riferimento a un costruttore con sintassi Class::newo un'alternativa per generics Class< T >::new. Si noti che il costruttore non ha argomenti.
final Car car = Car.create( Car::new );
final List<Car> cars = Arrays.asList( car );
La seconda opzione è un riferimento a un metodo statico con la sintassi Class::static_method. Tieni presente che il metodo accetta esattamente un parametro di tipo Car.
cars.forEach( Car::collide );
Il terzo tipo è un riferimento a un metodo di un'istanza di un oggetto arbitrario di un certo tipo con la sintassi Class::method. Si noti che il metodo non accetta argomenti.
cars.forEach( Car::repair );
E l'ultimo, il quarto tipo, è un riferimento a un metodo di un'istanza di una classe specifica con la sintassi instance::method. Tieni presente che il metodo accetta solo un parametro di tipo Car.
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
L'esecuzione di tutti questi esempi come programmi Java produce il seguente output della console (il riferimento alla classe Carpuò variare):
Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Per informazioni più dettagliate e dettagli sulle modalità di riferimento si rimanda alla documentazione ufficiale .

2.4. Annotazioni duplicate

Da quando Java 5 ha introdotto il supporto per le annotazioni , questa funzionalità è diventata molto popolare e ampiamente utilizzata. Tuttavia, uno dei limiti dell'utilizzo delle annotazioni era il fatto che la stessa annotazione non può essere dichiarata più di una volta nello stesso posto. Java 8 infrange questa regola e introduce annotazioni duplicate. Ciò consente di ripetere più volte le stesse annotazioni nella posizione in cui sono dichiarate. Le annotazioni duplicate dovrebbero annotarsi utilizzando annotation @Repeatable. In realtà, questo non è tanto un cambiamento nel linguaggio quanto un trucco del compilatore, mentre la tecnica rimane la stessa. Diamo un'occhiata a un semplice esempio:
package com.javacodegeeks.java8.repeatable.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class RepeatingAnnotations {
    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    public @interface Filters {
        Filter[] value();
    }

    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    @Repeatable( Filters.class )
    public @interface Filter {
        String value();
    };

    @Filter( "filter1" )
    @Filter( "filter2" )
    public interface Filterable {
    }

    public static void main(String[] args) {
        for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
            System.out.println( filter.value() );
        }
    }
}
Come possiamo vedere, la classe Filterè annotata con @Repeatable( Filters. class). Filtersè semplicemente il proprietario delle annotazioni Filter, ma il compilatore Java cerca di nasconderne la presenza agli sviluppatori. Pertanto, l'interfaccia Filterablecontiene annotazioni Filterdichiarate due volte (senza menzionare Filters). L'API Reflection fornisce anche un nuovo metodo getAnnotationsByType()per restituire annotazioni duplicate di qualche tipo (ricorda che Filterable. class.getAnnotation( Filters. class) restituirà un'istanza inserita dal compilatore Filters). L'output del programma sarà simile al seguente:
filter1
filter2
Per informazioni più dettagliate vi invitiamo a fare riferimento alla documentazione ufficiale .

2.5. Inferenza del tipo migliorata

Il compilatore Java 8 ha ricevuto molti miglioramenti nell'inferenza dei tipi. In molti casi, i parametri di tipo espliciti possono essere definiti dal compilatore, rendendo così il codice più pulito. Diamo un'occhiata a un esempio:
package com.javacodegeeks.java8.type.inference;

public class Value<T> {
    public static<T> T defaultValue() {
        return null;
    }

    public T getOrDefault( T value, T defaultValue ) {
        return ( value != null ) ? value : defaultValue;
    }
}
Ed ecco l'uso con type Value<String>:
package com.javacodegeeks.java8.type.inference;

public class TypeInference {
    public static void main(String[] args) {
        final Value<String> value = new Value<>();
        value.getOrDefault( "22", Value.defaultValue() );
    }
}
Il parametro del tipo Value.defaultValue()viene determinato automaticamente e non è necessario fornirlo in modo esplicito. In Java 7, lo stesso esempio non verrà compilato e dovrà essere riscritto come <NOBR>Value.<String>defaultValue()</NOBR>.

2.6. Supporto per le annotazioni ampliato

Java 8 espande il contesto in cui è possibile utilizzare le annotazioni. Al giorno d'oggi, quasi tutto può avere un'annotazione: variabili locali, tipi generici, superclassi e interfacce implementate, persino eccezioni di metodi. Di seguito vengono presentati alcuni esempi:
package com.javacodegeeks.java8.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;

public class Annotations {
    @Retention( RetentionPolicy.RUNTIME )
    @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
    public @interface NonEmpty {
    }

    public static class Holder<@NonEmpty T> extends @NonEmpty Object {
        public void method() throws @NonEmpty Exception {
        }
    }

    @SuppressWarnings( "unused" )
    public static void main(String[] args) {
        final Holder<String> holder = new @NonEmpty Holder<String>();
        @NonEmpty Collection<@NonEmpty String> strings = new ArrayList<>();
    }
}
ElementType.TYPE_USEe ElementType.TYPE_PARAMETERdue nuovi tipi di elementi per descrivere il contesto di annotazione rilevante. Annotation Processing APIha anche subito piccole modifiche per riconoscere nuovi tipi di annotazioni in Java.

3. Nuove funzionalità nel compilatore Java

3.1. Nomi dei parametri

Nel corso del tempo, gli sviluppatori Java hanno inventato diversi modi per memorizzare i nomi dei parametri dei metodi nel bytecode Java per renderli disponibili in fase di runtime (ad esempio, la libreria Paranamer ). Infine, Java 8 crea questa difficile funzione nel linguaggio (usando l'API e il metodo Reflection Parameter.getName()) e nel bytecode (usando il nuovo argomento del compilatore javac:) –parameters.
package com.javacodegeeks.java8.parameter.names;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class ParameterNames {
    public static void main(String[] args) throws Exception {
        Method method = ParameterNames.class.getMethod( "main", String[].class );
        for( final Parameter parameter: method.getParameters() ) {
            System.out.println( "Parameter: " + parameter.getName() );
        }
    }
}
Se compili questa classe senza utilizzare un argomento –parameterse poi esegui il programma, vedrai qualcosa del genere:
Parameter: arg0
Con il parametro –parameterspassato al compilatore, l'output del programma sarà diverso (verrà mostrato il nome effettivo del parametro):
Parameter: args
Per gli utenti avanzati di Maven, l'argomento –parameters può essere aggiunto alla compilazione utilizzando la sezione maven-compiler-plugin:
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <configuration>
    <compilerArgument>-parameters</compilerArgument>
    <source>1.8</source>
    <target>1.8</target>
    </configuration>
</plugin>
Per verificare la disponibilità dei nomi dei parametri esiste un comodo isNamePresent()metodo fornito dalla classe Parameter.

4. Nuovi strumenti Java

Java 8 viene fornito con un nuovo set di strumenti da riga di comando. In questa sezione vedremo quelli più interessanti.

4.1. Motore Nashorn: jjs

jjs è un motore Nashorn autonomo basato sulla riga di comando. Prende un elenco di file di codice sorgente JavaScript e li esegue. Ad esempio, creiamo un file func.js con il seguente contenuto:
function f() {
     return 1;
};

print( f() + 1 );
Per eseguire questo file passiamolo come argomento a jjs :
jjs func.js
L'output della console sarà così:
2
Per maggiori dettagli consultare la documentazione .

4.2. Analizzatore di dipendenza della classe: jdeps

jdeps è uno strumento da riga di comando davvero eccezionale. Mostra le dipendenze a livello di pacchetto o classe per le classi Java. Accetta un file .class , una cartella o un file JAR come input. Per impostazione predefinita , jdeps invia le dipendenze all'output standard (console). Ad esempio, diamo un'occhiata al rapporto sulle dipendenze della popolare libreria Spring Framework . Per mantenere l'esempio breve, esaminiamo solo le dipendenze per il file JAR org.springframework.core-3.0.5.RELEASE.jar.
jdeps org.springframework.core-3.0.5.RELEASE.jar
Questo comando restituisce parecchio, quindi analizzeremo solo una parte dell'output. Le dipendenze sono raggruppate per pacchetti. Se non sono presenti dipendenze, verrà visualizzato non trovato .
org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
   org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.io
      -> java.lang
      -> java.lang.annotation
      -> java.lang.ref
      -> java.lang.reflect
      -> java.util
      -> java.util.concurrent
      -> org.apache.commons.logging                         not found
      -> org.springframework.asm                            not found
      -> org.springframework.asm.commons                    not found
   org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.lang
      -> java.lang.annotation
      -> java.lang.reflect
      -> java.util
Per informazioni più dettagliate vi invitiamo a fare riferimento alla documentazione ufficiale .
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION