java - settitle - ¿Por qué no tienen primitivo Stream(Collector)?
para que sirve settitle en java (5)
Convierta las corrientes primitivas en secuencias de objetos en caja si hay métodos que falta.
MyResult result = businessObjs.stream()
.mapToInt( ... )
.boxed()
.collect( new MyComplexComputation(...));
O no utilice los flujos primitivos en primer lugar y trabaje con Integer
s todo el tiempo.
MyResult result = businessObjs.stream()
.map( ... ) // map to Integer not int
.collect( new MyComplexComputation(...));
Estoy escribiendo una biblioteca para programadores novatos, así que estoy tratando de mantener la API lo más limpia posible.
Una de las cosas que debe hacer mi biblioteca es realizar algunos cálculos complejos en una gran colección de ints o largos. Hay muchos escenarios y objetos de negocio que mis usuarios necesitan para calcular estos valores, por lo que pensé que la mejor manera sería usar secuencias para permitir a los usuarios asignar objetos de negocio a IntStream
o LongStream
y luego calcular los cálculos dentro de un recopilador.
Sin embargo, IntStream y LongStream solo tienen el método de recopilación de 3 parámetros:
collect(Supplier<R> supplier, ObjIntConsumer<R> accumulator, BiConsumer<R,R> combiner)
Y no tiene el método de collect(Collector)
simple que tiene Stream<T>
.
Así que en lugar de poder hacer
Collection<T> businessObjs = ...
MyResult result = businessObjs.stream()
.mapToInt( ... )
.collect( new MyComplexComputation(...));
Tengo que proporcionar proveedores, acumuladores y combinadores como este:
MyResult result = businessObjs.stream()
.mapToInt( ... )
.collect(
()-> new MyComplexComputationBuilder(...),
(builder, v)-> builder.add(v),
(a,b)-> a.merge(b))
.build(); //prev collect returns Builder object
Esto es demasiado complicado para mis usuarios novatos y es muy propenso a errores.
Mi trabajo consiste en crear métodos estáticos que tomen un IntStream
o LongStream
como entrada y oculten la creación y ejecución del recopilador para usted.
public static MyResult compute(IntStream stream, ...){
return .collect(
()-> new MyComplexComputationBuilder(...),
(builder, v)-> builder.add(v),
(a,b)-> a.merge(b))
.build();
}
Pero eso no sigue las convenciones normales de trabajar con Streams:
IntStream tmpStream = businessObjs.stream()
.mapToInt( ... );
MyResult result = MyUtil.compute(tmpStream, ...);
Debido a que tiene que guardar una variable temporal y pasarla al método estático, o crear la secuencia dentro de la llamada estática, lo que puede ser confuso cuando se mezcla con los otros parámetros para mi cálculo.
¿Hay una forma más limpia de hacerlo mientras aún trabajas con IntStream
o LongStream
?
De hecho, hicimos prototipos de algunas especializaciones de Collector.OfXxx
. Lo que encontramos, además de la obvia molestia de los tipos más especializados, fue que esto no era realmente muy útil sin tener un complemento completo de colecciones primitivas especializadas (como hace Trove o GS-Colecciones, pero sí lo hace el JDK). no tengo). Sin una IntArrayList, por ejemplo, un Collector.OfInt simplemente empuja el boxeo en otro lugar, desde el Collector al contenedor, que no es una gran ganancia, y mucho más superficie API.
El Sr. Geotz proporcionó la respuesta definitiva de por qué se tomó la decisión de no incluir Coleccionistas especializados ; sin embargo, quería investigar más a fondo cuánto afectó esta decisión al rendimiento.
Pensé que publicaría mis resultados como respuesta.
Utilicé el marco jmh microbenchmark para calcular el tiempo que lleva calcular los cálculos utilizando ambos tipos de recopiladores en colecciones de tamaños 1, 100, 1000, 100,000 y 1 millón:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class MyBenchmark {
@Param({"1", "100", "1000", "100000", "1000000"})
public int size;
List<BusinessObj> seqs;
@Setup
public void setup(){
seqs = new ArrayList<BusinessObj>(size);
Random rand = new Random();
for(int i=0; i< size; i++){
//these lengths are random but over 128 so no caching of Longs
seqs.add(BusinessObjFactory.createOfRandomLength());
}
}
@Benchmark
public double objectCollector() {
return seqs.stream()
.map(BusinessObj::getLength)
.collect(MyUtil.myCalcLongCollector())
.getAsDouble();
}
@Benchmark
public double primitiveCollector() {
LongStream stream= seqs.stream()
.mapToLong(BusinessObj::getLength);
return MyUtil.myCalc(stream)
.getAsDouble();
}
public static void main(String[] args) throws RunnerException{
Options opt = new OptionsBuilder()
.include(MyBenchmark.class.getSimpleName())
.build();
new Runner(opt).run();
}
}
Aquí están los resultados:
# JMH 1.9.3 (released 4 days ago)
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_31.jdk/Contents/Home/jre/bin/java
# VM options: <none>
# Warmup: 20 iterations, 1 s each
# Measurement: 20 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.sample.MyBenchmark.objectCollector
# Run complete. Total time: 01:30:31
Benchmark (size) Mode Cnt Score Error Units
MyBenchmark.objectCollector 1 avgt 200 140.803 ± 1.425 ns/op
MyBenchmark.objectCollector 100 avgt 200 5775.294 ± 67.871 ns/op
MyBenchmark.objectCollector 1000 avgt 200 70440.488 ± 1023.177 ns/op
MyBenchmark.objectCollector 100000 avgt 200 10292595.233 ± 101036.563 ns/op
MyBenchmark.objectCollector 1000000 avgt 200 100147057.376 ± 979662.707 ns/op
MyBenchmark.primitiveCollector 1 avgt 200 140.971 ± 1.382 ns/op
MyBenchmark.primitiveCollector 100 avgt 200 4654.527 ± 87.101 ns/op
MyBenchmark.primitiveCollector 1000 avgt 200 60929.398 ± 1127.517 ns/op
MyBenchmark.primitiveCollector 100000 avgt 200 9784655.013 ± 113339.448 ns/op
MyBenchmark.primitiveCollector 1000000 avgt 200 94822089.334 ± 1031475.051 ns/op
Como puede ver, la versión primitiva de Stream es un poco más rápida, pero incluso cuando hay 1 millón de elementos en la colección, es solo 0.05 segundos más rápida (en promedio).
Para mi API, prefiero seguir las convenciones de la Corriente de Objetos más limpia y usar la versión en caja ya que es una penalización de rendimiento menor.
Gracias a todos los que arrojaron una idea de este problema.
He implementado los coleccionistas primitivos en mi biblioteca StreamEx (desde la versión 0.3.0). Existen interfaces IntCollector
, LongCollector
y DoubleCollector
que amplían la interfaz del Collector
y están especializadas para trabajar con primitivos. Hay una pequeña diferencia adicional en la combinación de procedimientos, ya que los métodos como IntStream.collect
aceptan un BiConsumer
lugar de BinaryOperator
.
Hay un montón de métodos de recopilación predefinidos para unir números a una cadena, almacenar en una matriz primitiva, BitSet
, encontrar min, max, sum, calcular estadísticas de resumen, realizar operaciones de agrupación por y partición. Por supuesto, puedes definir tus propios coleccionistas. Aquí hay varios ejemplos de uso (asumiendo que tiene int[] input
matriz de int[] input
con datos de entrada).
Unir números como una cadena con separador:
String nums = IntStreamEx.of(input).collect(IntCollector.joining(","));
Agrupación por último dígito:
Map<Integer, int[]> groups = IntStreamEx.of(input)
.collect(IntCollector.groupingBy(i -> i % 10));
Suma los números positivos y negativos por separado:
Map<Boolean, Integer> sums = IntStreamEx.of(input)
.collect(IntCollector.partitioningBy(i -> i > 0, IntCollector.summing()));
Aquí hay un simple benchmark que compara estos coleccionistas y los coleccionistas de objetos habituales.
Tenga en cuenta que mi biblioteca no proporciona (y no proporcionará en el futuro) ninguna estructura de datos visible para el usuario, como mapas en primitivos, por lo que la agrupación se realiza en el HashMap
habitual. Sin embargo, si está utilizando Trove / GS / HFTC / lo que sea, no es tan difícil escribir recopiladores primitivos adicionales para las estructuras de datos definidas en estas bibliotecas para obtener más rendimiento.
Quizás si se usan referencias de métodos en lugar de lambdas, el código necesario para la recopilación de flujos primitivos no parecerá tan complicado.
MyResult result = businessObjs.stream()
.mapToInt( ... )
.collect(
MyComplexComputationBuilder::new,
MyComplexComputationBuilder::add,
MyComplexComputationBuilder::merge)
.build(); //prev collect returns Builder object
En la respuesta definitiva de Brian a esta pregunta , menciona otros dos marcos de recopilación de Java que sí tienen colecciones primitivas que en realidad se pueden usar con el método de recopilación en flujos primitivos. Pensé que podría ser útil ilustrar algunos ejemplos de cómo usar los contenedores primitivos en estos marcos con flujos primitivos. El código de abajo también funcionará con una transmisión paralela.
// Eclipse Collections
List<Integer> integers = Interval.oneTo(5).toList();
Assert.assertEquals(
IntInterval.oneTo(5),
integers.stream()
.mapToInt(Integer::intValue)
.collect(IntArrayList::new, IntArrayList::add, IntArrayList::addAll));
// Trove Collections
Assert.assertEquals(
new TIntArrayList(IntStream.range(1, 6).toArray()),
integers.stream()
.mapToInt(Integer::intValue)
.collect(TIntArrayList::new, TIntArrayList::add, TIntArrayList::addAll));
Nota: Soy un comendador de Eclipse Collections .