vida perezosos perezoso osos oso meme habitat dibujo clases caracteristicas bebe java-8 java-stream

java-8 - perezosos - perezoso habitat



¿Cómo se implementan los flujos perezosos en Java 8? (3)

Estoy leyendo Java 8, específicamente el "Streams API". Quería saber cómo las corrientes pueden ser perezosas?

Creo que las secuencias solo se agregan como una biblioteca y no se realizan cambios en el lenguaje para apoyar la pereza. Además, me sorprendería si alguien me dice que se ha logrado a través de la reflexión.


¿Por qué necesitarías reflexión para obtener la pereza? Por ejemplo, considere esta clase:

class LazySeq<T> { private final List<T> list; private Predicate<? super T> predicate; public LazySeq(List<T> input) { this.list = new ArrayList<>(input); } //Here you just store the predicate, but you don''t perform a filtering //You could also return a new LazySeq with a new state public LazySeq<T> filter(Predicate<? super T> predicate) { this.predicate = predicate; return this; } public void forEach(Consumer<? super T> consumer){ if(predicate == null) { list.forEach(consumer); } else { for(T elem : list) { if(predicate.test(elem)) { consumer.accept(elem); } } } } }

Cuando llama al filter en la secuencia lenta, el filtrado no ocurre inmediatamente, por ejemplo:

LazySeq<Integer> lazySeq = new LazySeq<>(Arrays.asList(1, 2, 3, 4)); lazySeq = lazySeq.filter(i -> i%2 == 0);

Si ve el contenido de la secuencia después de llamar al filtro, verá que siempre es 1, 2, 3, 4 . Sin embargo, al llamar a una operación de terminal, como forEach , el filtrado se realizará antes de usar el consumidor. Así por ejemplo:

lazySeq.filter(i -> i%2 == 0).forEach(System.out::println);

imprimirá 2 y 4.

Este es el mismo principio con Stream s. Desde una fuente, encadena operaciones que tienen ciertas propiedades. Estas operaciones son intermedias, que devuelven un flujo perezoso (como un filter o map ), o un terminal (como forEach ). Algunas de estas operaciones de terminal son cortocircuitos (como findFirst ), por lo que es posible que no atraviese toda la canalización (por ejemplo, puede pensar en un retorno anticipado en un bucle for que devuelve el índice de un valor en una matriz).

Al llamar a una operación de terminal, esta cadena de operaciones comienza a ejecutarse de modo que al final obtenga el resultado esperado.

La pereza puede lograrse almacenando un nuevo estado en la tubería cuando se aplica una operación intermedia, y cuando llama a una terminal, recorre todos los estados uno por uno en los datos.

La API de Stream no se implementa realmente de esa manera (es un poco más compleja) pero realmente el principio está aquí.


No hay reflejo o proxies. La reflexión y los proxies vienen con un costo de rendimiento que debe evitarse a menos que no haya una alternativa y el rendimiento sea el número uno en Java .

Lo que hace posible la pereza es el estilo funcional de hacer las cosas. Básicamente, un stream comienza con una fuente (ej: Lista), número de operaciones intermedias (ej: filtros, mapa ...) y una operación de terminal (ej: conteo, suma, etc.). Los pasos intermedios se ejecutan perezosamente porque pasa las funciones (lambdas) que se encadenan en la tubería para que se ejecuten en el paso terminal.

ex: filter(Predicate<? super T>)

filter en este ejemplo espera una función que nos diga si un objeto en el flujo cumple con algunos criterios o no.

Se han usado muchas características que vienen de Java 7 para hacer esto eficiente. Ej: invocar dynamic para ejecutar lambdas en lugar de proxies o clases internas anónimas y pools de ForkJoin para ejecución paralela.

Si está interesado en los aspectos internos de Java 8, entonces tiene que ver esta charla del experto Brian Goetz, que se encuentra en Youtube .


la transmisión no es un contenedor de datos, sino un contenedor de lógica. solo necesitas pasar una instancia de interfaz para recordar la lógica.

Considere el siguiente código:

class FilterIterable<T> implements Iterable<T> { private Iterable<? extends T> iter; private Predicate<? super T> pred; public FilterIterable(Iterable<? extends T> iter, Predicate<? super T> pred) { this.iter = iter; this.pred = pred; } public Iterator<T> iterator() { return FilterIterator<T>(); } class FilterIterator<T> implements Iterator<T> { private Iterator<? extends T> iterator = iter.iterator(); private T next = null; FilterIterator() { getNext(); } private void getNext() { next = null; while (iterator.hasNext()) { T temp = iterator.next(); if (pred.test(temp)) { next = temp; break; } } } public boolean hasNext() { return next != null; } public T next() { T temp = next; getNext(); return temp; } } }

la lógica está envuelta dentro de pred , pero solo se invoca cuando el objeto se itera. y, de hecho, esta clase no almacena datos, solo mantiene un iterable que puede contener datos, o incluso solo otro soporte lógico.

y los elementos se devuelven bajo demanda también. Este tipo de paradigma hace que la llamada secuencia api sea perezosa.