[Pour les débutants] Comment utiliser Stream API après Java 8

Comment interagir avec l'API Stream après Java 8

introduction

Aperçu

Cela fait longtemps que l'interface de fonction est devenue disponible en Java. Je pensais que c'était assez récent, mais c'était il y a cinq ans. Avec une telle interface de fonction et l'API Stream, je pense qu'il y a une différence considérable entre ceux qui disent «facile à comprendre» et ceux qui disent «difficile à comprendre». Je vais l'écrire comme référence pour savoir comment gérer l'API Stream dans une telle situation.

supposition

En bref, on a l'impression que les personnes novices dans les interfaces de fonction, les flux et les options optionnelles développent avec Java 8.

Conclusion

Quoi qu'il en soit, le fait est d'arrêter de tout mettre dans la chaîne de méthodes.

Facteurs qui rendent le code écrit dans l'API Stream difficile à comprendre

Il existe différentes manières d'écrire des expressions lambda et des références de méthode.

Il existe plusieurs façons de créer un objet fonctionnel. Définissez un objet fonction qui prend une chaîne et appelle le StringUtils # isEmpty défini ci-dessus pour renvoyer le résultat.

Function<String, Boolean> emptyChecker1 = new Function<>{
  @Override
  public Boolean apply(String s) {
    return StringUtils.isEmpty(s);
  }
}

Function<String, Boolean> emptyChecker2 = s -> StringUtils.isEmpty(s);

Function<String, Boolean> emptyChecker3 = StringUtils::isEmpty;

Il est particulièrement difficile de faire référence à une méthode, et selon que vous écrivez le nom de la classe ou le nom de la variable à gauche de ::, la méthode à appeler changera, et dans certains cas elle sera confuse.

La fonction de l'argument est trop compliquée à comprendre

Je ne peux pas montrer ce code quand j'ai un enfant avec 1 an d'expérience en développement Java (confession).

list.getValues().forEach(value -> {
  if (CollectionUtils.isEmpty(value.getPropertyList())) {
    return;
  }
  if (!CollectionUtils.isEmpty(value.getPropertyList())) {
    Property property = value.getPropertyList().stream()
        .findFirst()
        .orElseThrow(RuntimeException::new);
    service.insert(value, property);
    return;
  }

  switch (value.getType()) {
    case TYPE_1:
      value.getProperty1().setAmount(1000);
      break;
    case TYPE_2:
      value.getProperty2().setAmount(1000);
      break;
    case TYPE_3:
      value.getProperty3().setAmount(1000);
      break;
    }
    service.insert(value);
});

J'essaie de lire le code source en sachant que ** il y a un paragraphe pour chaque bloc de traitement **, mais si l'argument de forEach est si long, je ne peux pas tout lire en même temps, ce qui me rend mal à l'aise. Je vais. Je suis en troisième année, donc je suis sûr que les enfants en première année ...

Il est assez difficile d'écrire le traitement de fin

Si vous voulez juste le convertir en liste, vous pouvez utiliser Collectors.toList () pour vous en débarrasser en un instant, mais je trouve assez difficile d'écrire un processus simple pour convertir une liste en carte. Que faire en cas de duplication? Pour les débutants, les obstacles sont élevés et l'écriture est difficile.

Map<Key, List<Value>> map = values.stream()
      .collect(Collectors.groupingBy(Value::getKey));

Règles autour de l'API Stream (je voulais opérer dans le futur)

Nous avons formulé certaines règles en partant du principe que de nombreux membres inexpérimentés sont inscrits.

Ecrire des arguments d'opération intermédiaires avec des références de méthode

Le but est de garder les arguments des opérations intermédiaires Stream # map et Stream # filter simples et améliorer la lisibilité. Nous pensons que cette règle présente les avantages suivants:

Le troisième est un peu difficile à comprendre, je vais donc l'expliquer à l'aide d'un exemple de programme qui calcule le score moyen en classe de l'examen de mi-session.

À propos, l'examen de mi-session comprend trois matières, le japonais, les mathématiques et l'anglais, et considère la mise en œuvre de ʻExaminationScoreSummary # average`.

public class ExaminationScore {
  private final Integer japaneseScore;
  private final Integer mathScore;
  private final Integer englishScore;

  // constractor, getter
}

public class ExaminationScoreSummary() {
  private final List<ExaminationScore> values;
  // constractor, getter

