Gestion des cartes dans Groovy vs Java


  • FrançaisFrançais


  • Java est un excellent langage de programmation, mais parfois je veux un langage de type Java qui soit juste un peu plus flexible et compact. C’est alors que j’opte pour Sensationnel.

    Dans un article récent, j’ai passé en revue certaines des différences entre créer et initialiser des cartes dans Groovy et faire la même chose en Java. En bref, Groovy a une syntaxe concise pour configurer des cartes et accéder aux entrées de carte par rapport à l’effort nécessaire en Java.

    Cet article approfondira davantage les différences de gestion des cartes entre Groovy et Java. Pour cela, j’utiliserai le exemple de tableau des employés utilisé pour la démonstration de la bibliothèque JavaScript DataTables. Pour continuer, commencez par vous assurer que les versions récentes de Groovy et Java sont installées sur votre ordinateur.

    Installer Java et Groovy

    Groovy est basé sur Java et nécessite également une installation Java. Une version récente et/ou décente de Java et de Groovy se trouve peut-être déjà dans les dépôts de votre distribution Linux, ou vous pouvez télécharger et installer Groovy à partir du Site Web d’Apache Groovy. Une bonne option pour les utilisateurs de Linux est SDKMan, qui peut être utilisé pour obtenir plusieurs versions de Java, Groovy et de nombreux autres outils connexes. Pour cet article, j’utilise les versions du SDK de :

    • Java : version 11.0.12-open d’OpenJDK 11
    • Groovy : version 3.0.8.

    Retour au problème : les cartes

    Premièrement, d’après mon expérience, les cartes et les listes (ou du moins les tableaux) se retrouvent souvent dans le même programme. Par exemple, le traitement d’un fichier d’entrée est très similaire au passage d’une liste ; souvent, je le fais lorsque je veux catégoriser les données rencontrées dans le fichier d’entrée (ou la liste), en stockant une sorte de valeur dans des tables de recherche, qui ne sont que des cartes.

    Deuxièmement, Java 8 a introduit toute la fonctionnalité Streams et les lambdas (ou fonctions anonymes). D’après mon expérience, la conversion de données d’entrée (ou de listes) en cartes implique souvent l’utilisation de Java Streams. De plus, Java Streams est le plus flexible lorsqu’il s’agit de flux d’objets typés, offrant des fonctionnalités de regroupement et d’accumulation prêtes à l’emploi.

    Traitement de la liste des employés en Java

    Voici un exemple concret basé sur ces dossiers d’employés fictifs. Vous trouverez ci-dessous un programme Java qui définit une classe Employee pour contenir les informations sur les employés, crée une liste d’instances Employee et traite cette liste de différentes manières :

         1  import java.lang.*;
         2  import java.util.Arrays;
           
         3  import java.util.Locale;
         4  import java.time.format.DateTimeFormatter;
         5  import java.time.LocalDate;
         6  import java.time.format.DateTimeParseException;
         7  import java.text.NumberFormat;
         8  import java.text.ParseException;
           
         9  import java.util.stream.Collectors;
           
        10  public class Test31 {
           
        11      static public void main(String args[]) {
           
        12          var employeeList = Arrays.asList(
        13              new Employee("Tiger Nixon", "System Architect",
        14                  "Edinburgh", "5421", "2011/04/25", "$320,800"),
        15              new Employee("Garrett Winters", "Accountant",
        16                  "Tokyo", "8422", "2011/07/25", "$170,750"),
                                                            ...
       
        81              new Employee("Martena Mccray", "Post-Sales support",
        82                  "Edinburgh", "8240", "2011/03/09", "$324,050"),
        83              new Employee("Unity Butler", "Marketing Designer",
        84                  "San Francisco", "5384", "2009/12/09", "$85,675")
        85          );
           
        86          // calculate the average salary across the entire company
           
        87          var companyAvgSal = employeeList.
        88              stream().
        89              collect(Collectors.averagingDouble(Employee::getSalary));
        90          System.out.println("company avg salary = " + companyAvgSal);
           
        91          // calculate the average salary for each location,
        92          //     compare to the company average
           
        93          var locationAvgSal = employeeList.
        94              stream().
        95              collect(Collectors.groupingBy((Employee e) ->
        96                  e.getLocation(),
        97                      Collectors.averagingDouble(Employee::getSalary)));
        98          locationAvgSal.forEach((k,v) ->
        99              System.out.println(k + " avg salary = " + v +
       100                  "; diff from avg company salary = " +
       101                  (v - companyAvgSal)));
           
       102          // show the employees in Edinburgh approach #1
           
       103          System.out.print("employee(s) in Edinburgh (approach #1):");
       104          var employeesInEdinburgh = employeeList.
       105              stream().
       106              filter(e -> e.getLocation().equals("Edinburgh")).
       107              collect(Collectors.toList());
       108          employeesInEdinburgh.
       109              forEach(e ->
       110                  System.out.print(" " + e.getSurname() + "," +
       111                      e.getGivenName()));
       112          System.out.println();
           
           
       113          // group employees by location
           
       114          var employeesByLocation = employeeList.
       115              stream().
       116              collect(Collectors.groupingBy(Employee::getLocation));
           
       117          // show the employees in Edinburgh approach #2
           
       118          System.out.print("employee(s) in Edinburgh (approach #2):");
       119          employeesByLocation.get("Edinburgh").
       120              forEach(e ->
       121                  System.out.print(" " + e.getSurname() + "," +
       122                      e.getGivenName()));
       123          System.out.println();
           
       124      }
       125  }
           
       126  class Employee {
       127      private String surname;
       128      private String givenName;
       129      private String role;
       130      private String location;
       131      private int extension;
       132      private LocalDate hired;
       133      private double salary;
           
       134      public Employee(String fullName, String role, String location,
       135          String extension, String hired, String salary) {
       136          var nn = fullName.split(" ");
       137          if (nn.length > 1) {
       138              this.surname = nn[1];
       139              this.givenName = nn[0];
       140          } else {
       141              this.surname = nn[0];
       142              this.givenName = "";
       143          }
       144          this.role = role;
       145          this.location = location;
       146          try {
       147              this.extension = Integer.parseInt(extension);
       148          } catch (NumberFormatException nfe) {
       149              this.extension = 0;
       150          }
       151          try {
       152              this.hired = LocalDate.parse(hired,
       153                  DateTimeFormatter.ofPattern("yyyy/MM/dd"));
       154          } catch (DateTimeParseException dtpe) {
       155              this.hired = LocalDate.EPOCH;
       156          }
       157          try {
       158              this.salary = NumberFormat.getCurrencyInstance(Locale.US).
       159                  parse(salary).doubleValue();
       160          } catch (ParseException pe) {
       161              this.salary = 0d;
       162          }
       163      }
           
       164      public String getSurname() { return this.surname; }
       165      public String getGivenName() { return this.givenName; }
       166      public String getLocation() { return this.location; }
       167      public int getExtension() { return this.extension; }
       168      public LocalDate getHired() { return this.hired; }
       169      public double getSalary() { return this.salary; }
       170  }

    Programmation et développement

    Wow, c’est beaucoup de code pour un simple programme de démonstration ! Je vais d’abord le parcourir en morceaux.

    À partir de la fin, les lignes 126 à 170 définissent le Employee classe utilisée pour stocker les données des employés. La chose la plus importante à mentionner ici est que les champs de la fiche de l’employé sont de types différents, et en Java cela conduit généralement à définir ce type de classe. Vous pouvez rendre ce code un peu plus compact en utilisant @Data du projet Lombok annotation pour générer automatiquement les getters (et setters) pour le Employee classer. Dans les versions plus récentes de Java, je peux déclarer ce genre de choses comme un enregistrement plutôt qu’une classe, puisque le but est de stocker des données. Stockage des données sous forme de liste de Employee instances facilite l’utilisation des flux Java.

    Les lignes 12 à 85 créent la liste des Employee instances, alors maintenant vous avez déjà traité 119 des 170 lignes.

    Il y a neuf lignes d’instructions d’importation au début. Fait intéressant, il n’y a pas d’importations liées à la carte ! C’est en partie parce que j’utilise des méthodes de flux qui produisent des cartes comme résultats, et en partie parce que j’utilise le var mot clé pour déclarer des variables, de sorte que le type est déduit par le compilateur.

    Les parties intéressantes du code ci-dessus se produisent dans les lignes 86 à 123.

    Aux lignes 87-90, je convertis employeeList dans un flux (ligne 88) puis utilisez collect() pour appliquer le Collectors.averagingDouble() méthode à la Employee::getSalary (ligne 89) pour calculer le salaire moyen dans l’ensemble de l’entreprise. Il s’agit d’un pur traitement de liste fonctionnelle ; aucune carte n’est impliquée.

    Aux lignes 93-101, je convertis employeeList dans un ruisseau à nouveau. J’utilise alors le Collectors.groupingBy() méthode pour créer une carte dont les clés sont les emplacements des employés, renvoyés par e.getLocation()et dont les valeurs sont le salaire moyen pour chaque emplacement, renvoyé par Collectors.averagingDouble() encore appliqué à la Employee::getSalary méthode appliquée à chaque employé du sous-ensemble de sites, plutôt qu’à l’ensemble de l’entreprise. C’est le groupingBy() La méthode crée des sous-ensembles par emplacement, qui sont ensuite moyennés. Utilisation des lignes 98 à 101 forEach() pour parcourir l’emplacement d’impression des entrées de la carte, le salaire moyen et la différence entre les moyennes de l’emplacement et la moyenne de l’entreprise.

    Supposons maintenant que vous vouliez examiner uniquement les employés situés à Édimbourg. Une façon d’accomplir cela est montrée dans les lignes 103-112, où j’utilise le flux filter() méthode pour créer une liste des seuls employés basés à Édimbourg et le forEach() méthode pour imprimer leurs noms. Pas de cartes ici non plus.

    Une autre façon de résoudre ce problème est illustrée aux lignes 113-123. Dans cette méthode, je crée une carte où chaque entrée contient une liste d’employés par emplacement. Tout d’abord, aux lignes 113-116, j’utilise le groupingBy() méthode pour produire la carte que je veux avec des clés d’emplacements d’employés dont les valeurs sont des sous-listes d’employés à cet emplacement. Ensuite, aux lignes 117-123, j’utilise le forEach() méthode pour imprimer la sous-liste des noms des employés du site d’Édimbourg.

    Lorsque nous compilons et exécutons ce qui précède, la sortie est :

    company avg salary = 292082.5
    San Francisco avg salary = 284703.125; diff from avg company salary = -7379.375
    New York avg salary = 410158.3333333333; diff from avg company salary = 118075.83333333331
    Singapore avg salary = 357650.0; diff from avg company salary = 65567.5
    Tokyo avg salary = 206087.5; diff from avg company salary = -85995.0
    London avg salary = 322476.25; diff from avg company salary = 30393.75
    Edinburgh avg salary = 261940.7142857143; diff from avg company salary = -30141.78571428571
    Sydney avg salary = 90500.0; diff from avg company salary = -201582.5
    employee(s) in Edinburgh (approach #1): Nixon,Tiger Kelly,Cedric Frost,Sonya Flynn,Quinn Rios,Dai Joyce,Gavin Mccray,Martena
    employee(s) in Edinburgh (approach #2): Nixon,Tiger Kelly,Cedric Frost,Sonya Flynn,Quinn Rios,Dai Joyce,Gavin Mccray,Martena

    Traitement de la liste des employés dans Groovy

    Groovy a toujours fourni des fonctionnalités améliorées pour le traitement des listes et des cartes, en partie en étendant la bibliothèque Java Collections et en partie en fournissant des fermetures, qui ressemblent un peu aux lambdas.

    L’un des résultats est que les cartes dans Groovy peuvent facilement être utilisées avec différents types de valeurs. Par conséquent, vous ne pouvez pas être poussé à créer la classe Employé auxiliaire ; à la place, vous pouvez simplement utiliser une carte. Examinons une version Groovy de la même fonctionnalité :

         1  import java.util.Locale
         2  import java.time.format.DateTimeFormatter
         3  import java.time.LocalDate
         4  import java.time.format.DateTimeParseException
         5  import java.text.NumberFormat
         6  import java.text.ParseException
           
         7  def employeeList = [
         8      ["Tiger Nixon", "System Architect", "Edinburgh",
         9          "5421", "2011/04/25", "\$320,800"],
        10      ["Garrett Winters", "Accountant", "Tokyo",
        11          "8422", "2011/07/25", "\$170,750"],

                               ...

        76      ["Martena Mccray", "Post-Sales support", "Edinburgh",
        77          "8240", "2011/03/09", "\$324,050"],
        78      ["Unity Butler", "Marketing Designer", "San Francisco",
        79          "5384", "2009/12/09", "\$85,675"]
        80  ].collect { ef ->
        81      def surname, givenName, role, location, extension, hired, salary
        82      def nn = ef[0].split(" ")
        83      if (nn.length > 1) {
        84          surname = nn[1]
        85          givenName = nn[0]
        86      } else {
        87          surname = nn[0]
        88          givenName = ""
        89      }
        90      role = ef[1]
        91      location = ef[2]
        92      try {
        93          extension = Integer.parseInt(ef[3]);
        94      } catch (NumberFormatException nfe) {
        95          extension = 0;
        96      }
        97      try {
        98          hired = LocalDate.parse(ef[4],
        99              DateTimeFormatter.ofPattern("yyyy/MM/dd"));
       100      } catch (DateTimeParseException dtpe) {
       101          hired = LocalDate.EPOCH;
       102      }
       103      try {
       104          salary = NumberFormat.getCurrencyInstance(Locale.US).
       105              parse(ef[5]).doubleValue();
       106      } catch (ParseException pe) {
       107          salary = 0d;
       108      }
       109      [surname: surname, givenName: givenName, role: role,
       110          location: location, extension: extension, hired: hired, salary: salary]
       111  }
           
       112  // calculate the average salary across the entire company
           
       113  def companyAvgSal = employeeList.average { e -> e.salary }
       114  println "company avg salary = " + companyAvgSal
           
       115  // calculate the average salary for each location,
       116  //     compare to the company average
           
       117  def locationAvgSal = employeeList.groupBy { e ->
       118      e.location
       119  }.collectEntries { l, el ->
       120      [l, el.average { e -> e.salary }]
       121  }
       122  locationAvgSal.each { l, a ->
       123      println l + " avg salary = " + a +
       124          "; diff from avg company salary = " + (a - companyAvgSal)
       125  }
           
       126  // show the employees in Edinburgh approach #1
           
       127  print "employee(s) in Edinburgh (approach #1):"
       128  def employeesInEdinburgh = employeeList.findAll { e ->
       129      e.location == "Edinburgh"
       130  }
       131  employeesInEdinburgh.each { e ->
       132      print " " + e.surname + "," + e.givenName
       133  }
       134  println()
           
       135  // group employees by location
           
       136  def employeesByLocation = employeeList.groupBy { e ->
       137      e.location
       138  }
           
       139  // show the employees in Edinburgh approach #2
           
       140  print "employee(s) in Edinburgh (approach #1):"
       141  employeesByLocation["Edinburgh"].each { e ->
       142      print " " + e.surname + "," + e.givenName
       143  }
       144  println()

    Parce que j’écris juste un script ici, je n’ai pas besoin de mettre le corps du programme dans une méthode à l’intérieur d’une classe ; Groovy s’en charge pour nous.

    Aux lignes 1 à 6, je dois encore importer les classes nécessaires à l’analyse des données. Groovy importe pas mal de choses utiles par défaut, y compris java.lang.* et java.util.*.

    Aux lignes 7 à 90, j’utilise le support syntaxique de Groovy pour les listes en tant que valeurs séparées par des virgules entre parenthèses [ and ]. Dans ce cas, il y a une liste de listes ; chaque sous-liste contient les données des employés. Notez que vous avez besoin du \ en face de la $ dans le domaine du salaire. C’est parce qu’un $ apparaissant à l’intérieur d’une chaîne entourée de guillemets doubles indique la présence d’un champ dont la valeur doit être interpolée dans la chaîne. Une alternative serait d’utiliser des guillemets simples.

    Mais je ne veux pas travailler avec une liste de listes ; Je préférerais avoir une liste de cartes analogue à la liste des instances de la classe Employee dans la version Java. J’utilise la collection Groovy .collect() aux lignes 90 à 111 pour décomposer chaque sous-liste de données d’employés et la convertir en une carte. La méthode collect prend un argument Groovy Closure, et la syntaxe pour créer une fermeture entoure le code avec { et } et répertorie les paramètres comme a, b, c -> d’une manière similaire aux lambdas de Java. La plupart du code ressemble assez à la méthode du constructeur dans la classe Java Employee, sauf qu’il y a des éléments dans la sous-liste plutôt que des arguments du constructeur. Cependant, les deux dernières lignes—

    [surname: surname, givenName: givenName, role: role,

        location: location, extension: extension, hired: hired, salary: salary]

    — créer une carte avec des clés surname, givenName, role, location, extension, hiredet salary. Et, puisqu’il s’agit de la dernière ligne de la fermeture, la valeur renvoyée à l’appelant est cette carte. Pas besoin d’instruction de retour. Inutile de citer ces valeurs clés ; Groovy suppose qu’il s’agit de chaînes. En fait, s’il s’agissait de variables, il faudrait les mettre entre parenthèses pour indiquer la nécessité de les évaluer. La valeur attribuée à chaque touche apparaît sur son côté droit. Notez qu’il s’agit d’une carte dont les valeurs sont de différents types : les quatre premières sont Stringalors int, LocalDateet double. Il aurait été possible de définir les sous-listes avec des éléments de ces différents types, mais j’ai choisi d’adopter cette approche car les données seraient souvent lues sous forme de valeurs de chaîne à partir d’un fichier texte.

    Les bits intéressants apparaissent dans les lignes 112-144. J’ai conservé le même type d’étapes de traitement que dans la version Java.

    Aux lignes 112-114, j’utilise la collection Groovy average() méthode, qui aime collect() prend un argument de fermeture, itérant ici sur la liste des cartes d’employés et en choisissant le salary évaluer. Notez que l’utilisation de ces méthodes sur la classe Collection signifie que vous n’avez pas besoin d’apprendre à transformer des listes, des cartes ou tout autre élément en flux, puis d’apprendre les méthodes de flux pour gérer vos calculs, comme en Java. Pour ceux qui aiment Java Streams, ils sont disponibles dans les nouvelles versions de Groovy.

    Aux lignes 115-125, je calcule le salaire moyen par lieu. D’abord, aux lignes 117-119, je transforme employeeListqui est une liste de cartes, dans une carte, à l’aide de la collection groupBy() méthode, dont les clés sont les valeurs d’emplacement et dont les valeurs sont des sous-listes liées des cartes d’employés appartenant à cet emplacement. Ensuite, je traite ces entrées de carte avec le collectEntries() méthode, en utilisant la average() méthode pour calculer le salaire moyen pour chaque emplacement.

    Notez que collectEntries() passe chaque clé (emplacement) et valeur (sous-liste d’employés à cet emplacement) dans la fermeture (le l, el -> string) et s’attend à ce qu’une liste à deux éléments de clé (emplacement) et de valeur (salaire moyen à cet emplacement) soit renvoyée, en les convertissant en entrées de carte. Une fois que j’ai la carte des salaires moyens par lieu, locationAvgSalje peux l’imprimer en utilisant la Collection each() méthode, qui prend également une fermeture. Lorsque each() est appliqué à une carte, il passe en clé (localisation) et en valeur (salaire moyen) de la même manière que collectEntries().

    Aux lignes 126-134, je filtre les employeeList pour obtenir une sous-liste de employeesInEdinburghen utilisant le findAll() méthode, qui est analogue à Java Streams filter() méthode. Et encore une fois, j’utilise le each() méthode pour imprimer la sous-liste des employés à Édimbourg.

    Aux lignes 135 à 144, j’adopte l’approche alternative consistant à regrouper les employeeList dans une carte des sous-listes d’employés à chaque emplacement, employeesByLocation. Ensuite, aux lignes 139 à 144, je sélectionne la sous-liste des employés à Édimbourg, en utilisant l’expression employeesByLocation[“Edinburgh”] et le each() méthode pour imprimer la sous-liste des noms d’employés à cet emplacement.

    Pourquoi je préfère souvent Groovy

    C’est peut-être juste ma familiarité avec Groovy, accumulée au cours des 12 dernières années environ, mais je me sens plus à l’aise avec l’approche Groovy pour améliorer Collection avec toutes ces méthodes qui prennent une fermeture comme argument, plutôt que l’approche Java de convertir le list, map ou tout ce qui est à portée de main à un flux, puis en utilisant des flux, des lambdas et des classes de données pour gérer les étapes de traitement. Il me semble que je passe beaucoup plus de temps avec les équivalents Java avant que quelque chose ne fonctionne.

    Je suis également un grand fan du typage statique fort et des types paramétrés, tels que Map,employee> ,employee>comme trouvé en Java. Cependant, au jour le jour, je trouve que l’approche plus détendue des listes et des cartes acceptant différents types me soutient mieux dans le monde réel des données sans nécessiter beaucoup de code supplémentaire. La saisie dynamique peut certainement revenir mordre le programmeur. Pourtant, même en sachant que je peux activer la vérification de type statique dans Groovy, je parie que je ne l’ai pas fait plus d’une poignée de fois. Peut-être que mon appréciation pour Groovy vient de mon travail, qui consiste généralement à mettre en forme un tas de données, puis à les analyser; Je ne suis certainement pas votre développeur moyen. Alors Groovy est-il vraiment un Java plus Pythonique ? Nourriture pour la pensée.

    J’aimerais voir dans Java et Groovy quelques installations supplémentaires comme average()et averagingDouble(). Des versions à deux arguments pour produire des moyennes pondérées et des méthodes statistiques au-delà de la moyenne, comme la médiane, l’écart type, etc., seraient également utiles. Tabnine offre des suggestions intéressantes sur la mise en œuvre de certaines d’entre elles.

    Ressources géniales

    La Site Apache Groovy a beaucoup de grande documentation. D’autres bonnes sources incluent la page de référence pour Améliorations groovy de la classe Java Collectionl’introduction plus didactique de travailler avec des collectionset M. Haki. La Site de Baeldung fournit de nombreux tutoriels utiles en Java et Groovy. Et une très bonne raison d’apprendre Groovy est d’apprendre Graalsun framework Web complet merveilleusement productif construit sur d’excellents composants comme Hibernate, Spring Boot et Micronaut.

    Source

    La Rédaction

    L'équipe rédactionnnelle du site

    Pour contacter personnellement le taulier :

    Laisser un commentaire

    Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

    Copy code