JavaRush /Blog Java /Random-FR /Fonctionnalités Java 8 – Le guide ultime (partie 1)
0xFF
Niveau 9
Донецк

Fonctionnalités Java 8 – Le guide ultime (partie 1)

Publié dans le groupe Random-FR
La première partie de la traduction de l'article Fonctionnalités Java 8 – Le guide ULTIMATE . La deuxième partie est ici (le lien peut changer). Fonctionnalités Java 8 – Le guide ultime (Partie 1) - 1 Note de l'éditeur : Cet article a été publié alors que Java 8 était disponible au public et tout indique qu'il s'agit bien d'une version majeure. Ici, nous avons fourni en abondance des guides Java Code Geeks tels que Jouer avec Java 8 – Lambdas et concurrence , Guide de l'API de date et d'heure Java 8 : LocalDateTime et Classe abstraite par rapport à l'interface à l'ère Java 8 . Nous proposons également des liens vers 15 didacticiels Java 8 incontournables provenant d'autres sources . Bien sûr, nous examinons certains inconvénients, comme le côté obscur de Java 8 . Il est donc temps de rassembler toutes les principales fonctionnalités de Java 8 en un seul endroit pour votre commodité. Apprécier!

1. Introduction

La sortie de Java 8 est sans aucun doute le plus grand événement depuis Java 5 (sorti il ​​y a assez longtemps, en 2004). Il a apporté de nombreuses nouvelles fonctionnalités à Java tant au niveau du langage, du compilateur, des bibliothèques, des outils que de la JVM (Java Virtual Machine). Dans ce didacticiel, nous allons examiner ces changements et démontrer différents cas d'utilisation avec des exemples réels. Le guide se compose de plusieurs parties dont chacune aborde un aspect spécifique de la plateforme :
  • Langue
  • Compilateur
  • Bibliothèques
  • Outils
  • Environnement d'exécution (JVM)

2. Nouvelles fonctionnalités de Java 8

Dans tous les cas, Java 8 est une version majeure. On peut dire que cela a pris beaucoup de temps en raison de la mise en œuvre des fonctionnalités que tout développeur Java recherchait. Dans cette section, nous allons aborder la plupart d’entre eux.

2.1. Lambdas et interfaces fonctionnelles

Les lambdas (également connues sous le nom de méthodes privées ou anonymes) constituent le changement de langage le plus important et le plus attendu dans toute la version Java 8. Ils nous permettent de spécifier une fonctionnalité en tant qu'argument de méthode (en déclarant une fonction autour) ou de spécifier du code en tant que données. : concepts que tout développeur fonctionnel connaît . De nombreux langages sur la plateforme JVM (Groovy, Scala , ...) avaient des lambdas dès le premier jour, mais les développeurs Java n'avaient d'autre choix que de représenter les lambdas via des classes anonymes. Discuter de la conception des lambdas a demandé beaucoup de temps et d'efforts de la part du public. Mais des compromis ont finalement été trouvés, ce qui a conduit à l’émergence de nouveaux designs concis. Dans sa forme la plus simple, un lambda peut être représenté comme une liste de paramètres séparés par des virgules, un symbole -> et un corps. Par exemple:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) )
Notez que le type de l'argument e est déterminé par le compilateur. De plus, vous pouvez spécifier explicitement le type d'un paramètre en plaçant le paramètre entre parenthèses. Par exemple:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
Dans le cas où le corps lambda est plus complexe, il peut être entouré d'accolades, semblable à une définition de fonction normale en Java. Par exemple:
Arrays.asList( "a", "b", "d" ).forEach( e -< {
    System.out.print( e );
    System.out.print( e );
} );
Un lambda peut faire référence à des membres de classe et à des variables locales (ce qui rend implicitement l'appel efficace, que finalle champ soit accédé ou non). Par exemple, ces 2 extraits sont équivalents :
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );
ET:
final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );
Les Lambdas peuvent renvoyer une valeur. Le type de retour sera déterminé par le compilateur. Une déclaration returnn'est pas requise si le corps du lambda est constitué d'une seule ligne. Les deux extraits de code ci-dessous sont équivalents :
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
ET:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );
Les développeurs du langage ont longuement réfléchi à la manière de rendre les fonctions existantes compatibles avec Lambda. C’est ainsi qu’est né le concept d’interface fonctionnelle. Une interface fonctionnelle est une interface avec une seule méthode. En conséquence, il peut être implicitement converti en expression lambda. java.lang.Runnableet java.util.concurrent.Callabledeux excellents exemples d'interfaces fonctionnelles. En pratique, les interfaces fonctionnelles sont très fragiles : si quelqu'un ajoute ne serait-ce qu'une autre méthode à la définition de l'interface, elle ne sera plus fonctionnelle et le processus de compilation ne se terminera pas. Pour éviter cette fragilité et définir explicitement l'intention d'une interface comme fonctionnelle, une annotation spéciale a été ajoutée dans Java 8 @FunctionalInterface(toutes les interfaces existantes dans la bibliothèque Java ont reçu l'annotation @FunctionalInterface). Regardons cette définition simple d'une interface fonctionnelle :
@FunctionalInterface
public interface Functional {
    void method();
}
Il y a une chose à garder à l'esprit : les méthodes par défaut et les méthodes statiques ne violent pas le principe d'une interface fonctionnelle et peuvent être déclarées :
@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();

    default void defaultMethod() {
    }
}
Les Lambdas sont la fonctionnalité la plus populaire de Java 8. Ils ont tout le potentiel pour attirer davantage de développeurs vers cette merveilleuse plate-forme et fournir une prise en charge intelligente des fonctionnalités de Java pur. Pour des informations plus détaillées, veuillez vous référer à la documentation officielle .

