Thématiques principales

Affichage des articles dont le libellé est NIO 2. Afficher tous les articles
Affichage des articles dont le libellé est NIO 2. Afficher tous les articles

jeudi 2 novembre 2017

Evolution Java 8

Nous voila arrivé a la dernière version officielle de Java... Enfin officiel, en fait non car Java 9 vient de sortir mais avant qu'elle ne devienne la référence, Java 8 a encore quelques mois devant elle.
Voyons ses évolutions principales et moins principales.

L’implémentation par défaut des interfaces, les defender methods

Cette feature n'est pas forcement celle que l'on doit utiliser, a priori, celle ci est surtout l'objet d'un besoin de compatibilité des API avec d ancienne version de celle-ci en fournissant des comportements sans avoir a les implémentés.

1
2
3
4
5
public interface InterfaceAMoi {
    default void test(){
        System.out.println("a implementer");
    };
}

Bon ils est vrai que sur le fond c'est probablement l'une des grosses surprises de Java 8, car une interface ne définie qu'une interface de réalisation, voici que celle ci va fournir une comportement par défaut. Alors bien sur une interface n'est pas une classe et ne peut donc que manipuler par défaut que le comportement mais on peut se demander si nous ne risquons pas de retomber dans les problèmes du c++ et des héritages multiples que permettait d’éviter les anciennes interfaces. Il va falloir rester très prudent avec cette nouvelle feature de Java. L'exemple de ce probleme est tres bien expliqué dans cet article.

En parlant d'interface, on peut noter que l'on peut maintenant y définir des méthodes statique. Cela est plutôt mineure comme avancé mais cela va permettre de ne plus avoir a construire des classes a constructeur privé juste pour définir des bibliothèques de fonctions utilitaires.

Lambda expressions

Les lambda expressions sont la grosse evolution de Java 8. Issues des approches fonctionnelles, elles permettent la définition a la volée d'une fonction directement utilisable. Son intérêt et globalement la simplification du code qui jusque la devait être écrit sous la forme de classe interne anonyme comme on pouvait le faire avec Swing.

Donc avant on écrivait ceci:

1
2
3
4
5
6
7
public static void main(String[] args) {
  Runnable c=new Runnable(){
    @Override
    public void run() {
      System.out.println("J'implemente ma classe annonyme");
    }};
}

Désormais on écrira ceci:

1
2
3
4
5
public static void main(String[] args) {
  Runnable c=()->{
     System.out.println("J'implemente ma lambda expression");
  };
}

Bien sur il existe des contraintes. La première est qu'une lambda expression se construit sur la base d'une interface (pas d'une classe abstraite) ne comportant qu'une seule méthode. Voila pourquoi on peut utiliser les lambda expression a partir de l'interface Runnable.

Donc si on définit une interface :

1
2
3
public interface Toto {
    public void toto();
}

et on l'utilise comme suit

1
2
3
Toto c=()->{
      System.out.println("J'implemente ma lambda expression");
 };

A noter que l'utilisation des classes anonymes n'a jamais été une bonne pratique, on peut donc se demander si les lambda expression en sont une puisque finalement elles ne sont qu'un sucre syntaxique permettant de simplifier l’écriture et la lecture du code (pour ceux qui ont l'habitude de les manipuler....)

A mon sens, avec ce que l'on vient seulement de découvrir, les lambda expression ouvrent la possibilité d'une utilisation plus souple et plus dynamique de comportement en facilitant l'emploi de certains pattern de conception comme les patterns décorateurstratégie, médiateur ou encore de type fabrique ou commande. Mais pour comprendre tout cela il faudrait regarder plus profondément  dans package java.util.function.

java.util.function

Ce package contient un grand nombre d'interfaces fonctionnelles mais 3 principales sont a citer car participant de façon de façon générales au sein de tous types d'approches fonctionnelles:

  • les predicats pour tester les functions
  • les consumers pour appliquer des fonctions
  • les opérateurs pour combiner des opérations et des éléments

A noter pour les logiciens que ces concepts modélisent de façon générale des logiques de tous types (mais je reviendrais sur ces points dans des articles dédiés aux logiques)

Les références méthodes

Elles fonctionnent comme des lambda expression mais permettent de simplifier l’implémentation d'une classe anonyme. In fine l'utilisation de ce mécanisme permet d'appeler une méthode de classe ou d'instance de façon groupée en réalisant une lambda expression de façon implicite

au lieu de:

1
2
3
4
5
6
7
List<String> l = new ArrayList<>();
l.sort(new Comparator<String>() {
  @Override
  public int compare(String o1, String o2) {
    return o1.compareTo(o2);
  }
});

