Collection, collections, Collect, Collector, Collectos
La collection est l'interface ancêtre des collections Java.
Les collections sont une classe d'outils dans le cadre du package Java.util, qui connota diverses méthodes statiques pour le traitement des collections.
java.util.stream.stream # Collect (java.util.stream.collector <? Super T, a, r>) est une fonction du flux responsable de la collecte de flux.
java.util.stream.collector est une interface pour la collecte de fonctions qui déclare les fonctions d'un collecteur.
Java.util ..comparators est une classe d'outils de collection avec une série d'implémentations de collection intégrées.
La fonction du collecteur
Vous pouvez considérer Java8 Streams comme des itérateurs de jeu de données fantaisistes et paresseux. Ils prennent en charge deux types d'opérations: les opérations intermédiaires (par exemple, le filtre, la carte) et les opérations terminales (telles que le nombre, findFirst, foreach, réduction). Les opérations intermédiaires peuvent être connectées pour convertir un flux en un autre. Ces opérations ne consomment pas de flux et le but est de créer un pipeline. En revanche, les opérations terminales consomment des classes, ce qui entraîne un résultat final. Collect est une opération de réduction, tout comme la réduction, elle peut accepter diverses méthodes comme paramètres et accumuler des éléments dans le flux dans un résultat sommaire. L'approche spécifique est définie en définissant une nouvelle interface collector.
Collectionneurs prédéfinis
Ce qui suit est une brève démonstration du collecteur intégré de base. La source de données simulée est la suivante:
Final ArrayList <Dish> Dishes = lists.NewArrayList (nouveau plat ("porc", false, 800, type.meat), nouveau plat ("bœuf", false, 700, type.meat), nouveau plat ("poulet", false, 400, type.meat), nouveau plat ("French Fries", true, 530, type.other), nouveau plat ("Rice", true, 350, type. vrai, 120, type.other), nouveau plat ("pizza", vrai, 550, type.other), nouveau plat ("crevettes", false, 300, type.fish), nouveau plat ("saumon", false, 450, type.fish));Valeur maximale, valeur minimale, valeur moyenne
// Pourquoi retourner facultatif? Que faire si le flux est nul? Optinal a beaucoup de sens à ce moment facultatif <law> mostcaloridish = Dishes.Stream (). Max (comparateur.compartingInt (Dish :: Getcalories)); Facultatif <Dish> Mincaloriedish = Dishes.Stream (). Dishes.Stream (). Collectez (collecteurs.AvingIntInt (Dish :: Getcalories)); Intsummarystatistics SummaryStatistics = Dishes.Stream (). Collect (Collectors.SumlarisingInt (Dish :: Getcalories)); double moyenne = sommarystatistics.getAuthert (); Count long = SummaryStatistics.getCount (); int max = sommarystatistics.getMax (); int min = résuméStatistics.getMin (); Sum long = résuméstatistics.getsum ();
Ces indicateurs statistiques simples ont des fonctions de collection intégrées collecteurs, en particulier pour les fonctions de déballage de type numérique, qui seront beaucoup moins coûteuses que l'exploitation directe du type d'emballage.
Connectez le collecteur
Vous voulez assembler les éléments du flux?
// connecter directement String join1 = Dishes.Stream (). Map (Dish :: getName) .Collect (collectionners.joining ()); // String Comma join2 = Dishes.Stream (). Map (Dish :: GetName) .Collect (Collectors.Joining (","));toliste
List <string> names = Dishes.Stream (). Map (Dish :: GetName) .Collect (Tolist ());
Maptez le flux d'origine dans un flux d'élément unique et collectez-le en tant que liste.
enchet
Set <pype> types = Dishes.Stream (). Map (Dish :: GetType) .Collect (collectionners.toset ());
Collectez le type comme un ensemble et vous pouvez le répéter.
tomap
Map <type, Dish> byType = Dishs.Stream (). Collecture (tomap (Dish :: GetType, D -> D));
Parfois, il peut être nécessaire de convertir un tableau en carte pour le cache, ce qui facilite plusieurs calculs et acquisitions. Tomap fournit les fonctions de génération des méthodes k et v. (Notez que la démo ci-dessus est une fosse, vous ne pouvez pas l'utiliser comme ceci !!!
Ce qui précède est presque les collectionneurs les plus couramment utilisés, et ils sont essentiellement suffisants. Mais en tant que débutant, la compréhension prend du temps. Pour vraiment comprendre pourquoi cela peut être utilisé pour collecter, vous devez vérifier la mise en œuvre interne. Vous pouvez voir que ces collectionneurs sont basés sur java.util.stream.collectors.collectorImpl, qui est une classe d'implémentation de collection mentionné au début. Le collecteur personnalisé apprendra l'utilisation spécifique plus tard.
Réduction personnalisée
Les quelques précédents sont des cas particuliers du processus de réduction défini par la méthode d'usine de réduction. En fait, la réduction des collectionneurs peut être utilisée pour créer un collecteur. Par exemple, cherchez une somme
Integer Totalcalories = Dishes.Stream (). Collect (réduction (0, Dish :: Getcalories, (i, j) -> i + j)); // Utiliser la fonction intégrée au lieu de la fonction flèche entière totale totale2 = Dishes.Stream (). Collect (réducteur (0, plat :: getcalories, entier :: sum));
Bien sûr, vous pouvez également utiliser Réduire directement
Facultatif <Integer> Totalcalories3 = Dishes.Stream (). Carte (Dish :: Getcalories) .reduce (entier :: sum);
Bien que ce soit OK, si vous envisagez l'efficacité, vous devez toujours choisir ce qui suit
int sum = Dishes.Stream (). MaptInT (Dish :: getcalories) .sum ();
Choisissez la meilleure solution selon la situation
Comme mentionné ci-dessus, la programmation fonctionnelle fournit généralement plusieurs façons d'effectuer la même opération. L'utilisation de Collector Collect est plus complexe que l'utilisation d'API de flux. L'avantage est que Collect peut fournir un niveau d'abstraction et de généralisation plus élevé, et est plus facile à réutiliser et à personnaliser.
Notre conseil est d'explorer autant que possible les différentes solutions au problème, choisissez toujours la plus professionnelle, ce qui est généralement la meilleure décision en termes de lisibilité et de performance.
En plus de recevoir une valeur initiale, la réduction peut également utiliser le premier élément comme valeur initiale
Facultatif <Asping> Mostcaloriedish = Dishes.Stream () .Collect (réducteur ((D1, D2) -> d1.getcalories ()> d2.getcalories ()? D1: d2));
Réduction
L'utilisation de la réduction est assez compliquée et l'objectif est de fusionner deux valeurs en une seule valeur.
public statique <t, u> collectionneur <t ,?, u> réduction (U identité, fonction <? Super T ,? étend U> Mappeur, BinaryOperator <u> op)
Tout d'abord, j'ai vu 3 génériques.
U est le type de valeur de retour. Par exemple, la chaleur calculée dans la démo ci-dessus, U est entier.
En ce qui concerne T, T est le type d'élément en flux. À partir de la fonction de fonction, nous pouvons savoir que la fonction du mappeur est de recevoir un paramètre t puis de renvoyer un résultat U. correspondant à la vaisselle en démo.
? Au milieu de la liste générique avec le collecteur de valeur de retour, cela représente le type de conteneur. Un collectionneur a bien sûr besoin d'un conteneur pour stocker des données. Ici? Cela signifie que le type de conteneur est incertain. En fait, le conteneur ici est u [].
À propos des paramètres:
L'identité est la valeur initiale du type de valeur de retour, qui peut être comprise comme le point de départ de l'accumulateur.
Le mappeur est la fonction de la carte, et sa signification réside dans la conversion des flux de flux en flux de type souhaité.
OP est la fonction principale, et sa fonction est de savoir comment gérer deux variables. Parmi eux, la première variable est la valeur cumulative, qui peut être comprise comme la somme, et la deuxième variable est le prochain élément à calculer. Ainsi, l'accumulation est obtenue.
Il existe également une méthode surchargée pour omettre le premier paramètre, ce qui signifie que le premier paramètre du flux est utilisé comme valeur initiale.
Public Static <T> Collector <T ,?, Facultatif <T>> Réduction (BinaryOperator <T> OP)
Voyons la différence entre la valeur de retour. T représente la valeur d'entrée et le type de valeur de retour, c'est-à-dire que le type de valeur d'entrée et le type de valeur de sortie sont les mêmes. Une autre différence est facultative. En effet, il n'y a pas de valeur initiale et que le premier paramètre peut être nul. Lorsque l'élément de flux est nul, il est très significatif de retourner facultatif.
En regardant la liste des paramètres, il ne reste plus que binaryopérateur. BinaryOperator est une interface de fonction triple, l'objectif est de calculer deux paramètres du même type et des mêmes valeurs de retour du même type. Il peut être compris comme 1> 2? 1: 2, c'est-à-dire trouver la valeur maximale de deux nombres. Trouver la valeur maximale est une déclaration relativement facile à comprendre. Vous pouvez personnaliser l'expression lambda pour sélectionner la valeur de retour. Ensuite, ici, il s'agit de recevoir le type d'élément T de deux flux et de retourner la valeur de retour du type T. Il est également correct d'utiliser la somme pour comprendre.
Dans la démo ci-dessus, on constate que les fonctions de réduction et de collecte sont presque les mêmes, les deux renvoient un résultat final. Par exemple, nous pouvons utiliser l'effet de réduction de la toléste:
// implémenter manuellement TolistCollector --- Abus de réduction et des réglementations immuables --- Impossible de parallèle Lister <Integer> Calories = Dishes.Stream (). Map (Dish :: Getcalories) .reduce (New ArrayList <Neger> (), (List <Integer> L, Integer E) -> {L.Add (e); retour l;}, (lister <INTERD> L1, <inder>; l2) -> {l1.addall (l2);Permettez-moi d'expliquer les pratiques ci-dessus.
<u> u réduction (u identité, bifonction <u,? Super T, U> Accumulateur, combintier binaire <u>);
U est le type de valeur de retour, voici la liste
Bifonction <u ,? L'accumulateur Super T, U> est un accumulateur, et son objectif est d'accumuler des valeurs et de calculer les règles pour les éléments individuels. Voici le fonctionnement de la liste et des éléments, et enfin la liste de retour. Autrement dit, ajoutez un élément à la liste.
BinaryOperator <U> Combiner est un combiner, et l'objectif est de fusionner deux variables de types de valeur de retour en un. Voici la fusion de deux listes.
Il y a deux problèmes avec cette solution: l'une est un problème sémantique et l'autre est un problème pratique. Le problème sémantique est que la méthode de réduction vise à combiner deux valeurs pour générer une nouvelle valeur, ce qui est une réduction immuable. Au lieu de cela, la conception de la méthode de collection consiste à modifier le conteneur et à accumuler les résultats à sortir. Cela signifie que l'extrait de code ci-dessus abuse de la méthode de réduction car il modifie la liste en tant qu'accumulateur en place. La mauvaise sémantique pour utiliser la méthode de réduction crée également un problème pratique: cette réduction ne peut pas fonctionner en parallèle, car une modification simultanée de la même structure de données par plusieurs threads peut détruire la liste elle-même. Dans ce cas, si vous souhaitez la sécurité des fils, vous devez allouer une nouvelle liste à la fois et l'allocation d'objets affectera à son tour les performances. C'est pourquoi la collecte convient pour exprimer des réductions sur les conteneurs mutables, et plus important encore, il convient aux opérations parallèles.
Résumé: La réduction convient à la réduction de conteneurs immuables, la collecte convient à la réduction des conteneurs mutables. Collect convient au parallélisme.
Regroupement
La base de données rencontre souvent le besoin de résumé de groupe et fournit le groupe par primitif. En Java, si vous suivez le style pédagogique (écrivez manuellement des boucles), il sera très lourd et sujette aux erreurs. Java 8 fournit des solutions fonctionnelles.
Par exemple, vaisselle de groupe par type. Semblable au TOMAP précédent, mais la valeur de regroupement n'est pas un plat, mais une liste.
Map <type, list <sh>> DishesByType = Dishes.Stream (). Collectez (GroupingBy (Dish :: GetType));
ici
public statique <t, k> collectionneur <t ,?, map <k, list <t>>> groupement (fonction <? super t ,? étend k> classificateur)
Le classificateur de paramètres est une fonction, conçue pour recevoir un paramètre et la convertir en un autre type. La démo ci-dessus est de convertir le plat d'élément de flux en type de type, puis de regrouper le flux en fonction du type. Son regroupement interne est mis en œuvre via HashMap. Groupement par (classificateur, hashmap :: new, en aval);
En plus du regroupement en fonction de la fonction de propriété de l'élément de flux lui-même, vous pouvez également personnaliser la base de regroupement, tel que le regroupement en fonction de la plage de chaleur.
Étant donné que vous savez déjà que le paramètre de regroupement est de la fonction et que le type de fonction de paramètre est Dish, vous pouvez personnaliser le classificateur comme:
Caloriclevel privé getcaloriclevel (Dish d) {if (d.getcalories () <= 400) {return Caloriclevel.Diet; } else if (d.getcalories () <= 700) {return caloriclevel.normal; } else {return caloriclevel.fat; }}Passer juste dans les paramètres
Map <caloriclevel, list <lage>> DishesBylevel = Dishes.Stream () .Collect (GroupingBy (this :: getcaloriclevel));
Regroupement à plusieurs niveaux
Le groupement surcharge également plusieurs autres méthodes, telles que
public statique <t, k, a, d> collecteur <t ,?, map <k, d >> groupement (fonction <? super t ,? étend k> classificateur, collecteur <? Super T, a, d> en aval)
Il existe de nombreux génériques et horreurs. Allons une brève compréhension. Un classificateur est également un classificateur, qui reçoit le type d'élément du flux et renvoie une base pour laquelle vous souhaitez regrouper, c'est-à-dire la cardinalité de la base de regroupement. Ainsi, T représente le type d'élément actuel du flux, et K représente le type d'élément du regroupement. Le deuxième paramètre est en aval, et le montant en aval est un collecteur de collection. Ce type d'élément collecteur est une sous-classe de T, le conteneur de type de conteneur est A, et le type de valeur de retour de réduction est D. c'est-à-dire que le k du groupe est fourni par le classificateur, et la valeur du groupe est réduite par le collecteur du deuxième paramètre. Il se trouve que le code source de la démo précédente est:
public statique <t, k> collectionneur <t ,?, map <k, list <t>>> grouping par (function <? super t ,? étend k> classificateur) {return GroupingBy (Classifier, Tolist ()); }Utilisez Tolist comme collection de réduction, et le résultat final est une liste <law>, de sorte que le type de valeur des fins du groupe est la liste <law>. Ensuite, le type de valeur peut être déterminé de manière analogue par le collecteur de réduction, et il y a des dizaines de millions de collecteurs réduits. Par exemple, je souhaite regrouper à nouveau la valeur, et le regroupement est également une sorte de réduction.
// Carte de regroupement à plusieurs niveaux <Type, map <caloriclevel, list <Dish>>> byTypeAndCalory = Dishes.Stream (). Collect (GroupingBy (Dish :: GetType, GroupingBy (This :: Getcaloriclevel))); byTypeandCalory.ForEach ((type, Sortie) -> { System.out.println("------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- System.out.println ("/ t" + niveau);Les résultats de vérification sont:
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- [Dish (nom = bœuf, végétarien = faux, calories = 700, type=MEAT)]------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- type = autre)]
Résumé: Les paramètres principaux du regroupement sont des générateurs K et des générateurs V. Le générateur V peut être n'importe quel type de collecteur de collecteur.
Par exemple, le générateur V peut calculer le numéro, implémentant ainsi le nombre de sélection (*) dans l'instruction SQL du tableau A Groupe par type
Map <type, long> typeScount = Dishes.Stream (). Collect (GroupingBy (Dish :: GetType, compter ())); System.out.println (typScount); ---------- {Fish = 2, viande = 3, autre = 4} SQL Search Group le plus élevé Score select MAX(id) from table A group by Type
Map <type, facultatif <law>> MORSCALORICBYTYPE = DISHES.STREAM () .COLLECT (GroupingBy (Dish :: GetType, Maxby (Comparator.ChareParingInt (Dish :: Getcalories)))));
Facultatif ici n'a aucun sens, car il n'est certainement pas nul. Ensuite, j'ai dû le retirer. Utilisation de la collecte et
Map <type, Dish> MostcaloricByType = Dishes.Stream () .Collect (GroupingBy (Dish :: GetType, CollectionAndThen (Maxby (Comparator.ChareParingInt (Dish :: Getcalories)), Facultatif :: Get)));
Il semble que le résultat soit sorti ici, mais l'idée n'est pas d'accord. Il compile l'alarme jaune et le modifie en:
Map <type, Dish> MostcaloricByType = Dishes.Stream () .Collect (Tomap (Dish :: GetType, Function.Identity (), BinaryOperator.maxby (comparaison (Dish :: Getcalories)))));
Oui, le regroupement devient Tomap, la clé est toujours de type, la valeur est toujours un plat, mais il y a un paramètre de plus! ! Ici, nous répondons à la fosse au début. La démonstration de Tomap au début est pour une compréhension facile. S'il est vraiment utilisé, il sera tué. Nous savons que la réorganisation d'une liste dans une carte sera inévitablement confrontée au même problème. Lorsque k est le même, V l'emporte-t-il ou l'ignore-t-il? La méthode de démonstration précédente consiste à insérer K à nouveau et à lancer une exception directement lorsque K est présent:
Java.lang.ILLEGALSTATEException: Duplicate Key Dish (nom = porc, légumes = false, calories = 800, type = viande) sur java.util.stream.collectors.lambda $ throwingMerger 0 (collection.java:133)
La bonne façon est de fournir des fonctions pour gérer les conflits. Dans cette démo, le principe de la gestion des conflits est de trouver le plus important, qui répond simplement à nos exigences pour regrouper et trouver la plus grande. (Je ne veux vraiment plus faire l'apprentissage fonctionnel de Java 8, je pense qu'il y a des pièges de problèmes de performance partout)
Continuez la cartographie SQL à base de données, select sum(score) from table a group by Type
Map <type, entier> TotalcaloriesByType = Dishes.Stream () .Collect (GroupingBy (Dish :: GetType, SummingInt (Dish :: Getcalories))));
Cependant, un autre collecteur qui est souvent utilisé en conjonction avec le regroupement par la méthode de mappage. Cette méthode reçoit deux paramètres: une fonction transforme les éléments dans le flux et l'autre collecte les objets de résultat transformés. Le but est d'appliquer une fonction de mappage à chaque élément d'entrée avant l'accumulation, de sorte que le collecteur qui reçoit des éléments d'un type particulier peut s'adapter à différents types d'objets. Permettez-moi de regarder un exemple pratique de l'utilisation de ce collectionneur. Par exemple, vous voulez obtenir les niveaux de caloricaux dans le menu pour chaque type de plat. Nous pouvons combiner le regroupement et la cartographie des collectionneurs comme suit:
Map <type, set <Caloriclevel >> caloriclevelsbyType = Dishes.Stream () .Collect (GroupingBy (Dish :: GetType, Mapping (This :: getcaloriclevel, toset ())));
Le Toseet ici utilise le HashSet par défaut, et vous pouvez également spécifier manuellement la tcollection d'implémentation spécifique (HashSet :: New)
Partition
Le partitionnement est un cas particulier de regroupement: un prédicat (une fonction qui renvoie une valeur booléenne) est utilisée comme fonction de classification, qui est appelée fonction de partition. La fonction de partition renvoie une valeur booléenne, ce qui signifie que le type de clé de la carte groupée est booléen, il peut donc être divisé en jusqu'à deux groupes: vrai ou faux. Par exemple, si vous êtes végétarien, vous voudrez peut-être séparer le menu par végétarien et non végétarien:
Map <boolean, list <sh>> partitionedMenu = Dishes.Stream (). Collect (PartioningBy (Dish :: Isvegetarian));
Bien sûr, l'utilisation du filtre peut obtenir le même effet:
Lister <law> Vegetaringishes = Dishes.Stream (). Filtre (Dish :: Isvegetarian). Collect (collectionners.tolist ());
L'avantage du partitionnement est d'enregistrer deux copies, ce qui est utile lorsque vous souhaitez classer une liste. Dans le même temps, comme le regroupement, le partitionnement par des méthodes surchargées, qui peuvent spécifier le type de valeur de regroupement.
Map <boolean, map <type, list <lage>>> végétarienShesByType = Dishes.Stream () .Collect (partitioningBy (Dish :: Isvegetarian, GroupingBy (Dish :: getType))); map <boolean, entier> végétariendishestotalcalories = wash SummingInt (Dish :: getcalories))); map <boolean, Dish> MostcaloricPartionEdByVegetarian = Dishes.Stream () .Collect (PartioningBy (Dish :: Isvegetarian, CollectionAndThen (Maxby (ComparentInt (Dish :: Getcalories)), optionnel :: get)));
En tant que dernier exemple de l'utilisation du partitionnement par collection, nous mettons le modèle de données de menu de côté pour voir un exemple plus complexe et intéressant: diviser les tableaux en nombres privilégiés et non primaires.
Tout d'abord, définissez une fonction de partition principale:
Private Boolean isprime (int candidat) {int candidateroot = (int) math.sqrt ((double) candidat); return IntStream.Rangeclosed (2, candidateroot) .Nonematch (i -> candidat% i == 0);}Puis trouvez les nombres de premier ordre et sans prisme de 1 à 100
Map <boolean, list <Integer >> partitionPrimes = intStream.Rangeclosed (2, 100) .boxed () .collect (partitioningBy (this :: isprime));