[JAVA] Élément 47: Préférez la collecte au flux comme type de retour

47. La collection doit être sélectionnée dans le flux comme type de retour

Dans quel type un élément contigu doit-il être renvoyé?

Les types de retour possibles pour les éléments contigus sont les interfaces de collection, les itérables, les tableaux et les flux.

Retour en flux

Vous entendrez peut-être qu'il est bon de revenir dans un flux, mais comme mentionné au point 45, il est important de séparer le flux de l'itération.

Étant donné que Stream n'hérite pas de Iterable, la seule façon d'utiliser l'instruction for-each pour transformer la valeur renvoyée en tant que flux est d'utiliser la méthode iterator de Stream. Il semble que le code ci-dessous fonctionne correctement avec la méthode iterator.

// Won't compile, due to limitations on Java's type inference
for (ProcessHandle ph : ProcessHandle.allProcesses()::iterator) {
    // Process the process
}

Cependant, ce code ne peut pas être compilé et doit être converti comme suit.

// Hideous workaround to iterate over a stream
for  (ProcessHandle ph : (Iterable<ProcessHandle>)
                        ProcessHandle.allProcesses()::iterator)

Ce code fonctionne, mais il est compliqué et déroutant. Une alternative consiste à utiliser la méthode de l'adaptateur. Le JDK ne fournit pas une telle méthode, mais vous pouvez facilement l'écrire comme ceci:

// Adapter from  Stream<E> to Iterable<E>
public static <E> Iterable<E> iterableOf(Stream<E> stream) {
    return stream::iterator;
}

En utilisant cette méthode d'adaptateur, il est possible de tourner for-each pour le flux comme suit.

for (ProcessHandle p : iterableOf(ProcessHandle.allProcesses())) {
    // Process the process
}

Retour avec Iterable

Au contraire, même si le client essaie de le traiter comme un flux, mais que la valeur de retour ne correspond qu'à Iterable, il faut le gérer. Cette correspondance n'est pas préparée en JDK, mais vous pouvez facilement écrire la méthode correspondante comme suit.

// Adapter from Iterable<E> to Stream<E>
public static <E> Stream<E> streamOf(Iterable<E> iterable) {
    return StreamSupport.stream(iterable.spliterator(), false);
}

Retour en collection

L'interface Collection est un sous-type de Iterable et possède également des méthodes de flux, de sorte qu'elle peut gérer à la fois le traitement d'itération et le traitement de flux. Par conséquent, le type de retour optimal pour une méthode qui renvoie ** éléments contigus est généralement une collection ou un sous-type de collection approprié **. Si les éléments contigus à renvoyer sont suffisamment petits, vous pouvez renvoyer une implémentation d'une Collection telle que ArrayList ou HashSet, mais vous ne devez pas stocker de grands éléments contigus en mémoire pour les renvoyer en tant que ** Collection. ** **

Si les éléments continus à renvoyer sont volumineux mais peuvent être exprimés de manière concise, envisagez d'implémenter une collection spéciale. Par exemple, considérons une implémentation qui renvoie un ensemble de puissance d'un ensemble donné. Qu'est-ce qu'un ensemble de puissance? Par exemple, un ensemble de puissance de {a, b, c} est {{}, {a}, {b}, {c}, {a, b}, {a, c}, {b, Cela ressemble à c}, {a, b, c}}, et s'il y a un ensemble de n éléments, il y a un ensemble de puissances de 2 à la nième puissance. Ne pensez pas à mettre l'ensemble de puissance dans une collection standard, car ce sera un très grand ensemble. Une collection personnalisée qui réalise cela peut être facilement implémentée à l'aide de AbstractList. Le mécanisme consiste à indexer chaque élément de l'ensemble et à déterminer s'ils existent ou non par bit. Le code ressemble à ceci:

package tryAny.effectiveJava;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

//Returns the power set of an input set as custom collection
public class PowerSet {
    public static final <E> Collection<Set<E>> of(Set<E> s) {
        List<E> src = new ArrayList<>(s);
        if (src.size() > 30)
            throw new IllegalArgumentException("Set too big " + s);
        return new AbstractList<Set<E>>() {
            @Override
            public int size() {
                return 1 << src.size(); // 2 to the power srcSize
            }

            @Override
            public boolean contains(Object o) {
                return o instanceof Set && src.containsAll((Set) o);
            }

            @Override
            public Set<E> get(int index) {
                Set<E> result = new HashSet<>();
                for (int i = 0; index != 0; i++, index >>= 1)
                    if ((index & 1) == 1)
                        result.add(src.get(i));
                return result;
            }
        };
    }
}