on aura:

1
2
List<String> l = new ArrayList<>();
l.sort(String::compareTo);

Ce qui indéniablement simplifie l’écriture lorsque l’implémentation elle même est simple....et commutative le cas échéant....

A noter en digression sur ce chapitre que java 8 offre maintenant la capacité de tri parrallele sur les tableaux:

1
Arrays.parallelSort(myArray);

Dans d'autres cas ca sera plus simple:


1
l.forEach(String::trim);

Stream methode

Les streams permettent le traitement de listes sous la forme d'un flux un peu comme le ferai un iterator mais orienté fonctionnelle. Ainsi naturellement, les lambda expressions vont s'appliquer dessus au travers de divers activités telles que les filtres.

Les streams vont fournir également des capacités sur le processus de traitement en permettant soit de séquentialisé les opérations (par exemple si l'ordre des éléments a une importance) ou d’être parallélisé si l'ordre n'a pas d'importance et que les données peuvent être partitionnées.

Par exemple pour récupérer la sous-liste des chaines démarrant par S dans une liste de chaînes:


1
2
List<String> l = new ArrayList<>();
List<String> filtredl=l.stream().filter((s)-> s.startsWith("S")).collect(Collectors.toList());

L'incorporation de Nashorn

Apres l'ajout d'une API de scripting et d'un interpreteur javascript (Rhino) dans Java 6, il aura fallu attendre Java 8 et l'integration de Nashorn pour beneficier d'un moteur JavaScript du type de NodeJS. L'article de Soat en donne une bonne description.

Time API

Java 8 intègre une refonte complète de l'API gérant le temps car celle ci manquait de cohérence générale. Ainsi avec cette mise a jour, Java 8 propose une API fournissant un ensemble de classes plus rationnelles, thread-safe et immutable.

rationalisant les besoins généraux de la gestion du temps et des dates en les . Je n'entre pas dans le détail car l'article suivant fourni déjà tout les exemples nécessaires a une bonne compréhension et si voulez plus d'info, cet article ci vous conviendra peut être mieux.

Les annotations

Dans la liste des améliorations on notera la possibilité de placer plusieurs fois sur le même éléments une même annotation et également de pouvoir en placer sur les types. Ceci aura pour intérêt, en donnant au compilateur les framework de vérification adéquat, d'ajouter des contraintes sur le code afin d'en garantir une meilleur qualité.

Java IO

On notera également quelques plus sur l'API IO introduite avec Java 7 pour une manipulation des fichiers en cohérence avec l'introduction des streams.

Les verrous  stamped

Après les verrous de lecture ecriture venus avec la Java 7, Java 8 introduit les verrous stamped. Autant si les premiers sont d'une utilisation tres simple, ils soufrent de problème de performances, ainsi, les seconds seront a optimiser afin de satisfaire aux éventuels problème de concurrence. Par ce fait, ils sont par contre plus compliqué a utiliser.

On entrera dans les details de la programmation multi-thread dans un autre article. Cependant je vous invite a lire celui-ci si vous souhaitez avancer sur le sujet.

Les concurrent Adders

Il sont la en remplacement des Atomic afin de fournir une solution de comptage concurrente et non bloquante. Tout comme les verrous en lecture ecriture, les Atomic on été intégrés dans Java 7 pour simplifier l’écriture et l’emploie de bloc synchronised sur de simple compteur (voir article) Seulement, comportant certaines limitations (de contention essentiellement), Java 8 apporte une nouvelle solution les Adders.

Process

 La classe Process se voit améliorée de trois méthodes pour vérifier l’état de vie d'un processus système, pour le détruire ou pour l'attendre. Ces méthodes complètent celles existantes de l'API JNI qui permettait de lancer des processus au sein de l'OS hebergeant la JVM.

SecureRandom

Java 8 soufrait d'un manque de securité dans la génération des nombres aléatoires pour les clefs de chiffrements. Elle offre désormais une API  stable quelque soit l'OS.

Optional

 L'api Optional est une nouvelle évolution de Java 8 dont le but est de pousser le développeur a ne plus utiliser le mots clef null comme d'un type. En effet, dans de nombreuses méthodes, il arrivait qu' il soit nécessaire de finaliser celle ci par un return null qui oblige nécessairement l'utilisateur de la méthode a préalablement vérifier la non nullité des données.

Avec Optional, on dispose d'une API permettant d’éviter de fournir un null voir de fournir une valeur par défaut adapté au type réel retourné de la méthode.

L'article suivant fournit l'ensemble des cas d'utilisation de cette classe et cet article ci décrit la nature de ce qu'est un Optional.

Attention a l'abus, Optional ne sert qu'a éviter de faire un retour de méthode null et ne s'utilise pas comme d'un Mock.... Optional ne sert qu'a contourner la nullité.

Suppression du PermGen

Avant de parler de Java 8 je vous propose de consulter le fonctionnement de la gestion memoire avant java 8 et celui ci sur l'optimisation.

Dans Java8, les développeurs vont voir disparaitre la PermGen ou permanent generation. Cet espace mémoire dédié au chargement des classes. Disparition? non quand même pas en fait, la PermGen a etre renommé en Metaspace qui va trouver sa place dans la mémoire native. En fait, depuis que la JVM est capable de charger et décharger des Classloader personnalisé, on va désormais trouver un Metaspace par Classloader permettant de charger et décharger directement dans la mémoire des classes. A noter que implémentation de Classloader personnalisé peut etre fastidieux et je recommande grandement l'utilisation d'OSGI. Mais ceci est une autre histoire.... enfin article.

Pour plus de détails n’hésitez pas a consulter cet article.

Voila, l'article sur la conséquente version Java 8 est terminer, Elle aura ete longue et pourtant je n'ai pu entrer dans trop de détails.... je tacherai de fournir quelques articles supplémentaires afin de compléter celui-ci.

Edit (06/11/2019)

Une référence utile sur la concaténation de chaînes :
https://redfin.engineering/java-string-concatenation-which-way-is-best-8f590a7d22a8

samedi 14 octobre 2017

Evolution Java 7

Nous voila donc arrivé a la version 7. Cette version, tout comme la version 5 amène ses bouleversements mais dans une optique de simplification. Voyons en quoi.

Globalement je me suis inspirer de l'article de JmDoudou donc si vous voulez des détails, n’hésitez pas a aller le consulter.

Donc dans les grandes lignes, l'idée de cette version de Java est de simplifier et comme souvent, pour simplifier, on change la syntaxe afin de rendre plus lisible le code sauf pour le premier point qui lui consiste a permettre l’écriture des types entiers en binaire. Bon en dehors d'un besoin très spécifique, comme de traiter un protocole très bas niveau... je ne m’entendrais pas sur ce point.

Autre point de réécriture, celui de permettre d'utiliser des anderscores dans les nombres afin de faire des groupes de chiffres et simplifier la lecture.

Exemple:

1
2
3
4
5
int value = 1_146_789;
value = 4_3;
value = 4___3;
value = 0b1001110_10001011_01001101_01000101;
float pi = 3.141_593f;

Comme je l'avais évoqué dans la partie sur les enum  le switch/case n’était pas capable de traiter autre chose que des types primitifs ou des enum. Maintenant ces derniers pourront traiter des String.

Exemple:

1
2
3
4
5
6
7
String s="toto";
switch(s)
{
    case "toto" : toto();break;
    case "titi" : titi();break;
    default: defaut();break;
}


Bon après honnêtement moi les switch/case j'aime pas ça et souvent on peut les éviter par une bonne hiérarchie de classe (et ça va de même avec les if/then/else imbriqué) mais on retraitera de ce genre de sujet.

L’opérateur <>. alors la, clairement, il s'agit de Ze Sucre Syntaxique. En fait il n'apporte que de la lisibilité et c'est franchement pas mal. Je m'explique, le problème est simple, on est partie dans la construction d'une hiérarchie de classe avec un beau pattern composite et op on se dit tiens utilisons des generiques! Dans le principe c'est pas une mauvaise idée, au contraire sauf que souvent on se retrouve a declarer des listes de truc, machin avec des listes de chouettes shumrtz.

Exemple:

1
Map<Integer, Map<String,List<Truc>>> maStructure=new HashMap<Integer, Map<String,List<Truc>>>();

Bon je dis pas j'ai un peu exageré le trait....  quoique.... Enfin et bien l'operateur diamant permet d'ecrire juste:

1
Map<Integer, Map<String,List<Truc>>> maStructure=new HashMap<>();

Et le compilateur fait le reste du travail de résolution des types s'il en est capable et ca marche aussi pour les retours de fonctions:


1
2
3
private static Map<Integer, Map<String,List<Truc>>> titi() {
     return new HashMap<>();
}

Gestion des ressources. Il s'agit de permettre au bloc try de gérer lui-même la fermeture d'une ressource au lieu de devoir le faire via le finally employé jusqu’à la version 6. On aura alors une écriture plus simple, plus lisible et plus fiable avec laquelle on pourra continuer a gérer les erreurs et exceptions éventuelles mais ou la fermeture des flux se fera automatiquement.

Exemple:


1
2
3
4
5
6
7
8
try (BufferedReader bufferedReader = new BufferedReader(new FileReader("monfichier.txt"))) {
    String ligne = null;
    while ((ligne = bufferedReader.readLine()) != null) {
        System.out.println(ligne);
    }
} catch (IOException ioe) {
    ioe.printStackTrace();
}


Enfin pour ceux qui voudront créer leur propre flux compatible avec le bloc try, il faudra implémenter l'interface java.lang.AutoCloseable

La gestion des exceptions a également été revu en java 7, et c'est une bonne chose car cette nouvelle gestion permet une gestion plus concise des anomalies et de leur type leur de reemissions d'exceptions.

En effet, maintenant, un bloc catch peut être défini pour un groupe d'exception possible permettant de factoriser leur traitements et d’éviter la multiplication des blocs:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
try{ //TODO
}catch (Exception1 e1) {
    //TODOX
}
catch (Exception2 e2) {
    //TODOX
}
catch (Exception3 e3) {
    //TODOX
}
finally {
    //TODO
}

devient si les blocs TODOX sont identiques


1
2
3
4
5
6
try{ //TODO
}catch (Exception1 | Exception2 | Exception3 e) {
    //TODOX
}finally {
    //TODO
}


C'est mieux quand même!

Pour la reemissions d'exceptions c'est simple, si vous catcher une exception a l'aide d'un sur-type mais que vous devez la reemettre avec un throw, et bien sur la définition de la méthode (a la suite du mot clefs throws) vous pourrez toujours utiliser la définition de type de l'exception initiale et non de son sur-type, sauf.... si vous imbriquez deux blocs try/catch.... Dans l'absolu ça permet de garder l'information initiale malgré un filtrage "trop globale".

java.nio.Path en remplacement de java.io.File.... bon ca parle de soi-même, c'est pour accéder plus simplement aux chemins comme sait le faire Python.... et oui dans les autres langages on trouve des trucs bien, il faut savoir en tirer parti... et couplé aux classes Files, DirectoryStream, Filter and coe, on obtient toutes les infos pour traiter les fichiers et toutes leurs proprietés. Dommage que ca n'ai pas été fait déjà a la version 6 qui avait déjà tenté une mise a jour.... a oui pour info tout cela s’appelle l'API NIO 2 et en plus elle intègre la possibilité de traité des flux de façon asynchrone.

Sinon encore des updates dans awt/Swing .... je rentre dans le détail?

Ah si! un truc très sympas, l'ajout du package java.lang.invoke qui enrichi la réflexion deja existante avec des capacités de recherche de méthodes en respectant la visibilité. Cela fournira donc des objets, les MethodHandle, sortes de pointeur de fonction sur les méthodes ciblés. Il faudra faire un petit billet dédié a cette API associé a l'API réflexion. Ca sera très intéressant.

L'API de concurrence est peut être l'apport le plus significatif de Java pour cette version. Par contre son utilisation est loin d’être trivial car nécessitant de bien comprendre les conséquences de son utilisation. Ainsi cette API propose via la classe RecursiveTask de diviser le traitement de processus et les repartissant via des Thread dédiés sur les processeurs physiques de la machine. Ceci va permettre d'exploiter au mieux des ressources cependant attention une limitation en terme de synchronisation peu survenir ainsi une division trop élevé d'une tache en sous-tache pourra être contre productive car passant plus de temps a resynchroniser les résultats de Thread qu'a exécuter leur propre tache. A noter que l'approche doit être forcement récursive.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Fibonacci extends RecursiveTask<Integer> {
    public static void main(final String[] args) {
        final ForkJoinPool p = new ForkJoinPool();
        final Fibonacci f = new Fibonacci(1_000_000_000);
        p.execute(f);
    }
    final int n;
    Fibonacci(final int n) {
        this.n = n;
    }
    @Override
    protected final Integer compute() {
        if (this.n <= 1) {
            return this.n;
        }
        final Fibonacci f1 = new Fibonacci(this.n - 1);
        f1.fork();
        final Fibonacci f2 = new Fibonacci(this.n - 2);
        return f2.compute() + f1.join();
    }
}


Voila un petit tour d'horizon de java 7, on approche de la dernière version officiellement sortie, la version 9 mais il nous reste encore la version 8 qui a apporté une nouvelle fois de nouveaux concepts très important.

Informations complementaires sur java 7