En regardant le cours suivant que j'ai fait:
public class FibonacciSupplier implements Iterator<Integer> {
private final IntPredicate hasNextPredicate;
private int beforePrevious = 0;
private int previous = 1;
private FibonacciSupplier(final IntPredicate hasNextPredicate) {
this.hasNextPredicate = hasNextPredicate;
}
@Override
public boolean hasNext() {
return hasNextPredicate.test(previous);
}
@Override
public Integer next() {
int result = beforePrevious + previous;
beforePrevious = previous;
previous = result;
return result;
}
public static FibonacciSupplier infinite() {
return new FibonacciSupplier(i -> true);
}
public static FibonacciSupplier finite(final IntPredicate predicate) {
return new FibonacciSupplier(predicate);
}
}
Et l'utilisation de celui-ci dans:
public class Problem2 extends Problem<Integer> {
@Override
public void run() {
result = toList(FibonacciSupplier.finite(i -> (i <= 4_000_000)))
.stream()
.filter(i -> (i % 2 == 0))
.mapToInt(i -> i)
.sum();
}
@Override
public String getName() {
return "Problem 2";
}
private static <E> List<E> toList(final Iterator<E> iterator) {
List<E> list = new ArrayList<>();
while (iterator.hasNext()) {
list.add(iterator.next());
}
return list;
}
}
Comment pourrais-je créer un infini Stream<E>
?
Si je devais utiliser Stream<Integer> infiniteStream = toList(FibonacciSupplier.infinite()).stream()
, je n'aurais peut-être même pas un flux infini.
Au lieu de cela, le code serait bouclé à jamais pour la création de la variable list
dans une méthode sous-jacente.
Jusqu'ici, c'est purement théorique, mais je peux certainement en comprendre le besoin si je veux d'abord sauter les x premiers nombres d'un flux infini, puis le limiter par les derniers y, quelque chose comme:
int x = MAGIC_NUMBER_X;
int y = MAGIC_NUMBER_y;
int sum = toList(FibonacciSupplier.infinite())
.stream()
.skip(x)
.limit(y)
.mapToInt(i -> i)
.sum();
Le code ne renverrait jamais un résultat, comment devrait-il être fait?
Votre erreur est de penser que vous avez besoin d'une Iterator
ou d'une Collection
pour créer une Stream
. Pour créer un flux infini, une seule méthode fournissant une valeur après l'autre suffit. Donc pour votre classe FibonacciSupplier
, l’utilisation la plus simple est:
IntStream s=IntStream.generate(FibonacciSupplier.infinite()::next);
ou, si vous préférez les valeurs encadrées:
Stream<Integer> s=Stream.generate(FibonacciSupplier.infinite()::next);
Notez que dans ce cas, la méthode n'a pas besoin d'être nommée next
ni de remplir l'interface Iterator
. Mais peu importe que cela soit comme avec votre classe. De plus, comme nous venons d'indiquer au flux d'utiliser la méthode next
en tant que Supplier
, la méthode hasNext
ne sera jamais appelée. C’est juste infini.
Créer un flux fini en utilisant votre Iterator
est un peu plus compliqué:
Stream<Integer> s=StreamSupport.stream(
Spliterators.spliteratorUnknownSize(
FibonacciSupplier.finite(intPredicate), Spliterator.ORDERED),
false);
Dans ce cas, si vous voulez une IntStream
finie avec des valeurs int
sans boîte, votre FibonacciSupplier
doit implémenter PrimitiveIterator.OfInt
.
En Java 8, il n'y a pas de classes concrètes publiques et concrètes implémentant l'interface Stream . Cependant, il existe quelques méthodes d'usine statiques . L'un des plus importants est StreamSupport.stream . En particulier, il est utilisé dans la méthode default Collection.stream - héritée de la plupart des classes de collection:
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
L'implémentation par défaut de cette méthode crée un Spliterator en appelant spliterator()
et transmet l'objet créé à la méthode factory. Spliterator est une nouvelle interface introduite avec Java 8 pour prendre en charge les flux parallèles . Il est similaire à Iterator , mais contrairement à ce dernier, un Spliterator peut être divisé en plusieurs parties pouvant être traitées indépendamment. Voir Spliterator.trySplit pour plus de détails.
La méthode default Iterable.spliterator a également été ajoutée à Java 8, de sorte que chaque Iterable class prend automatiquement en charge Spliterators . L'implémentation se présente comme suit:
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
La méthode crée le Spliterator à partir d'un arbitraire Iterator . Si vous combinez ces deux étapes, vous pouvez créer un Stream à partir d’un arbitraire Iterator :
<T> Stream<T> stream(Iterator<T> iterator) {
Spliterator<T> spliterator
= Spliterators.spliteratorUnknownSize(iterator, 0);
return StreamSupport.stream(spliterator, false);
}
Pour avoir une impression de Spliterators , voici un exemple très simple sans utilisation de collections. La classe suivante implémente Spliterator pour itérer sur un intervalle d'entiers semi-ouvert:
public final class IntRange implements Spliterator.OfInt {
private int first, last;
public IntRange(int first, int last) {
this.first = first;
this.last = last;
}
public boolean tryAdvance(IntConsumer action) {
if (first < last) {
action.accept(first++);
return true;
} else {
return false;
}
}
public OfInt trySplit() {
int size = last - first;
if (size >= 10) {
int temp = first;
first += size / 2;
return new IntRange(temp, first);
} else {
return null;
}
}
public long estimateSize() {
return Math.max(last - first, 0);
}
public int characteristics() {
return ORDERED | DISTINCT | SIZED | NONNULL
| IMMUTABLE | CONCURRENT | SUBSIZED;
}
}
Vous pouvez utiliser les primitives de prise en charge de flux de bas niveau et la bibliothèque Spliterators
pour créer un flux à partir de Iterator
.
Le dernier paramètre de StreamSupport.stream()
indique que le flux n'est pas parallèle. Assurez-vous de le laisser comme ça parce que votre itérateur de Fibonacci dépend des itérations précédentes.
return StreamSupport.stream( Spliterators.spliteratorUnknownSize( new Iterator<Node>()
{
@Override
public boolean hasNext()
{
// to implement
return ...;
}
@Override
public ContentVersion next()
{
// to implement
return ...;
}
}, Spliterator.ORDERED ), false );
Pour ajouter une autre réponse, AbstractSpliterator est peut-être un meilleur choix, surtout compte tenu de l'exemple de code. Generate est inflexible car il n'y a pas de [bonne] façon de donner une condition d'arrêt sauf en utilisant limit. Limit n'accepte qu'un nombre d'éléments plutôt qu'un prédicat. Nous devons donc savoir combien d'éléments nous voulons générer - ce qui pourrait ne pas être possible et que se passe-t-il si le générateur est une boîte noire?
AbstractSpliterator est une solution de transition entre avoir à écrire un spliterator complet et utiliser Iterator/Iterable. AbstractSpliterator manque juste de la méthode tryAdvance où nous vérifions d’abord notre prédicat pour être fait, et sinon passons la valeur générée à une action. Voici un exemple de séquence de Fibonacci utilisant AbstractIntSpliterator:
public class Fibonacci {
private static class FibonacciGenerator extends Spliterators.AbstractIntSpliterator
{
private IntPredicate hasNextPredicate;
private int beforePrevious = 0;
private int previous = 0;
protected FibonacciGenerator(IntPredicate hasNextPredicate)
{
super(Long.MAX_VALUE, 0);
this.hasNextPredicate = hasNextPredicate;
}
@Override
public boolean tryAdvance(IntConsumer action)
{
if (action == null)
{
throw new NullPointerException();
}
int next = Math.max(1, beforePrevious + previous);
beforePrevious = previous;
previous = next;
if (!hasNextPredicate.test(next))
{
return false;
}
action.accept(next);
return true;
}
@Override
public boolean tryAdvance(Consumer<? super Integer> action)
{
if (action == null)
{
throw new NullPointerException();
}
int next = Math.max(1, beforePrevious + previous);
beforePrevious = previous;
previous = next;
if (!hasNextPredicate.test(next))
{
return false;
}
action.accept(next);
return true;
}
}
public static void main(String args[])
{
Stream<Integer> infiniteStream = StreamSupport.stream(
new FibonacciGenerator(i -> true), false);
Stream<Integer> finiteStream = StreamSupport.stream(
new FibonacciGenerator(i -> i < 100), false);
// Print with a side-effect for the demo
infiniteStream.limit(10).forEach(System.out::println);
finiteStream.forEach(System.out::println);
}
}
Pour plus de détails, j'ai couvert les générateurs Java 8 dans mon blog http://thecannycoder.wordpress.com/