Dans le code ci-dessus, une exception est levée lorsque l'ensemble contient 30 éléments ou plus. Cela est dû au fait que la valeur maximale qui peut être renvoyée par la méthode de taille de Collection est de 2 à la 31e puissance-1.

Dans certains cas, le type à renvoyer n'est déterminé que par la difficulté de mise en œuvre. Par exemple, envisagez d'écrire une méthode qui renvoie toutes les sous-listes de la liste d'entrée. Vous pouvez écrire trois lignes de code pour créer des sous-listes et les placer dans une collection standard, mais la mémoire doit contenir une collection bidimensionnelle. Ce n'est pas mal comparé à un ensemble qui devrait croître de façon exponentielle, mais c'est inacceptable. L'implémentation d'une collection personnalisée, comme dans le cas des Power Sets, est fastidieuse.

Cependant, renvoyer toutes les sous-listes d'une liste sous forme de flux peut être implémenté directement avec un peu d'ingéniosité. Les sous-listes qui contiennent le premier caractère de la liste sont appelées préfixes. Autrement dit, les préfixes de (a, b, c) sont (a), (a, b), (a, b, c). Les sous-listes qui contiennent le dernier caractère de la liste sont appelées suffixes. Autrement dit, les suffixes de (a, b, c) sont (a, b, c), (b, c), (c). À ce stade, toutes les sous-listes de la liste sont les suffixes des préfixes de liste et une liste vide. La mise en œuvre est la suivante.

package tryAny.effectiveJava;

import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

//Returns a stream of all the sublists of its input list
public class SubLists {
    public static <E> Stream<List<E>> of(List<E> list) {
        return Stream.concat(Stream.of(Collections.emptyList()), prefixes(list).flatMap(SubLists::suffixes));
    }

    private static <E> Stream<List<E>> prefixes(List<E> list) {
        return IntStream.rangeClosed(1, list.size()).mapToObj(end -> list.subList(0, end));
    }

    private static <E> Stream<List<E>> suffixes(List<E> list) {
        return IntStream.range(0, list.size()).mapToObj(start -> list.subList(start, list.size()));
    }
}

Ce code a la même idée que celle imbriquée de la boucle for normale ci-dessous.

for (int start = 0; start < src.size(); start++)
    for (int end = start + 1; end <= src.size(); end++)
        System.out.println(src.subList(start, end));

Une traduction littérale de cette boucle for en traitement de flux serait plus simple, mais moins lisible. Plus précisément, c'est comme suit.

// Returns a stream of all the sublists of its input list
public static <E> Stream<List<E>> of(List<E> list) {
   return IntStream.range(0, list.size())
      .mapToObj(start ->
         IntStream.rangeClosed(start + 1, list.size())
            .mapToObj(end -> list.subList(start, end)))
      .flatMap(x -> x);
}

Aucune des deux implémentations n'est mauvaise, mais certains utilisateurs peuvent avoir besoin de code pour convertir à partir d'un flux afin qu'il puisse être itéré, ou ils peuvent avoir à faire le streaming là où le traitement itératif est naturel. Le code qui convertit à partir du flux afin qu'il puisse être itéré non seulement encombre le code client, mais présente également des problèmes de performances par rapport à l'implémentation dans Collection.

Recommended Posts

Élément 47: Préférez la collecte au flux comme type de retour
Élément 28: Préférer les listes aux tableaux
Rubrique 65: Préférez les interfaces à la réflexion
Rubrique 43: Préférez les références de méthode aux lambdas
Point 42: Préférez les lambdas aux classes anonymes
Élément 39: Préférez les annotations aux modèles de dénomination
Point 85: Préférez les alternatives à la sérialisation Java
Point 58: Préférez les boucles for-each aux boucles for traditionnelles
Élément 23: Préférez les hiérarchies de classes aux classes balisées
Point 61: Préférez les types primitifs aux primitives encadrées
Je cherche un moyen de renvoyer Oui / Non de Dialog sous la forme d'un booléen ...
Élément 81: Préférez les utilitaires de concurrence pour attendre et notifier
Pour déployer manuellement Struts2 en tant que fichier war
Élément 80: Préférez les exécuteurs, les tâches et les flux aux threads
Passez un argument à la méthode et recevez le résultat de l'opération comme valeur de retour