Près de deux mois après la sortie de la préversion de Java SE 8, lors de la conférence JavaOne, Oracle publie une déclinaison de la plateforme de développement pour ARM.
La préversion de Java SE 8 pour ARM va permettre de développer des applications Java pour les systèmes embarqués et autres dispositifs reposant sur l’architecture ARM à l’instar du Raspberry Pi. Elle embarque une version de JavaFX (sur Linux), le framework de développement d’Applications Internet Riches.
Pour rappel, Java SE 8 introduira comme nouveautés : les expressions lambda, le moteur JavaScript Nashorn, les annotations, la nouvelle API « date and time » et bien plus.
La sortie de la version finale de la plateforme est prévue pour septembre 2013.
Il y a de quoi bien s'amuser même si l'inference des types n'est pas finalisé (du coup il faut parfois caster explicitement, ce qui alourdit la syntaxe).
Quelques exemples vites fait :
1 - On peut effectuer des itérations internes sur les éléments Iterable :
Iterable<String> iterable = ...
// Ceci :
for (String s : iterable) {
System.out.println(s);
}
// Peut-être remplacé par cela :
iterable.forEach((String s) -> {
System.out.println(s);
});
Par defaut cela revient à la même chose (c'est à dire à utiliser un Iterator), mais chaque implémentation peut définir sa propre implémentation de forEach() plus optimisé (c'est le cas par exemple d'ArrayList qui effectuera directement le parcours sur son tableau interne).
Au passage ce code peut être encore plus compacte, de diverses manières :
// Expression Lambda complète, dont l'expression peut prendre plusieurs lignes :
iterable.forEach((String s) -> {
System.out.println(s);
});
// Lorsque le code de l'expression se résume à une ligne,
// on peut se passer des crochet et du point-virgule final { ... ;}
iterable.forEach((String s) -> System.out.println(s));
// Le type du (ou des) paramètre est déduit du contexte
// il est donc possible de s'en passer :
iterable.forEach((s) -> System.out.println(s));
// Lorsqu'on n'a qu'un seul et unique paramètre,
// on peut se passer des parenthèses :
iterable.forEach(s -> System.out.println(s));
Mieux encore, lorsqu'on se contente d’appeler une méthode, et que sa signature correspond à l'interface fonctionnelle, on peut remplacer l'expression Lambda par un pointeur de méthode (notez bien les doubles deux-points :: )
Map<String, Object> map = ...
// Avec un Iterator on se ballade plein de chose inutile :
for (Map.Entry<String,Object> e : map.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
System.out.println(k + " = " + v);
}
// Alors qu'on peut aller droit à l'essentiel avec les expr. lambda :
map.forEach((k,v) -> {
System.out.println(k + " = " + v);
});
L'API de Collections profite des "Default Method" pour se doter de quelques méthodes utiles, exemple :
List<String> list = ...
// Passer tous les éléments de la liste en majuscule :
list.replaceAll(String::toUpperCase);
// Supprimer tous les éléments de moins de 4 caractères :
list.removeAll(s -> s.length() < 4);
// Trier les éléments selon l'ordre naturel (compareTo()) :
list.sort(Comparators.naturalOrder());
// Trier les éléments selon leurs tailles :
list.sort(Comparators.comparing(String::length));
Note : cette dernière ligne ne fonctionne pas encore à cause de l'inférence des types qui n'est pas finalisé, et pour le moment il faut spécifier explicitement le paramétrage Generics, ce qui ne sera plus le cas dans la version finale :
Mais le plus gros ajout vient des streams : lorsqu'on utilise un stream, on peut filtrer ou modifier les valeurs de la source de données, sans modifier cette source.
Collection<String> coll = ...
// On prend tous les éléments de 6 caractères,
// en ignorant les doublons,
// que l'on va trier par taille,
// puis convertir en majuscule,
// pour finalement afficher les valeurs :
coll.stream()
.filter(s -> s.length() < 6)
.uniqueElements()
.sorted(Comparators.<String, Integer>comparing(String::length))
.map(String::toUpperCase)
.forEach(System.out::println);
// Même chose que ci-dessus, mais en stockant le résultat dans une ArrayList :
List<String> result = coll.stream()
.filter(s -> s.length() < 6)
.uniqueElements()
.sorted(Comparators.<String, Integer>comparing(String::length))
.map(String::toUpperCase)
.into(new ArrayList<String>());
// Afficher seulement 5 éléments, après le 10ième
// (cela permet de mettre en place un système de pagination)
coll.stream()
.slice(10, 5)
.forEach(System.out::println);
// Recherche de la valeur minimum, selon l'ordre naturel :
String min = coll.stream()
.min(Comparators.naturalOrder())
.get();
La grosse force des "stream" étant de pouvoir paralléliser les traitements.
En effet il suffit d'utiliser la méthode parallelStream() à la place de stream() pour que le traitement soit effectuer en parallèle (si cela est possible bien entendu).
abiGuba, quels seront les différences entre ces interfaces et une classe abstraite ?
Les interfaces ne peuvent pas contenir d'état car elles ne peuvent pas contenir d'attribut d'instance.
Envoyé par Gugelhupf
Est-ce que c'est semblable aux Traits et Mixins ?
Oui cela s'en approche beaucoup.
Envoyé par Gugelhupf
Comment résoudre le problème d'héritage multiple si une classe implémente 2 interfaces qui contiennent une méthode defaut avec le même nom mais un contenu différent ?
S'il n'y a aucun lien entre les deux interfaces, cela provoquera une erreur de compilation. Il faudra alors l'implémenter.
On peut bien sûr se contenter de réutiliser une implémentation d'une interface via une syntaxe spécifique, par exemple :
interface A {
public default void method() {
System.out.println("A::method");
}
}
interface B {
public default void method() {
System.out.println("B::method");
}
}
class C implements A, B {
public void method() {
A.super.method();
}
}
Par contre les règles sont plus souples, car si la méthode est implémenter dans une classe parente, cela prendra toujours la priorité sur les méthodes par défaut, qui comme leur nom l'indique, ne seront utilisé QUE dans le cas où la classe ne possèdent pas d'implémentation.
Là où cela est très intéressant, c'est que la résolution de la méthode est géré par la JVM au runtime. C'est à dire que cela permet d'ajouter une méthode à une interface sans avoir à recompiler toutes les classes qui l'implémentent...
Envoyé par Gugelhupf
Ça parait logique de ce point de vue, mais alors pourquoi la compilation est autorisée en C++ ? ...
Les règles d'héritage du C++ et de Java sont très différente.
En Java toutes les méthodes sont implicitement virtuelles et donc hérités par défaut, alors que c'est exactement l'inverse en C++.
Du coup les problèmes que l'on peut rencontrer via l'héritage multiple sont moins visible...
public interface Demo {
public void method1();
public default void method2() {
System.out.println("method2");
}
}
Les classes qui implémentent cette interface pourront se contenter d'implémenter la méthode method1().
La méthode method2() est optionnelle et utilisera le code par défaut si elle n'est pas implémenté par la classe.
Mais ce n'est pas tout, on pourra utiliser des méthodes private, destiné à être utiliser dans les "default methods" afin de pouvoir mutualiser le code.
Par exemple :
public interface Demo {
public static void method() {
/* ... */
}
}
Attention toutefois car à l'heure actuelle ceci n'est implémenté que dans le compilateur. En clair cela va compiler normalement, mais cela risque de générer des erreurs à l'exécution...
1. J'aurais aimé savoir s'il y avait une différence entre le fait de préciser le type d'un argument ou non :
Il n'y a aucune différence. S'il n'est pas précisé le type est déduite selon le contexte par le compilateur...
Envoyé par Gugelhupf
2. Aussi, si on utilise un lambda dans une classe, pourra-t-on automatiquement avoir accès au this dans le lambda (sans avoir à le faire passer par un paramètre) ?
// On retourne x + y
IntBinatyOperator op1 = (x, y) -> x + y;
// On retourne x + y + z (z variable extérieur au bloc lambda)
int z = 10;
IntBinatyOperator op1 = (x, y) -> x + y + z;
La seule restriction c'est que la variable doit être implicitement final.
C'est à dire que la variable doit être définie et ne doit pas être modifié dans la lambda ou en dehors...
Une interface fonctionnelle c'est juste une interface qui ne contient qu'une seule et unique méthode abstraite (sans compter les "default's methods" donc), comme par exemple Runnable.
Les lambdas et les références de méthode ne peuvent être convertis qu'en une interface fonctionnelle, à condition que leurs signatures correspondent.
Par exemple dans ce cas là la méthode Iterable::forEach() est définie comme ceci :
public default void forEach(Block<? super T> block) {
for (T t : this) {
block.accept(t);
}
}
Et Block fait partie du package java.util.function, qui inclut un ensemble d'interface fonctionnelle basique...
Grosso-modo elle est définie comme ceci :
public interface Block<T> {
public void accept(T t);
}
Et c'est la signature de cette méthode qu'il faudra donc respecter avec la lambda (ou la référence de méthode).
Si tu références une méthode static, alors la signature de cette méthode devra correspondre à la signature de l'interface. C'est le cas dans ton exemple donc cela marchera bien
Après tu as encore le cas particulier des méthodes d'instances, où tu as deux possibilités :
Si tu références une méthode d'instance via son type, et la signature est alors modifié en rajoutant le type en tant que premier paramètre, ce qui permettra de passer l'instance lors de l'appel.
Si tu la référence via une instance d'un objet, la signature n'est pas modifié et l'appel se fera sur l'instance indiqué.
Exemple Object::toString pourra être associer avec une interface fonctionnelle tel quel celle-ci :
Aussi, je suis je suis un peu perdu quand tu parles de correspondance entre signature de méthode et signature d'interface, ou bien tu veux parler de correspondance entre signature de méthode et signature de la méthode de l'interface fonctionnelle.
Oui je voulais bien parler de la signature de la méthode de l'interface fonctionnelle.
Une lambda (ou une référence de méthode) ne peut être associé qu'à une interface fonctionnelle, et il faut que la signature correspondent à celle de la méthode :
L'expression lambda a la signature suivante : "void(ActionEvent)", ce qui correspond à la signature de la méthode de l'interface fonctionnelle ActionListener qui défini la méthode suivante :
MaClasse cls = new MaClasse();
ActionListener listener = cls::doSomething; // OK
Dans ce cas là l'instance ("cls" dans l'exemple) sera utilisée pour l'appel de la méthode, qui conserve du coup sa signature originale...
Et pour les méthodes c'est la même chose. Les méthodes qui utiliseront les lambdas n'ont rien de spécial, si ce n'est qu'elles utilisent une interface fonctionnelle. Mais même si cette notion est "nouvelle", cela ne l'est pas vraiment car l'API standard est déjà pleine d'interface fonctionnelle...
Du coup on pourra utiliser les lambdas/références de méthode avec des APIs existante, du moment qu'elles utilisent une interface fonctionnelle :
// Création d'un ThreadFactory via une classe anonyme :
Executors.newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r);
}
});
// Création d'un ThreadFactory via une référence de constructeur :
Executors.newCachedThreadPool(Thread::new);
// Ceci utilisera le constructeur Thread(Runnable),
// qui correspond à la signature de la méthode de ThreadFactory
Autre petite subtilité que j'ai découverte aujourd'hui : de nombreuses interfaces fonctionnelles (déjà existante ou non) sont enrichies de "default's method" permettant diverses choses.
Par exemple pour trier une liste d'User par nom, on pourra utiliser le code suivant :
Ou alors on peut enchainer plusieurs comparaisons qui seront traiter dans l'ordre.
Par exemple pour tirer par nom, prénom puis date de naissance on pourra faire ceci :
De plus j'ai une autre question sur la parallélisation dont tu as mentionné sur les multiples instruction dont on applique à une collection,
Juste une remarque : les opérations s'appliquent sur le flux (stream), mais n'affecte pas la collection en elle-même
Envoyé par la.lune
alors quand tu dis 'si cela est possible', je me pose la question possible dans le sens où certaines instructions dépendent des précédentes ou dans le sens de la capacité de l'ordinateur de paralléliser, et là aussi il y a deux option soit de la parallélisation juste en différents thread qui serait optimisée avec les architectures multi-core ou bien de la vrai parallélisation.
C'est juste que cela dépend des instructions que tu utilises, et de ce que tu veux faire.
Par exemple si tu veux stocker le résultat dans une collection, il faut que cette dernière soit thread-safe, car si ce n'est pas le cas cela engendrera des erreurs. Du coup même si tu choisis un stream parallel ce type d'opération sera en séquentiel par défaut (en fait c'est l'implémentation de la collection qui peut modifier cela).
Envoyé par la.lune
Ma dernière question c'est que tu parles d'interfaces de types qui ne sont pas encore finalisés dans une de tes réponses, alors peux-tu nous donner plus de détailles sur l'objet de ces interfaces et leur domaines d'applications.
En fait c'est juste un nouveau package java.util.function qui les interfaces fonctionnelles les plus courante.
Quelques exemples :
Block<T> qui permet d'exécuter un bout de code en prenant un objet en paramètre (c'est ce qui est utiliser par forEach() par exemple) :
On y retrouve aussi des versions optimisés pour les types primitifs, ou des versions "Bi" prenant deux paramètres (et utilisé par les Map pour les clef/valeur).
Il n'y a rien d'exceptionnel là dedans, c'est juste pour fournir une base commune qui sera utilisable par tout le monde plutôt que de réécrire tout plein d'interface similaire dans chaque API
Je n'ai pas vraiment eu le temps de le parcourir, mais l'interface Map s'est enrichie de plusieurs méthodes par défaut, en plus du forEach() dont j'avais déjà parlé !
Rien d'extraordinaire en soit, mais quand même des méthodes bien utile :
/*
* Supprime un couple clef/valeur,
* seulement si le couple existe.
*/
map.remove("KEY", "VALUE");
/*
* Remplace la valeur associé à la clef,
* seulement si cette clef existe déjà.
*/
map.replace("KEY", "NEWVALUE");
/*
* Remplace la valeur d'un couple clef/valeur
* seulement si le couple existe.
*/
map.replace("KEY", "VALUE", "NEWVALUE");
/*
* Remplace toutes les valeurs de la Map,
* par le résultat de l'expression.
* Exemple : mettre toutes les valeurs en Majuscule :
*/
map.replaceAll((k,v) -> v.toUpperCase());
/*
* Ajoute un couple clef/valeur,
* seulement si la clef n'est pas déjà présente.
*/
map.putIfAbsent("KEY", "VALUE");
/*
* Permet de récupérer ET de modifier
* la valeur d'une clef dans la Map.
*/
String value = map.compute("KEY", (k,v) -> v.toUpperCase());
/*
* Permet de modifier la valeur d'une clef,
* seulement si le couple clef/valeur existe.
*/
String value = map.computeIfPresent("KEY", (k,v)->"new value");
/*
* Permet de définir la valeur d'une clef,
* lorsque le couple clef/valeur est absent.
* Sinon on récupère la valeur existante.
*/
String value = map.computeIfAbsent("KEY", (k)->"empty");
A noter que ce "computeIfAbsent()" nous permettra d'implémenter une MultiMap en un rien de temps :
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité,
merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.