  public Integer average() {
    // TODO
  }
}

Lors de l'utilisation d'une expression lambda

Il peut être mis en œuvre de n'importe quelle manière. Je l'écris comme si je l'écrivais toujours.

public class ExaminationScoreSummary() {
  private final List<ExaminationScore> values;
  // constractor, getter

  public Integer average() {
    return values.stream()
        .mapToInt(score -> score.getJapaneseScore() + score.getMathScore() + score.getEnglishScore())
        .average();  
  }
}

Non, c'est bien, mais lorsque vous demandez le score total, vous devez ajouter les points à l'appelant à chaque fois.

Lors de l'utilisation de références de méthode

Dans le cas de la référence de méthode, il est impossible pour l'appelant d'ajouter, donc pour le moment, écrivez le processus d'ajout dans la classe ʻExaminationScore`.

public class ExaminationScore {
  private final Integer japaneseScore;
  private final Integer mathScore;
  private final Integer englishScore;

  // constractor, getter
  public Integer getTotalScore() {
    return japaneseScore + mathScore + englishScore;
  }
}

Pour les personnes expérimentées, il peut être naturel d'écrire le calcul entre des champs de la même classe dans la méthode dans laquelle les champs sont définis pour augmenter la cohésion. Mais à mon niveau, c'est difficile. C'est une histoire que si vous réglez l'utilisation des références de méthode, vous apprendrez également l'idée de base de la programmation orientée objet.

L'implémentation de l'appelant ressemble à ceci.

public class ExaminationScoreSummary() {
  private final List<ExaminationScore> values;
  // constractor, getter

  public Integer average() {
    return values.stream()
        .mapToInt(ExaminationScore::getTotalScore)
        .average();  
  }
}

Utilisez java.util.Comparator pour trier avec plusieurs clés

Supposons que vous souhaitiez publier dans l'ordre décroissant des résultats des examens de mi-session. Non, il y a un problème comme l'exposition de la partition ... Puisqu'il y a 3 matières, elles sont triées par ordre décroissant des scores de langue nationale, et si les scores de langue nationale sont les mêmes, ils sont triés par ordre décroissant des scores de mathématiques.

En utilisant Comparator # compare et Comparator # thenComparing, il est possible de mettre en œuvre comme suit.

