IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Java 8 est disponible, la plate-forme se met aux expressions lambdas,
Tour d'horizon des nouveautés

Le , par Hinault Romaric

602PARTAGES

14  0 
Si les versions 6 et 7 de Java étaient des évolutions douces : Java 8 est d'un tout autre ordre. Plus de 56 nouvelles fonctionnalités ont été ajoutées (http://openjdk.java.net/projects/jdk8/features). Les arrivées des lambdas, des méthodes par défaut, des interfaces fonctionnelles et de Stream vont modifier en profondeur le langage et donc l'écosystème Java tout entier. Nous pouvons aussi citer l'incorporation d'une nouvelle API pour gérer les dates, de nouvelles annotations et d’un nouveau moteur d'exécution JavaScript.


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();
}
Le nouveau package java.util.function propose d’ailleurs un certain nombre d’interfaces fonctionnelles répondant à divers usages.

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());
    }
});
En utilisant les lambdas, la nouvelle écriture sera :
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());
Les interfaces fonctionnelles servent aux lambdas, facilitant ainsi l'écriture puisqu'elles permettent d'écrire l'implémentation de façon plus concise. Nous montrons ci-dessous un exemple d'implémentation de l'interface Runnable.
Code : Sélectionner tout
Runnable r1 = () -> { System.out.println("My Runnable"); };
Plus de détails sur les lambdas sont disponibles dans un tutoriel publié récemment : http://soat.developpez.com/tutoriels...t-lambda-java8

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
    2
    Supplier<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
    3
    Random 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
    7
    Function<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
    2
    Function<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);
