¿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.



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);

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 .



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());

43

Esto es posible en Java 9.

Stream.generate(() -> null)
    .takeWhile(x -> iterator.hasNext())
    .map(n -> iterator.next())
    .forEach(System.out::println);

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);

1
import com.google.common.collect.Streams;

y uso Streams.stream(iterator):

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


-4

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


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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.