definicion - para que sirve settitle en java
¿Java 8 proporciona una buena manera de repetir un valor o función? (4)
En muchos otros idiomas, ej. Haskell, es fácil repetir un valor o función varias veces, ej. para obtener una lista de 8 copias del valor 1:
take 8 (repeat 1)
pero aún no lo he encontrado en Java 8. ¿Hay una función en el JDK de Java 8?
O, alternativamente, algo equivalente a un rango como
[1..8]
Parecería un reemplazo obvio para una declaración detallada en Java como
for (int i = 1; i <= 8; i++) {
System.out.println(i);
}
tener algo así como
Range.from(1, 8).forEach(i -> System.out.println(i))
aunque este ejemplo particular no parece mucho más conciso en realidad ... pero con suerte es más legible.
Aquí hay otra técnica que encontré el otro día:
Collections.nCopies(8, 1)
.stream()
.forEach(i -> System.out.println(i));
La llamada Collections.nCopies
crea una List
contiene n
copias de cualquier valor que proporciones. En este caso, es el valor Integer
1. Por supuesto, en realidad no crea una lista con n
elementos; crea una lista "virtualizada" que contiene solo el valor y la longitud, y cualquier llamada para get
dentro del rango simplemente devuelve el valor. El método nCopies
ha existido desde que se introdujo el Framework de Colecciones desde el JDK 1.2. Por supuesto, la capacidad de crear una secuencia a partir de su resultado se agregó en Java SE 8.
Gran cosa, otra forma de hacer lo mismo en aproximadamente el mismo número de líneas.
Sin embargo, esta técnica es más rápida que los IntStream.generate
e IntStream.iterate
, y sorprendentemente, también es más rápido que el enfoque de IntStream.range
.
Para iterate
y generate
el resultado quizás no sea demasiado sorprendente. El marco de flujos (en realidad, los Spliterators para estas secuencias) se basa en la suposición de que las lambdas generarán potencialmente diferentes valores cada vez, y que generarán un número ilimitado de resultados. Esto hace que la división paralela sea particularmente difícil. El método iterate
también es problemático para este caso porque cada llamada requiere el resultado de la anterior. Entonces las secuencias que usan generate
e iterate
no funcionan muy bien para generar constantes repetidas.
El rendimiento relativamente bajo del range
es sorprendente. Esto también está virtualizado, por lo que los elementos en realidad no existen en la memoria, y el tamaño se conoce desde el principio. Esto debería hacer un spliterator rápido y fácilmente paralelizable. Pero, sorprendentemente, no le fue muy bien. Quizás la razón es que el range
tiene que calcular un valor para cada elemento del rango y luego llamar una función sobre él. Pero esta función simplemente ignora su entrada y devuelve una constante, por lo que estoy sorprendido de que no esté en línea y asesinado.
La técnica Collections.nCopies
tiene que hacer boxeo / unboxing para manejar los valores, ya que no hay especializaciones primitivas de List
. Como el valor es el mismo siempre, básicamente se encuadra una vez y todas las n
copias comparten esa caja. Sospecho que el boxeo / unboxing está altamente optimizado, incluso intrínseco, y puede ser bien alineado.
Aquí está el código:
public static final int LIMIT = 500_000_000;
public static final long VALUE = 3L;
public long range() {
return
LongStream.range(0, LIMIT)
.parallel()
.map(i -> VALUE)
.map(i -> i % 73 % 13)
.sum();
}
public long ncopies() {
return
Collections.nCopies(LIMIT, VALUE)
.parallelStream()
.mapToLong(i -> i)
.map(i -> i % 73 % 13)
.sum();
}
Y aquí están los resultados de JMH: (2.8GHz Core2Duo)
Benchmark Mode Samples Mean Mean error Units
c.s.q.SO18532488.ncopies thrpt 5 7.547 2.904 ops/s
c.s.q.SO18532488.range thrpt 5 0.317 0.064 ops/s
Hay una gran cantidad de variación en la versión de ncopies, pero en general parece cómodamente 20 veces más rápido que la versión de rango. (Sin embargo, estaría bastante dispuesto a creer que he hecho algo mal).
Me sorprende lo bien que funciona la técnica de nCopies
. Internamente, no es muy especial, ¡con la secuencia de la lista virtualizada simplemente implementada usando IntStream.range
! Esperaba que fuera necesario crear un spliterator especializado para que esto funcione rápido, pero ya parece ser bastante bueno.
Para completar, y también porque no pude evitarlo :)
Generar una secuencia limitada de constantes es bastante similar a lo que verías en Haskell, solo que con el nivel de detalle de Java.
IntStream.generate(() -> 1)
.limit(8)
.forEach(System.out::println);
Para este ejemplo específico, podrías hacer:
IntStream.rangeClosed(1, 8)
.forEach(System.out::println);
Si necesita un paso diferente de 1, puede usar una función de mapeo, por ejemplo, para un paso de 2:
IntStream.rangeClosed(1, 8)
.map(i -> 2 * i - 1)
.forEach(System.out::println);
O crea una iteración personalizada y limita el tamaño de la iteración:
IntStream.iterate(1, i -> i + 2)
.limit(8)
.forEach(System.out::println);
Una vez que una función de repetición se define en algún lugar como
public static BiConsumer<Integer, Runnable> repeat = (n, f) -> {
for (int i = 1; i <= n; i++)
f.run();
};
Puede usarlo de vez en cuando de esta manera, por ejemplo:
repeat.accept(8, () -> System.out.println("Yes"));
Para obtener y equivalente a Haskell''s
take 8 (repeat 1)
Podrías escribir
StringBuilder s = new StringBuilder();
repeat.accept(8, () -> s.append("1"));