2.2. Interfaces par défaut et méthodes statiques

Java 8 a élargi la définition des interfaces avec deux nouveaux concepts : la méthode par défaut et la méthode statique. Les méthodes par défaut rendent les interfaces quelque peu similaires aux traits, mais servent un objectif légèrement différent. Ils vous permettent d'ajouter de nouvelles méthodes aux interfaces existantes sans rompre la compatibilité ascendante pour les versions précédemment écrites de ces interfaces. La différence entre les méthodes par défaut et les méthodes abstraites est que les méthodes abstraites doivent être implémentées alors que les méthodes par défaut ne le sont pas. Au lieu de cela, chaque interface doit fournir une implémentation dite par défaut, et tous les descendants la recevront par défaut (avec la possibilité de remplacer cette implémentation par défaut si nécessaire). Regardons l'exemple ci-dessous.
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'interface Defaulabledéclare une méthode par défaut notRequired()à l'aide d'un mot-clé defaultdans le cadre de la définition de la méthode. L'une des classes, DefaultableImpl, implémente cette interface en laissant la méthode par défaut telle quelle. Une autre classe, OverridableImpl, remplace l'implémentation par défaut et fournit la sienne. Une autre fonctionnalité intéressante introduite dans Java 8 est que les interfaces peuvent déclarer (et proposer des implémentations) de méthodes statiques. Voici un exemple :
private interface DefaulableFactory {
    // Interfaces now allow static methods
    static Defaulable create( Supplier<Defaulable> supplier ) {
        return supplier.get();
    }
}
Un petit morceau de code combine la méthode par défaut et la méthode statique de l'exemple ci-dessus :
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() );
}
La sortie console de ce programme ressemble à ceci :
Default implementation
Overridden implementation
L'implémentation de la méthode par défaut dans la JVM est très efficace et l'invocation de la méthode est prise en charge par les instructions de bytecode. Les méthodes par défaut ont permis aux interfaces Java existantes d'évoluer sans interrompre le processus de compilation. De bons exemples sont les nombreuses méthodes ajoutées à l'interface java.util.Collection: stream(), parallelStream(), forEach(), removeIf(), ... Bien qu'étant puissantes, les méthodes par défaut doivent être utilisées avec prudence : avant de déclarer une méthode par défaut, vous devez réfléchir à deux fois si cela est vraiment nécessaire car cela peut conduire à aux ambiguïtés et aux erreurs de compilation dans des hiérarchies complexes. Pour des informations plus détaillées, veuillez vous référer à la documentation .

2.3. Méthodes de référence

Les méthodes de référence implémentent une syntaxe utile pour référencer des méthodes ou des constructeurs existants de classes ou d'objets Java (instances). Associées aux expressions lambda, les méthodes de référence rendent les constructions de langage compactes et concises, les rendant ainsi basées sur des modèles. Vous trouverez ci-dessous une classe Carà titre d'exemple de différentes définitions de méthodes. Soulignons les quatre types de méthodes de référence pris en charge :
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() );
    }
}
La première méthode de référence est une référence à un constructeur avec une syntaxe Class::newou une alternative aux génériques Class< T >::new. Notez que le constructeur n'a aucun argument.
final Car car = Car.create( Car::new );
final List<Car> cars = Arrays.asList( car );
La deuxième option est une référence à une méthode statique avec la syntaxe Class::static_method. Notez que la méthode prend exactement un paramètre de type Car.
cars.forEach( Car::collide );
Le troisième type est une référence à une méthode d'instance d'un objet arbitraire d'un certain type avec la syntaxe Class::method. Notez qu'aucun argument n'est accepté par la méthode.
cars.forEach( Car::repair );
Et le dernier et quatrième type est une référence à une méthode d'instance d'une classe spécifique avec la syntaxe instance::method. Veuillez noter que la méthode n'accepte qu'un seul paramètre de type Car.
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
L'exécution de tous ces exemples en tant que programmes Java produit la sortie de console suivante (la référence de classe Carpeut varier) :
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
Pour des informations plus détaillées et des détails sur les méthodes de référence, veuillez vous référer à la documentation officielle .

2.4. Annotations en double

