Java 8 devrait ainsi avoir un impact au moins aussi important que Java 5 à son époque (rappelez-vous l'apparition des Generics). Il faut donc s'y préparer dès à présent. Voici quelques nouveautés plus en détail.
Les nouveautés du langage
Interfaces fonctionnelles : connues précédemment sous le nom de Single Abstract Method interfaces (SAM Interfaces), cette nouveauté introduit les interfaces qui possèdent uniquement une seule méthode d’instance abstraite. Les plus connues sont java.lang.Runnable, java.awt.event.ActionListener, java.util.Comparator. Avec Java 8, elles portent le nom d'interfaces fonctionnelles. Dès lors qu'une interface possède une seule méthode d’instance abstraite, elle est désignée comme interface fonctionnelle. Il est aussi possible d'annoter l'interface par @FunctionalInterface. Si une interface est annotée ainsi et possède plus d'une méthode d’instance abstraite, une erreur de compilation sera produite. C'est un peu le même principe qu'avec l'annotation @Override.
L'interface ci-dessous Runnable possède une méthode et est annotée @FunctionalInterface.
Code : | Sélectionner tout |
1 2 3 4 | @FunctionalInterface public interface Runnable { void run(); } |
Lambdas : il s'agit de la plus grosse nouveauté de Java 8. Décrite depuis la JSR 335 (https://jcp.org/en/jsr/detail?id=335), cette fonctionnalité permet d'apporter la puissance de la programmation fonctionnelle dans Java. Une expression lambda peut être assimilée à une fonction anonyme, ayant potentiellement accès au contexte (variables locales et/ou d'instance) du code appelant. Ces "fonctions anonymes" peuvent être affectées dans une interface fonctionnelle. Le code de l’expression lambda servira ainsi d’implémentation pour la méthode abstraite de l’interface. On peut donc les utiliser avec n'importe quel code Java utilisant une telle interface, à condition que les signatures de la méthode correspondent à celle de l’expression lambda.
La syntaxe utilisée est la suivante : (paramètres) -> code ou (paramètres) -> {code} quand il y a plus d'une instruction.
Prenons l'exemple du tri des éléments d'une collection.
Code : | Sélectionner tout |
1 2 3 4 5 6 | Arrays.sort(testStrings, new Comparator<String>() { @Override public int compare(String s1, String s2) { return(s1.length() - s2.length()); } }); |
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | // Forme longue : Arrays.sort(testStrings, (String s1, String s2) -> { return s1.length() – s2.length(); }); // Forme courte (possible uniquement s’il n’y a qu’une instruction) : Arrays.sort(testStrings, (String s1, String s2) -> s1.length() – s2.length()); // Forme courte avec type implicite des paramètres // (le type est déduit par le compilateur via l’inférence) Arrays.sort(testStrings, (s1, s2) -> s1.length() – s2.length()); |
Code : | Sélectionner tout |
Runnable r1 = () -> { System.out.println("My Runnable"); };
Références de méthode : une référence de méthode est utilisée pour définir une méthode en tant qu’implémentation de la méthode abstraite d’une interface fonctionnelle. La notation utilise le nom de la classe ou une instance de la classe, suivi de l'opérateur « :: » et du nom de la méthode à référencer. Le type des paramètres sera déduit du contexte selon l’interface fonctionnelle vers laquelle on affecte la référence.
On peut distinguer quatre types de méthodes références :
- Les références vers une méthode static, qui s’utilisent toujours avec le nom de la classe en préfixe. La signature de la référence correspond alors à la signature de la méthode.
Code : Sélectionner tout 1
2Supplier<Double> random = Math::random; double result = random.get(); // Math.random();
- Les références vers une méthode d’instance, liées à une instance spécifique, qui s’utilisent toujours avec l’instance en préfixe. Ici également, la signature de la référence correspond à la signature de la méthode, et tous les appels s’appliqueront sur l’instance définie dans la référence de méthode :
Code : Sélectionner tout 1
2
3Random r = new Random(); Supplier<Double> random2 = r::nextDouble; double result2 = random2.get(); // r.nextDouble();
- Les références vers une méthode d’instance, mais sans lien avec une instance précise. Comme pour les méthodes static, on utilisera comme préfixe le nom de la classe. La signature de la référence correspond alors à la signature de la méthode, précédée par un argument du type de la classe, qui correspondra à l’instance sur laquelle on appellera la méthode :
Code : Sélectionner tout 1
2
3
4
5
6
7Function<Random,Double> random3 = Random::nextDouble; Random r1 = new Random(); Random r2 = new Random(); Random r3 = new Random(); double result1 = random3.apply(r1); // r1.nextDouble(); double result2 = random3.apply(r2); // r2.nextDouble(); double result3 = random3.apply(r3); // r2.nextDouble();
- Enfin, il est possible de référencer un constructeur en utilisant le mot-clef “new” comme nom de méthode. Très pratique pour créer une fabrique :
Code : Sélectionner tout 1
2Function<String, Thread> factory = Thread::new; Thread t = factory.apply("name"); // new Thread("name");
Les références de méthodes sont une alternative aux expressions lambdas, lorsqu’il n’y a qu’une seule et unique méthode à exécuter, pour une syntaxe encore plus claire :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | Random r = new Random(); Supplier<Double> random = Math::random; Supplier<Double> random2 = r::nextDouble; Function<Random,Double> random3 = Random::nextDouble; Function<String, Thread> factory = Thread::new; Supplier<Double> random = () -> Math.random(); Supplier<Double> random2 = () -> r->nextDouble(); Function<Random,Double> random3 = (Random random) -> random.nextDouble(); Function<String, Thread> factory = (String name) -> new Thread(name); |
Méthodes par défaut (Defender Methods) : cette fonctionnalité permet de proposer une implémentation dite par "défaut" aux méthodes déclarées dans les interfaces. Par conséquent, depuis Java 8, une interface Java contient du code. L'avantage est de pouvoir faire évoluer les interfaces sans avoir à tout casser.
Dans l'exemple ci-dessous, une interface Person déclare deux méthodes. La méthode sayHello est dite par défaut via le mot clé default. Toute implémentation de Person imposera que la méthode sayGoodBye() soit implémentée. Pour sayHello, l'implémentation ne sera pas obligatoire, même si elle reste bien sûr possible.
Code : | Sélectionner tout |
1 2 3 4 5 6 | interface Person { void sayGoodBye(); default void sayHello() { System.out.println("Hello there!"); } } |
Plus de détails sur cette nouveauté peuvent être trouvés sur le tutoriel publié dernièrement : http://oliviercroisier.developpez.co...es-interfaces/
Méthodes static dans les interfaces : Java 8 propose également la possibilité de créer des méthodes statiques depuis une interface Java.
Dans l'exemple ci-dessous, une interface Person déclare une méthode statique.
Code : | Sélectionner tout |
1 2 3 4 5 | interface Person { static void sayHello() { System.out.println("Hello there!"); } } |
Les mises à jour de l'API
Stream et parallèles streams sur les collections : Java 8 apporte également la notion de Stream, qui représente un flux de données que l'on peut manipuler à la volée. L'utilisation d'un Stream se compose de trois parties :
- la création du flux à partir d'une source de données qui peut être très variée (un tableau, une collection, un flux d'entrée/sortie, des données générés à la volée, etc.)*;
- des opérations intermédiaires, qui permettent de transformer le flux en un autre flux (en filtrant des données ou en les transformant par exemple)*;
- une opération terminale, qui permet d'obtenir un résultat ou d'effectuer une opération spécifique.
Par exemple, pour créer un flux à partir d'une collection, on utilisera tout simplement la nouvelle méthode stream() de Collection.
On peut alors appliquer autant d'opérations intermédiaires que nécessaire, comme filter(), qui permet de filtrer certaines données, map() qui permet de modifier la donnée à la volée, distinct() qui permet d'éviter les doublons, sorted() qui permet de les trier, ou encore limit() qui restreint la taille des données.
Une fois toutes nos opérations intermédiaires effectuées, il faut alors appliquer la méthode finale qui va déterminer notre action. On peut par exemple utiliser forEach() pour itérer sur toutes ces valeurs, min()/max() pour récupérer seulement la valeur min/max, finAny()/findFirst() pour récupérer un élément correspondant aux critères, etc.
Bien sûr, toutes ces opérations sont pensées pour être utilisées avec des expressions lambdas ou des références de méthodes.
API java.time (http://openjdk.java.net/jeps/150) : la nouvelle API date, heure et calendrier basée sur la JSR 310 est disponible dans les packages java.time.*. Les classes sont désormais immuables et thread-safe. L'utilisation des méthodes chaînables rendent le code plus lisible. La nouvelle API différencie également deux modèles de temps : le temps machine et le temps humain. Pour une machine, le temps n’est qu’un entier augmentant depuis l’epoch (01 janvier 1970 00h00min00s0ms0ns). Pour un humain en revanche, il s’agit d’une succession de champs ayant une unité (année, mois, jours, heure, etc.). À noter également que le mois "0" n'existe plus et le mois "1" correspond au mois de janvier. Plus de détails sur cette nouveauté peuvent être trouvés sur le tutoriel publié dernièrement : http://soat.developpez.com/tutoriels...me-date-java8/.
Annotations multiples (http://openjdk.java.net/jeps/120) : Java 8 offre maintenant la possibilité de répéter plusieurs fois une annotation de même type pour un élément donné d'un programme.
Dans l'exemple ci-dessous, l’annotation @Schedule est utilisée deux fois, ce qui n'était pas permis dans les versions antérieures.
Code : | Sélectionner tout |
1 2 3 | @Schedule(dayOfMonth="last") @Schedule(dayOfWeek="Fri", hour="23") public void doPeriodicCleanup() { ... } |
Nous pourrions continuer la liste des modifications en précisant également la suppression du PermGen pour la machine virtuelle, les nombreux ajouts pour JavaFX (http://pixelduke.wordpress.com/2013/...part-i-javafx/) ou finalement l'ajout de classes dont l'intérêt va se découvrir au fil du temps. L'on peut citer, par exemple, les classes LongAdder et DoubleAdder (http://blog.palominolabs.com/2014/02...vs-atomiclong/).
Vous l'aurez compris, Java 8 nous apporte une vraie évolution dans le paysage Java. Pour plus de détails, n'hésitez pas à faire un tour sur :
- le site OpenJDK http://openjdk.java.net/projects/jdk8/features
- la discussion sur le forum proposée par AdiGuba : http://www.developpez.net/forums/d13...va-8-ensemble/
Les avis de l'équipe Java de Developpez.com
Fabrice Bouyé (bouye)
Le JDK 8 est enfin là !
À la vue des expressions lambdas, certains diront qu'avec quelques années de retard, Java met enfin à niveau sa syntaxe événementielle pour rattraper celle de C#, Groovy ou encore Scala.
Cependant, on aurait tort de penser qu'il s'agit là d'un simple sucre syntaxique destiné à donner un aspect moderne au langage et à le rendre plus attrayant !
Ces nouvelles fonctionnalités arrivent, en effet, avec une refonte majeure de la JVM, que ce soit au niveau du support des expressions lambdas elles-mêmes, des références de fonctions (similaires aux pointeurs de fonctions pour ceux ayant fait du C/C++) ou encore de la refonte des collections par l'introduction des streams.
Outre l'aspect extérieur (les API que nous, programmeurs, sommes amenés à manipuler), désormais la JVM s'adapte aux traitements en parallèle et au support du multicœur, et le tout sans devoir manuellement manipuler des threads ou des workers !
De plus, l'excellente bibliothèque des gestions du temps JODA-Time a fini par évoluer pour devenir le JSR 310, la nouvelle API de temps et de date du JDK 8.
Simple, efficace et pratique d'emploi, elle promet de nous faire rapidement oublier les fastidieuses opérations sur java.util.Calendar et java.util.Date, toujours promptes à créer toutes sortes de fiascos temporels si on n'y prend pas garde.
J'attends beaucoup de ce premier pack de fonctionnalités. Cependant, je garde également l’œil ouvert sur ce qui est encore à venir, ces autres fonctionnalités qui n'ont pu être intégrées au JDK 8 (jigsaw, etc.).
Les plans pour le futur JDK 9 semblent toujours aussi appétissants...
Je suis très enthousiaste de l'arrivée de Java 8 et des nombreuses fonctionnalités qui sont proposées. À mon avis, il faudra un certains temps afin de maîtriser toutes les subtilités des améliorations. Je compte sur les nombreux rédacteurs de Developpez.com pour nous faire découvrir tous les secrets de cette version.
Je suis également curieux de voir comment cette nouvelle version va s'imposer dans le monde de l'entreprise où on croise encore des JDK 1.4.
La fonctionnalité la plus intéressante de mon point de vue reste les implémentations par défaut, je regrette même qu'elle arrive aussi tard. Les évolutions des interfaces seront plus faciles à réaliser. Pour les lambdas, je ne suis pas encore assez expérimenté pour en comprendre toutes les subtilités mais je compte bien m'y mettre quand mon Eclipse préféré sera disponible.
Bienvenue Java 8 !!!
Java continue son évolution.
D'aucuns diront qu'il s'agit d'une évolution bien lente. Je parlerais plutôt d'évolution prudente et intelligente.
Certes Java ne fait pas la course aux fonctionnalités, mais elles ont le mérite d'être réfléchies et pesées.
Je trouve par exemple la notion d'interface fonctionnelle particulièrement ingénieuse, car non seulement cela permet de ne pas s'embêter à manipuler un nouveau type de données (des "fonctions-types" ou des types delegate), mais cela permet également une retro-compatibilité avec de nombreuses APIs existantes, et cela sans le moindre effort !
Les expressions Lambdas (et les références de méthode) permettront également de concevoir de nouveaux types d'APIs, mais n'oublions pas non plus les méthodes par défaut, qui permettront une meilleure évolution des APIs existantes et futures (et d'ailleurs l'API de Collections en profite bien).
Je pense vraiment qu'il s'agit d'une version qui marquera sans doute un tournant aussi important que J2SE 5.0...
Selon moi, les trois points les plus importants de cette nouvelle version sont 1) les Lambdas 2) les streams et 3) la disparition du permgen.
L'ajout des interfaces fonctionnelles et des méthodes par défaut a été imposé par les Lambdas. Ça reste très intéressant malgré tout. À mon sens, c'est tout de même au second plan.
Les Lambdas sont synonymes de programmation fonctionnelle. Si ce n'était qu'une question de syntaxe, ça aurait eu moins d'impact. Des frameworks tels que Guava le proposent déjà, comme je le montre dans l'article intitulé "Tutoriel d'introduction à la programmation fonctionnelle avec Guava".
Avec Java 8, on va bien plus loin. On a aussi accès au "reduce" du "filter-map-reduce" mais, et surtout, cela travaille pour de vrai dans un contexte multithread/multicoeur. On pouvait déjà travailler en multicoeur en Java, surtout depuis Java 7 qui a bien défriché le terrain pour Java 8, mais il fallait se coltiner les points techniques à chaque fois, avec les risques que cela comporte et avec une syntaxe non adaptée. Java 8 rend cela "simple".
Il ne faut pas oublier que les fabricants de processeurs ne cherchent plus à augmenter la fréquence. C'est désormais une course aux cœurs. L'avenir est donc aux traitements parallèles et aux lambdas. Encore faudra-t-il bien comprendre les patterns de la programmation fonctionnelle, pour ne pas les utiliser de travers et à tout-va.
Le permgen, quant à lui, disparaît et ce n'est pas trop tôt. On pointe les projecteurs sur le langage en oubliant qu'il se passe des choses en coulisse et plus spécifiquement du côté de la JVM. N'oublions pas que Java, c'est un langage et une Virtual Machine. C'est très important. La VM est conçue pour optimiser les programmes (plan d’exécution) à l'utilisation. Ce n'est pas juste un lanceur de classe Main. Avec la disparition du permgen, on voit le rapprochement des plusieurs éditeurs de JVM/GC qui porte ses fruits.
Et vous ?
- Que pensez-vous des nouveautés de Java 8 ?
- Pensez-vous migrer prochainement vers cette nouvelle version ?
- Pensez-vous également que les outils sont prêts à recevoir cette nouvelle version ?
- Quelle est votre nouveauté préférée ?
- Détaillez une nouveauté dans la discussion.
PS : un remerciement spécial à tous les membres ayant participé à l'élaboration de cette annonce : Mickael Baron, AdiGuba, bouye, Robin56, lunatix, CyaNnOrangehead, thierryler, alain.bernard, ced, Yohan Beschi de SOAT pour les tutoriels sur les nouveautés Java 8 (API Date et Time, Nashorn et le projet Lambda) et Olivier Croisier pour son article sur les nouveautés au niveau des interfaces.