¿Cómo convertir un iterador en una secuencia?

468

Busco una manera concisa para convertir una Iteratora una Streamo más específicamente a "ver" el iterador como una corriente.

Por razones de rendimiento, me gustaría evitar una copia del iterador en una nueva lista:

Iterator<String> sourceIterator = Arrays.asList("A", "B", "C").iterator();
Collection<String> copyList = new ArrayList<String>();
sourceIterator.forEachRemaining(copyList::add);
Stream<String> targetStream = copyList.stream();

Basado en algunas sugerencias en los comentarios, también he tratado de usar Stream.generate:

public static void main(String[] args) throws Exception {
    Iterator<String> sourceIterator = Arrays.asList("A", "B", "C").iterator();
    Stream<String> targetStream = Stream.generate(sourceIterator::next);
    targetStream.forEach(System.out::println);
}

Sin embargo, obtengo un NoSuchElementException(ya que no hay invocación de hasNext)

Exception in thread "main" java.util.NoSuchElementException
    at java.util.AbstractList$Itr.next(AbstractList.java:364)
    at Main$$Lambda$1/1175962212.get(Unknown Source)
    at java.util.stream.StreamSpliterators$InfiniteSupplyingSpliterator$OfRef.tryAdvance(StreamSpliterators.java:1351)
    at java.util.Spliterator.forEachRemaining(Spliterator.java:326)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at Main.main(Main.java:20)

Lo miré StreamSupporty Collectionsno encontré nada.

gontard
fuente
3
@DmitryGinzburg euh no quiero crear un flujo "Infinito".
gontard 01 de
1
@DmitryGinzburg Stream.generate(iterator::next)funciona?
gontard 01 de
1
@DmitryGinzburg Eso no funcionará para un iterador finito.
assylias 01 de

Respuestas:

543

Una forma es crear un Spliterator desde el Iterator y usarlo como base para su transmisión:

Iterator<String> sourceIterator = Arrays.asList("A", "B", "C").iterator();
Stream<String> targetStream = StreamSupport.stream(
          Spliterators.spliteratorUnknownSize(sourceIterator, Spliterator.ORDERED),
          false);

Una alternativa que tal vez sea más legible es usar un Iterable, y crear un Iterable desde un Iterator es muy fácil con lambdas porque Iterable es una interfaz funcional:

Iterator<String> sourceIterator = Arrays.asList("A", "B", "C").iterator();

Iterable<String> iterable = () -> sourceIterator;
Stream<String> targetStream = StreamSupport.stream(iterable.spliterator(), false);
asilias
fuente
26
Las secuencias son perezosas: el código solo vincula la secuencia al iterador, pero la iteración real no ocurrirá hasta una operación de terminal. Si usa el iterador mientras tanto, no obtendrá el resultado esperado. Por ejemplo, puede introducir un sourceIterator.next()antes de usar la transmisión y verá el efecto (la transmisión no verá el primer elemento).
assylias 01 de
99
@assylias, sí, ¡es realmente agradable! Tal vez podrías explicar para futuros lectores esta línea bastante mágica Iterable<String> iterable = () -> sourceIterator;. Tengo que admitir que me tomó un tiempo entenderlo.
gontard
77
Debo decir lo que encontré. Iterable<T>es un FunctionalInterfaceque solo tiene un método abstracto iterator(). Entonces, ¿ () -> sourceIteratores una expresión lambda instanciando una Iterableinstancia como una implementación anónima?
Jin Kwon
13
Nuevamente, () -> sourceIterator;es una forma abreviada denew Iterable<>() { @Override public Iterator<String> iterator() { return sourceIterator; } }
Jin Kwon
77
@ JinKwon No es realmente una forma abreviada de una clase anónima (hay algunas diferencias sutiles, como el alcance y cómo se compila), pero se comporta de manera similar en este caso.
Assylias
122

Desde la versión 21, la biblioteca Guava proporciona Streams.stream(iterator)

Se hace lo @ assylias 's respuesta espectáculos .

numéro6
fuente
Es mucho mejor usar esto de manera consistente hasta que JDK admita una línea nativa. Será mucho más sencillo encontrar (por lo tanto, refactorizar) esto en el futuro que las soluciones de JDK puro que se muestran en otros lugares.
Drekbour
Esto es excelente pero ... ¿cómo Java tiene iteradores y flujos nativos ... pero no hay una forma directa e integrada de ir de uno a otro? Toda una omisión en mi opinión.
Dan Lenski
92

Gran sugerencia! Aquí está mi versión reutilizable:

public class StreamUtils {

    public static <T> Stream<T> asStream(Iterator<T> sourceIterator) {
        return asStream(sourceIterator, false);
    }

    public static <T> Stream<T> asStream(Iterator<T> sourceIterator, boolean parallel) {
        Iterable<T> iterable = () -> sourceIterator;
        return StreamSupport.stream(iterable.spliterator(), parallel);
    }
}

Y uso (asegúrese de importar estáticamente asStream):

List<String> aPrefixedStrings = asStream(sourceIterator)
                .filter(t -> t.startsWith("A"))
                .collect(toList());
Matan
fuente
43

Esto es posible en Java 9.

Stream.generate(() -> null)
    .takeWhile(x -> iterator.hasNext())
    .map(n -> iterator.next())
    .forEach(System.out::println);
PhilipRoman
fuente
1
Simple, eficiente y sin recurrir a subclases: ¡esta debería ser la respuesta aceptada!
martyglaubitz
1
Desafortunadamente, estos no parecen funcionar con .parallel()transmisiones. También parecen un poco más lentos que ir Spliterator, incluso para uso secuencial.
Thomas Ahle
Además, el primer método aparece si el iterador está vacío. El segundo método funciona por ahora, pero viola el requisito de las funciones en map y tomarWhile por no tener estado, por lo que dudaría en hacerlo en el código de producción.
Hans-Peter Störr
Realmente, esta debería ser una respuesta aceptada. Aunque parallelpodría ser funky, la sencillez es increíble.
Sven
11

La clase Create Spliteratorfrom Iteratorusing Spliteratorscontiene más de una función para crear spliterator, por ejemplo, aquí estoy usando spliteratorUnknownSizeel iterador como parámetro, luego cree Stream usandoStreamSupport

Spliterator<Model> spliterator = Spliterators.spliteratorUnknownSize(
        iterator, Spliterator.NONNULL);
Stream<Model> stream = StreamSupport.stream(spliterator, false);
Bassem Reda Zohdy
fuente
1
import com.google.common.collect.Streams;

y uso Streams.stream(iterator):

Streams.stream(iterator)
       .map(v-> function(v))
       .collect(Collectors.toList());
sneha
fuente
-4

Utilizar Collections.list(iterator).stream()...

Israel CS Rocha
fuente
66
Si bien es corto, esto tiene un rendimiento muy bajo.
Olivier Grégoire
2
Esto desplegará todo el iterador en objeto java y luego lo convertirá en secuencia. No lo sugiero
iec2011007
3
Esto parece ser solo para enumeraciones, no iteradores.
john16384
1
No es una respuesta terrible en general, útil en caso de apuro, pero la pregunta menciona el rendimiento y la respuesta no es eficaz.
Trineo