Depuis que Java 5 a introduit la prise en charge des annotations , cette fonctionnalité est devenue très populaire et très largement utilisée. Cependant, l’une des limites de l’utilisation des annotations était le fait qu’une même annotation ne peut pas être déclarée plus d’une fois au même endroit. Java 8 enfreint cette règle et introduit des annotations en double. Cela permet aux mêmes annotations d'être répétées plusieurs fois à l'emplacement où elles sont déclarées. Les annotations en double doivent s'annoter elles-mêmes en utilisant annotation @Repeatable. En fait, il ne s’agit pas tant d’un changement de langage que d’une astuce du compilateur, alors que la technique reste la même. Regardons un exemple simple :
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() );
        }
    }
}
Comme nous pouvons le voir, la classe Filterest annotée avec @Repeatable( Filters. class). Filtersest simplement propriétaire des annotations Filter, mais le compilateur Java essaie de cacher leur présence aux développeurs. Ainsi, l'interface Filterablecontient des annotations Filterqui sont déclarées deux fois (sans mentionner Filters). L'API Reflection fournit également une nouvelle méthode getAnnotationsByType()pour renvoyer des annotations en double d'un certain type (rappelez-vous que Filterable. class.getAnnotation( Filters. class) renverra une instance injectée par le compilateur Filters). Le résultat du programme ressemblera à ceci :
filter1
filter2
Pour des informations plus détaillées, veuillez vous référer à la documentation officielle .

2.5. Inférence de type améliorée

Le compilateur Java 8 a reçu de nombreuses améliorations d'inférence de type. Dans de nombreux cas, des paramètres de type explicites peuvent être définis par le compilateur, rendant ainsi le code plus propre. Regardons un exemple :
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;
    }
}
Et voici l'utilisation avec 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() );
    }
}
Le paramètre type Value.defaultValue()est déterminé automatiquement et n’a pas besoin d’être fourni explicitement. Dans Java 7, le même exemple ne sera pas compilé et doit être réécrit sous la forme <NOBR>Value.<String>defaultValue()</NOBR>.

2.6. Prise en charge étendue des annotations

Java 8 étend le contexte dans lequel les annotations peuvent être utilisées. De nos jours, presque tout peut avoir une annotation : variables locales, types génériques, superclasses et interfaces implémentées, et même les exceptions de méthode. Quelques exemples sont présentés ci-dessous :
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_USEet ElementType.TYPE_PARAMETERdeux nouveaux types d'éléments pour décrire le contexte d'annotation pertinent. Annotation Processing APIa également subi des modifications mineures pour reconnaître de nouveaux types d'annotations en Java.

3. Nouvelles fonctionnalités du compilateur Java

3.1. Noms des paramètres

Au fil du temps, les développeurs Java ont inventé diverses façons de stocker les noms de paramètres de méthode dans le bytecode Java afin de les rendre disponibles au moment de l'exécution (par exemple, la bibliothèque Paranamer ). Enfin, Java 8 crée cette fonction difficile dans le langage (en utilisant l'API et la méthode Reflection Parameter.getName()) et le bytecode (en utilisant le nouvel argument du compilateur 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() );
        }
    }
}
Si vous compilez cette classe sans utiliser d'argument –parameterspuis exécutez le programme, vous verrez quelque chose comme ceci :
Parameter: arg0
Avec le paramètre –parameterspassé au compilateur, la sortie du programme sera différente (le nom réel du paramètre sera affiché) :
Parameter: args
Pour les utilisateurs avancés de Maven, l'argument –parameters peut être ajouté à la compilation à l'aide de la section 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>
Pour vérifier la disponibilité des noms de paramètres, il existe une isNamePresent()méthode pratique fournie par la classe Parameter.

4. Nouveaux outils Java

Java 8 est livré avec un nouvel ensemble d'outils de ligne de commande. Dans cette section, nous examinerons les plus intéressants d’entre eux.

4.1. Moteur Nashorn : jjs

jjs est un moteur Nashorn autonome basé sur une ligne de commande. Il prend une liste de fichiers de code source JavaScript et les exécute. Par exemple, créons un fichier func.js avec le contenu suivant :
function f() {
     return 1;
};

print( f() + 1 );
Pour exécuter ce fichier passons-le en argument à jjs :
jjs func.js
La sortie de la console ressemblera à ceci :
2
Pour plus de détails, consultez la documentation .

4.2. Analyseur de dépendances de classe : jdeps

jdeps est un très bon outil en ligne de commande. Il affiche les dépendances au niveau du package ou de la classe pour les classes Java. Il accepte un fichier .class , un dossier ou un fichier JAR en entrée. Par défaut , jdeps affiche les dépendances sur la sortie standard (console). À titre d'exemple, regardons le rapport de dépendance de la populaire bibliothèque Spring Framework . Pour que l'exemple soit court, examinons uniquement les dépendances du fichier JAR org.springframework.core-3.0.5.RELEASE.jar.
jdeps org.springframework.core-3.0.5.RELEASE.jar
Cette commande génère beaucoup de résultats, nous n’analyserons donc qu’une partie de la sortie. Les dépendances sont regroupées par packages. S'il n'y a pas de dépendances, not found sera affiché .
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
Pour des informations plus détaillées, veuillez vous référer à la documentation officielle .
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION