Les types de retour possibles pour les éléments contigus sont les interfaces de collection, les itérables, les tableaux et les 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
}
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);
}
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