List<ExaminationScore> values = new ArrayList<>();
values
    .stream()
    .sorted(Comparator.comparing(ExaminationScore::getJapaneseScore).thenComparing(ExaminationScore::getMathScore())
    .collect(Collectors.toList());

Il est pratique de pouvoir trier les choses si facilement. Au fait, si l'ordre de tri devient compliqué, est-ce que j'écrirai tout ici? Non, non, c'est assez difficile. Si vous allez au collège et que vous avez 5 matières ou si vous avez un examen de matières pratiques, ce sera un code très long.

Voici deux façons d'écrire la définition de «dans quel ordre» ailleurs.

1. Demandez aux éléments de la collection d'implémenter l'interface Comparable.

C'est un moyen de définir comment trier le type d'élément de collection. Tout d'abord, définissez comment trier. Il n'y a que deux étapes ci-dessous.

  1. Ajoutez ʻimplements Comparable `à la déclaration de classe d'élément
  2. Implémentez public int compareTo (element class o) dans la classe element

Cette fois, les scores sont triés dans l'ordre du score japonais, du score mathématique et du score anglais, donc je l'ai implémenté comme suit.

  1. Si les scores de langue nationale ne sont pas égaux, considérez le résultat de la comparaison des scores de langue nationale comme le résultat de comparaison «Score d'examen».
  2. Si les scores en langue nationale sont égaux et les scores en mathématiques ne sont pas égaux, le résultat de la comparaison des scores en mathématiques est traité comme le résultat de la comparaison «Score d'examen».
  3. Si les scores du japonais et des mathématiques sont égaux et les scores de l'anglais sont égaux, le résultat de la comparaison des scores de l'anglais est traité comme le résultat de la comparaison du «score de l'examen».

// 1.Dans la déclaration de classe de l'élément`implements Comparable<Classe d'élément>`Ajoutée
public class ExaminationScore implements Comparable<ExaminationScore> {
  private final Integer japaneseScore;
  private final Integer mathScore;
  private final Integer englishScore;

  // constractor, getter

  // 2.Dans la classe d'élément`public int compareTo(Classe d'élément o)`Mis en œuvre
  public int compareTo(ExaminationScore o) {
    if (japaneseScore.compareTo(o.japaneseScore) != 0) {
      return japaneseScore.compareTo(o.japaneseScore);
    }
    if (mathScore.compareTo(o.mathScore) != 0) {
      return mathScore.compareTo(o.mathScore);
    }
    return englishScore.compareTo(o.englishScore);
  }
}

J'oublie ce qu'il faut renvoyer avec la valeur de retour de Comparable # compareTo, donc j'essaye de l'implémenter aussi simple que possible en renvoyant le résultat de la comparaison des variables utilisées comme clé de tri.

Le tri se fait comme suit. Puisqu'il trie par ordre décroissant de score, Comparator # reverseOrder () est appelé.

List<ExaminationScore> values = new ArrayList<>();
values
    .stream()
    .sorted(Comparator.reverseOrder())
    .collect(Collectors.toList());

L'argument Stream # sorted peut être omis en implémentant la méthode Comparable # compareTo comme" -1 si le score est élevé ", mais je l'ai évité car il est difficile à comprendre.

2. Créez une classe distincte qui implémente l'interface Comparator

Contrairement à 1, l'ordre de tri est défini dans une autre classe. La procédure est la suivante:

  1. Créez une classe qui implémente Comparator <element class>
  2. Implémentez public int compare (élément classe o1, élément classe o2)
class ExaminationScoreComparator implements Comparator<ExaminationScore> {
  @Override
  public int compare(ExaminationScore o1, ExaminationScore o2) {
    if (Integer.compare(o1.getJapaneseScore(), o2.getJapaneseScore()) != 0) {
      return Integer.compare(o1.getJapaneseScore(), o2.getJapaneseScore());
    }
    if (Integer.compare(o1.getMathScore(), o2.getMathScore()) != 0) {
      return Integer.compare(o1.getMathScore(), o2.getMathScore());
    }
    return Integer.compare(o1.getEnglishScore(), o2.getEnglishScore());
  }
}

Le tri se fait comme suit. Passez l'instance de Comparator définie ci-dessus dans l'argument de Stream # sorted.

List<ExaminationScore> values = new ArrayList<>();
values
    .stream()
    .sorted(new ExaminationScoreComparator().reverseOrder())
    .collect(Collectors.toList());

Comparable vs Comparator

Après tout, il s'agit de savoir lequel utiliser, mais implémentons-le en utilisant Comparator.

Pour un ordre naturel, la cohérence avec les égaux n'est pas requise, mais fortement recommandée. En effet, l'utilisation d'un ensemble de tri ou d'une mappe de tri qui ne spécifie pas de comparateur explicite avec des éléments ou des clés dont l'ordre naturel est incohérent avec égaux ne garantit pas le comportement de l'ensemble et de la carte. En particulier, de tels ensembles de tri ou cartes de tri enfreignent les règles générales des ensembles ou des cartes. Cette convention est définie en utilisant les termes de la méthode equals.

Depuis Comparable (Java Platform SE 8)

Comme indiqué dans la documentation officielle, s'il y a un conflit entre ʻequals et compareTo, le fonctionnement avec Mapetc. ne sera pas garanti. Si la classe qui implémenteComparable est la clé Map, Map # get utilise le résultat de compareTo au lieu de la clé ʻequals, donc vous aurez un bogue étrange.

La même chose a été écrite dans Quel comparateur ou comparateur Java utiliser.

Le traitement en forme de plaque de chaudière de Stream # collect est coupé ailleurs

Je pense qu'il est courant de prendre un champ spécifique dans un élément de collection pour créer une nouvelle collection, ou d'utiliser un champ spécifique dans une collection comme clé pour créer une carte. Je pense qu'il vaut mieux pouvoir utiliser ce genre de chose sans toucher à l'API Stream un par un.

/**
 *Créez une autre instance à partir de l'élément de liste et renvoyez la liste des instances créées.<br>
 *La logique de création d'instance suit l'objet fonction donné dans le deuxième argument.<br>
 * @liste de paramètres
 * @générateur de paramètres Un objet fonction qui crée une instance d'un autre type à partir d'un élément de la liste
 * @param <S>Le type de l'élément de liste d'origine.
 * @param <R>Nouveau type d'élément de liste.
 * @return Liste des instances générées
 */
public static <S, R> List<Property> collect(List<S> list, Function<S, R> extractor){
  return list.stream()
      .map(extractor)
      .collect(Collectors.toList());
}

Depuis PropertyCollector.java

/**
 *Groupez la liste avec une clé spécifique et renvoyez une carte avec la clé et la liste associées.<br>
 *La logique de génération de clé suit l'objet de fonction donné dans le deuxième argument.<br>
 * @liste de paramètres Liste à grouper
 * @param keyExtractor Un objet fonction qui obtient une clé de liste à partir d'un élément de liste
 * @param <K>Type de clé. Doit être une classe qui implémente l'interface Comparable
 * @param <V>Type d'élément de liste
 * @retourner le résultat du regroupement
 */
public static <K, V> Map<K, List<V>> groupingBy(List<V> list, Function<V, K> keyExtractor) {
  return list.stream().collect(Collectors.groupingBy(keyExtractor));
}

Depuis MapCollector.java

finalement

L'interface de fonction et l'API Stream sont très utiles, mais pour les équipes avec de nombreux membres inexpérimentés, le code source peut devenir moins lisible et moins productif. Dans cet article, j'ai beaucoup écrit sur la blague à laquelle j'ai pensé la troisième année, mais il semble que l'équipe ait également imaginé une manière d'écrire de la même manière et utilisé de nouvelles fonctionnalités Java pour augmenter la productivité de l'équipe. Je serais heureux s'il y en avait.

Recommended Posts

[Pour les débutants] Comment utiliser Stream API après Java 8
[À voir absolument pour l'apprenti ingénieur Java] Comment utiliser l'API Stream
[Java] Introduction à l'API Stream
Application Java pour les débutants: stream
Java8 / 9 Beginners: Streaming API addiction points et comment les gérer
[java8] Pour comprendre l'API Stream
[Introduction à Java] À propos de l'API Stream
Java 8 ~ Stream API ~ pour commencer maintenant
Pour les débutants Java: List, Map, Iterator / Array ... Comment convertir?
API Java Stream
[Ruby] Comment utiliser slice pour les débutants
[Pour les débutants] Comment déboguer avec Eclipse
[Java] [Pour les débutants] Comment insérer des éléments directement dans un tableau à deux dimensions
[Java] Comment tester s'il est nul dans JUnit
[Pour les débutants] Comment implémenter la fonction de suppression
Comment utiliser l'API Java avec des expressions lambda
[Java] (pour MacOS) Méthode de définition du chemin de classe
[Pour les débutants] À propos des expressions lambda et de l'API Stream
[Pour les super débutants] Comment utiliser l'autofocus: vrai
[Introduction à Java] Bases de l'arithmétique Java (pour les débutants)
[Java] Comment rendre plusieurs boucles for uniques
[Java] API / carte de flux
Pratique de l'API Java8 Stream
[Spring Boot] Comment créer un projet (pour les débutants)
[Pour les débutants] Exemple minimum pour afficher RecyclerView en Java
Comment utiliser Truth (bibliothèque d'assertions pour Java / Android)
Résumé de l'API de communication Java (1) Comment utiliser Socket
Introduction à Java pour les débutants Connaissance de base du langage Java ①
Résumé de l'API de communication Java (3) Comment utiliser SocketChannel
Résumé de l'API de communication Java (2) Comment utiliser HttpUrlConnection
Comment faire une boucle Java Map (for Each / extended for statement)
Comment exécuter l'exemple WebCamCapture de NyARToolkit pour Java
Comment utiliser GitHub pour les super débutants (développement d'équipe)
Comment utiliser l'API Chain
Comment abaisser la version java
[Java] Comment utiliser Map
Comment désinstaller Java 8 (Mac)
Java - Comment créer JTable
Exécution de débogage Java [pour les débutants Java]
[Java] Instruction de base pour les débutants
Aide-mémoire de l'API Java Stream
Comment rédiger un commentaire java
Comment utiliser la classe Java
API Java Stream en 5 minutes
[Java] Comment utiliser removeAll ()
[Java] Comment afficher les Wingdings
Comment utiliser Java Map
[Java] Stream API - Traitement de l'arrêt du flux
Comment définir des constantes Java
[Java] Stream API - Traitement intermédiaire de flux
Java pour les débutants, masquage des données
Comment utiliser les variables Java
Comment convertir la base Java
[Java] Comment implémenter le multithreading