Cela peut également être une alternative intéressante à l’API de reflection, puisque cela permet un code sécurisé.

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!");
    }
}
Les méthodes par défaut permettent ainsi de faire évoluer l’API des interfaces sans provoquer de grosses incompatibilités dues à l’absence d’implémentation dans les classes qui les implémentent. L’API de base en profite grandement en enrichissant certaines de ses interfaces (en particulier dans l’API de Collections dont les interfaces s’enrichissent de plusieurs méthodes).

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!");
    }
}
Cela peut s’avérer utile pour proposer des méthodes utilitaires liées à l’interface (comme une “fabrique” par exemple).

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() { ... }
Nashorn, le nouveau moteur Javascript (http://openjdk.java.net/jeps/174) : un moteur JavaScript proposé par défaut depuis le JDK. Plus de détails sur cette nouveauté peuvent être trouvés sur le tutoriel publié récemment : http://soat.developpez.com/tutoriels...nashorn-java8/

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 :


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...
Mickael Baron (Mickael Baron)


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 !!!
Frédéric Martini (AdiGuba)


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...
Thierry Leriche-Dessirier (thierryler)


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.
Télécharger la nouvelle version de Java

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.

Une erreur dans cette actualité ? Signalez-nous-la !

Avatar de adiGuba
Expert éminent sénior https://www.developpez.com
Le 19/03/2014 à 10:17
Citation Envoyé par tomlev Voir le message
[*]méthodes par défaut : méthodes d'extension en C#.
Je suis globalement d'accord concernant les apports de la programmation fonctionnelle.

Pas contre je ne le suis pas du tout concernant les méthodes par défaut : il ne s'agit pas vraiment de l'équivalent des méthodes d'extensions de C#.
Ce n'est pas un peu différent, c'est très différent malgré une syntaxe relativement proche.

Pour moi si on devrait citer un équivalent en Java des méthodes d'extensions de C#, je nommerais plutôt l'import static de Java 5.0.
Même si la syntaxe est différente, le résultat est le même : du sucre syntaxique permettant l'appel d'une méthode static.
La seule différence étant que la syntaxe des méthodes d'extensions de C# est syntaxiquement identique à l'appel d'une méthode d'instance (même si ce n'est pas le cas).
En C# si a.method() est une méthode d'extension, la résolution de la méthode se fera uniquement à la compilation, selon les namespaces utilisé.
En Java si method(a) utilise un import static, la résolution de la méthode se fera uniquement à la compilation, selon l'import utilisé.
Bref dans les deux cas on aboutit à un appel de méthode static...

Comme tu le dis, les méthodes par défaut utilisent des méthodes virtuelles. C'est une énorme différence de mon point de vue.
Donc si a.method() est une méthode d'extension, la résolution de la méthode se fera à l'exécution selon le type réel de a, comme pour n'importe quelle méthode d'instance. La seule différence étant que son implémentation n'est pas requise, et que la JVM va alors utiliser une implémentation par défaut au lieu de générer une exception...

De plus ces méthodes appartiennent réellement à l'objet, ce n'est pas juste "pour faire beau" (on peut les retrouver via l'API de reflection par exemple...)

Citation Envoyé par Gugelhupf Voir le message
Les méthodes par défaut et static pour les interfaces peuvent avoir leurs avantages, pour ma part je n'ai pas eu de gros souci de conception à ce niveau.
Les streams (ou LINQ de C#) ne sont selon moi pas aussi puissant que le SQL.
Les méthodes par défaut ne pas d'une grosse utilité pour les développeurs d'applications, puisqu'il reste malgré tout possible de faire évoluer les interfaces en adaptant son code.
C'est par contre nettement plus utile pour les concepteurs d'API, qui ne pouvaient pas modifier leurs interfaces sous peine de "casser" le code de tous les utilisateurs de ces APIs...

Quand aux méthodes static dans les interfaces, c'est juste un petit plus en effet.
A noter toutefois qu'ils corrigent certains "défauts" des méthodes static dans une classe (elle ne "s’héritent" pas, et on doit obligatoirement utiliser la bonne syntaxe NomInderface.method() et non pas nomInstance.method()).

Citation Envoyé par Gugelhupf Voir le message
Ce qui me rebute par contre, c'est de voir toutes ces interfaces qui alourdissent la plateforme alors qu'ils n'ont pas forcément un grand intérêt. Par exemple au niveau du package function, l'interface Function<T, R> peut être utile sachant que les variadic template n'existent pas, mais pourquoi ajouter IntFunction<R>, LongFunction<R>, DoubleFunction<R> ?...
C'est vrai ! Sur les 43 interfaces fonctionnelles, il n'y a que 9 concepts différents. Toutes les autres sont des versions intégrants les types primitifs d'une manière ou d'une autre.
Mais c'est malheureusement un problème lié aux types primitifs...

Mais je pense que les plus grosses limitations viendront des "checked-exceptions", qui ne se marient pas très bien avec les expressions lambdas.
Ou plus précisément pour utiliser une checked-exceptions, il faut impérativement que le type de l'exception soit déclarée dans la signature de la méthode de l'interface fonctionnelle

a++
2  0 
Avatar de tomlev
Rédacteur/Modérateur https://www.developpez.com
Le 18/03/2014 à 21:25
Très bonne nouvelle pour Java, qui fait un énorme pas en avant avec cette nouvelle version

Citation Envoyé par Hinault Romaric Voir le message
Les arrivées des lambdas, des méthodes par défaut, des interfaces fonctionnelles et de Stream vont modifier en profondeur le langage et donc l'écosystème Java tout entier.
Je ne peux pas m'empêcher de faire un parallèle avec la sortie de C# 3 (en 2008), qui apportait à peu près les mêmes fonctionnalités (ou des fonctionnalités équivalentes) :
  • lambdas : idem en C#
  • méthodes par défaut : méthodes d'extension en C#.
    En fait c'est un peu différent, mais le résultat est essentiellement le même ; en C# les méthodes d'extension étaient nécessaires pour faire fonctionner Linq, en Java les méthodes par défaut sont nécessaires pour faire fonctionner Stream. Les méthodes d'extension de C# permettent d'ajouter des méthodes à n'importe quel type ; les méthodes par défaut de Java sont plus restrictives (puisqu'elles doivent être déclarées dans une interface), mais elles sont virtuelles, donc les classes qui implémentent l'interface peuvent les redéfinir. Les deux approches ont donc chacune des avantages et des inconvénients...
  • interfaces fonctionnelles : délégués en C# (ils existaient en fait depuis la première version de C#)
  • Stream : Linq en C#. En fait Linq fournit aussi une autre syntaxe qui se rapproche un peu du SQL ; c'est juste du "syntactic sugar" qui est converti en appels de méthodes, mais ça rend certaines requêtes plus simples à écrire.


C# 3 a profondément transformé la façon de coder de la plupart des développeurs C#, en ajoutant au langage ces éléments issus de la programmation fonctionnelle. Je partage donc l'avis d'Hinault : Java 8 aura probablement le même effet sur le monde Java.

En tous cas si j'ai l'occasion de refaire du Java un jour, je serai bien content d'avoir ces fonctionnalités, dont j'aurais du mal à me passer aujourd'hui
2  1 
Avatar de tomlev
Rédacteur/Modérateur https://www.developpez.com
Le 19/03/2014 à 10:45
Citation Envoyé par adiGuba Voir le message
Pas contre je ne le suis pas du tout concernant les méthodes par défaut : il ne s'agit pas vraiment de l'équivalent des méthodes d'extensions de C#.
Ce n'est pas un peu différent, c'est très différent malgré une syntaxe relativement proche.
Je sais bien, et j'ai d'ailleurs mentionné les différences (sans trop approfondir) dans mon message
C'est vrai que ça n'a pas grand chose à voir techniquement, mais c'est conceptuellement similaire, dans la mesure où les deux fonctionnalités permettent d'ajouter des comportements communs à des interfaces sans avoir à toucher aux implémentations existantes.

Par exemple :
  • en C#, on peut utiliser Linq sur n'importe quelle classe qui implémente l'interface IEnumerable<T> (équivalent de Iterable<T> en Java). Mais on n'allait pas demander à toutes ces classes d'implémenter les méthodes Where (filter), Select (map), Aggregate (reduce), etc. Les méthodes d'extension ont justement été introduites pour ça : ce sont des méthodes statiques qui peuvent être appelées comme des méthodes d'instance, moyennant un peu de "syntactic sugar". Du coup, toutes les implémentations de IEnumerable<T> profitent immédiatement des méthodes d'extension définies dans la classe Enumerable.
  • en Java, l'interface Collection<T> définit une defender method stream, et toutes les classes qui implémentent Collection<T> en profitent sans avoir à changer une ligne de code.
1  0 
Avatar de adiGuba
Expert éminent sénior https://www.developpez.com
Le 19/03/2014 à 11:48
Citation Envoyé par tomlev Voir le message
en Java, l'interface Collection<T> définit une defender method stream, et toutes les classes qui implémentent Collection<T> en profitent sans avoir à changer une ligne de code.
Oui... mais la différence c'est que chaque implémentation sera quand même libre de proposer une implémentation qui lui est plus spécifique, ce qui n'est pas possible avec les extension method...

Et c'est bien le cas d'ailleurs dans l'API standard.
Non pas directement via la méthode stream(), mais via la méthode par défaut spliterator() qui retourne un Spliterator, une sorte de super-Iterator à la base du fonctionnement des Stream.

Cette méthode spliterator() est définie par défaut dans Iterable avec une implémentation basique, mais elle est aussi redéfinie par défaut dans les interface Collection, List, Set, SortedSed pour proposer une implémentation plus spécifique prenant en compte les caractéristiques de chaque type de collection (SIZED, ORDERED, DISTINCT, ...).

Mais ce n'est pas tout, puisque chaque implémentation réelle peut également redéfinir cette méthode pour une version encore plus spécifique, voir même pour optimiser le code de l'itération interne.
C'est ce que font par exemple LinkedList, ArrayList, HashSet, TreeSet et sûrement d'autre...

Une méthode par défaut c'est bien. Une implémentation spécifique c'est mieux

a++
1  0 
Avatar de Traroth2
Membre émérite https://www.developpez.com
Le 19/03/2014 à 12:09
Ah, les affaires reprennent !

Sinon, on pourrait en savoir plus sur la disparition du PermGen space ? Où est stocké le bytecode, désormais ?
1  0 
Avatar de Népomucène
Modérateur https://www.developpez.com
Le 19/03/2014 à 16:14
J'ai une question toute simple : quel est l'intérêt technique de l'Interfaces fonctionnelles ?

Dans mon travail de développement de tous les jours, j'utilise des interfaces à 1,2,3 ... n méthodes.
Ensuite, quand je fais une classe qui implémente l'interface, mon IDE me créé immédiatement le squelette des méthodes.

Quel est l'intérêt technique de distinguer les interfaces à 1 méthode des interfaces à plusieurs méthodes ?
1  0 
Avatar de Saverok
Expert éminent https://www.developpez.com
Le 19/03/2014 à 17:28
Que pensez-vous des nouveautés de Java 8 ?
Enfin une vraie évolution de Java comparable à la 5 !!
Ca fait du bien et renouvelle l'intérêt pour ce langage que j'aime tant.

Pensez-vous migrer prochainement vers cette nouvelle version ?
Pour mes projets perso, oui assurément.
Ca me permettra de faire mumuse et de m'approprier les nouvelles fonctionnalités et syntaxes.
Par contre, au niveau pro, je crains que ça ne soit pas pour demain...
En effet, il y a pas 2 mois, j'ai travaillé sur un projet en 1.4
Et sur mes autres projets, Java 6 pointe tout juste le bout de son nez...
Alors je ne suis pas très optimiste sur son intégration rapide dans le milieu pro mais je me rappelle avoir été surpris par la vitesse d'intégration de Java5 car elle apportait beaucoup donc peut être que Java8 aura le même sort, espérons

Pensez-vous également que les outils sont prêts à recevoir cette nouvelle version ?
Pas encore mais ça viendra vite

Quelle est votre nouveauté préférée ?
Je dois dire que la fin du permgen me fait particulièrement plaisir
Je suis assez content par l'arrivée des Stream qui me facilitera beaucoup la vie car je travaille beaucoup en multithread et que jusqu'à présent, c'était très lourd à gérer.

De belles nuits blanches en perspective pour tester et intégrer tout ça
1  0 
Avatar de ymajoros
Membre habitué https://www.developpez.com
Le 20/03/2014 à 8:08
Sinon, on pourrait en savoir plus sur la disparition du PermGen space ? Où est stocké le bytecode, désormais ?
http://java.dzone.com/articles/java-8-permgen-metaspace

Un autre zone mémoire. On peut y mettre une taille max si on veut.

En gros, si tu as un classloader leak, ça va continuer à être problématique.

J'ai souvent entendu croire que le perm gen space était un problème en soi. Quand on rencontre le fameux "Out of memory: perm gen space", il y a une fuite de mémoire (application, serveur d'app, ...).

Jrockit n'a pas de permgen space, mais je constate régulièrement des class loader leaks dans des applications et sur des serveurs d'apps. Ça a juste tendance à ne pas être remarqué au début.

Pas de magie ici : il faut continuer à corriger les bugs.
1  0 
Avatar de adiGuba
Expert éminent sénior https://www.developpez.com
Le 20/03/2014 à 10:28
Citation Envoyé par la.lune Voir le message
Juste pour expliquer en plus ce que notre expert adiGuba a dit
Juste pour préciser : je ne suis pas un "expert".
C'est juste le titre donné par le forum car j'y ai beaucoup participé (à un époque), mais cela s'arrête là !

Citation Envoyé par la.lune Voir le message
Alors, les interfaces List, Set, SortedSettoutes héritent de l'interface Collection, et vu leur contexte, ils définissent elles aussi chacune une implémentation par défaut de spliterator() en appelant la méthode avec les bon paramètres spécifiques.
Cela va même plus loin car chaque implémentation spécifique (ArrayList, LinkedList, TreeSet, HashSet, etc.) redéfini également la méthode spliterator() pour fournir une version encore mieux adapté...

A titre d'exemple, sur les principales collections cela donne ceci :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
# Nom de la collection => Nom du Spliterator (caractéristiques)
            tableaux => Spliterators$ArraySpliterator (ORDERED, SIZED, SUBSIZED)
           ArrayList => ArrayList$ArrayListSpliterator (ORDERED, SIZED, SUBSIZED)
          LinkedList => LinkedList$LLSpliterator (ORDERED, SIZED, SUBSIZED)
             TreeSet => TreeMap$KeySpliterator (ORDERED, DISTINCT, SORTED, SIZED)
             HashSet => HashMap$KeySpliterator (DISTINCT, SIZED)
       LinkedHashSet => Spliterators$IteratorSpliterator (ORDERED, DISTINCT, SIZED, SUBSIZED)
ConcurrentLinkedQueu => ConcurrentLinkedQueue$CLQSpliterator (ORDERED, NONNULL, CONCURRENT)
ConcurrentSkipListSe => ConcurrentSkipListMap$KeySpliterator (ORDERED, DISTINCT, SORTED, NONNULL, CONCURRENT)
  ArrayBlockingQueue => Spliterators$IteratorSpliterator (ORDERED, NONNULL, CONCURRENT)
 LinkedBlockingQueue => LinkedBlockingQueue$LBQSpliterator (ORDERED, NONNULL, CONCURRENT)
 LinkedBlockingDeque => LinkedBlockingDeque$LBDSpliterator (ORDERED, NONNULL, CONCURRENT)
Citation Envoyé par la.lune Voir le message
Par contre, en prenant l'exemple des interfaces Map et StoredMap, ils n'ont pas fourni d’implémentation par défaut, la déclaration spliterator() n'existe même pas dans l'interface pour se contenter de l’implémentation par défaut existante dans l'interface Collection.
Map n'héritent pas de Collection
Par contre ses méthodes keySet(), values() et entrySet() retournent bien des Collections avec un Spliterator spécifique à chaque fois...

Exemple :
Code : Sélectionner tout
1
2
3
4
5
6
      HashMap$KeySet => HashMap$KeySpliterator (DISTINCT, SIZED)
      HashMap$Values => HashMap$ValueSpliterator (SIZED)
    HashMap$EntrySet => HashMap$EntrySpliterator (DISTINCT, SIZED)
      TreeMap$KeySet => TreeMap$KeySpliterator (ORDERED, DISTINCT, SORTED, SIZED)
      TreeMap$Values => TreeMap$ValueSpliterator (ORDERED, SIZED)
    TreeMap$EntrySet => TreeMap$EntrySpliterator (ORDERED, DISTINCT, SORTED, SIZED)


Citation Envoyé par la.lune Voir le message
Déjà vous avez vu que c'est en quelques lignes de code ajoutés à l'interface Collection que tout est résoudre. Une seule méthode que je n'ai pas mentionné ici ajouté à Collection et codé par défaut utilisant des méthodes aussi virtuelles pure c'est la méthode default boolean removeIf(Predicate<? super E> filter). Et on peut aussi à tout moment faire quelque chose de static à la Java 5 si on veut.
Sauf que même dans cet exemple les méthodes par défaut on un gros avantages sur les méthodes static ou les méthodes d'extensions.

En effet la méthode removeIf() est définie comme ceci :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
    default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        final Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }
Bref c'est une simple itération qui supprime les éléments avec remove(), selon la condition du filtre.

C'est très bien mais cela peut avoir des effets de bord selon l'implémentation de la Collection.
Par exemple l'opération remove() est très couteuse sur une ArrayList, car cela oblige à décaler les éléments du tableau interne. Donc le fait d'appeler plusieurs fois remove() implique que l'on va effectuer plusieurs fois ce décalage (autant de fois qu'il y a d'élément à supprimer), ce qui peut s'avérer assez couteux !

Du coup ArrayList redéfinie cette méthode removeIf() afin de marquer les éléments à supprimer, et de n'effectuer le décalage qu'une seule et unique fois même si on supprime plusieurs éléments...

a++
1  0 
Avatar de adiGuba
Expert éminent sénior https://www.developpez.com
Le 20/03/2014 à 12:09
@tomlev : A la rigueur tu peux dire que c'est syntaxiquement équivalent, mais ce n'est pas fonctionnellement équivalent.

a++
1  0