Colección, colecciones, recolección, coleccionista, coleccionistas
La colección es la interfaz antepasada de Java Collections.
Collections es una clase de herramientas bajo el paquete java.util, que connota varios métodos estáticos para procesar colecciones.
java.util.stream.stream#coleccionar (java.util.stream.collector <? Super t, a, r>) es una función de la transmisión responsable de recopilar transmisiones.
java.util.stream.collector es una interfaz para recopilar funciones que declaran las funciones de un coleccionista.
java.util.c.comparators es una clase de herramientas de colección con una serie de implementaciones de coleccionistas incorporadas.
La función del coleccionista
Puedes pensar en Java8 Streams como iteradores de conjunto de datos elegantes y perezosos. Admiten dos tipos de operaciones: operaciones intermedias (p. Ej. Filtro, MAP) y operaciones terminales (como Count, FindFirst, foreach, Reduce). Las operaciones intermedias se pueden conectar para convertir una transmisión en otra. Estas operaciones no consumen transmisiones, y el propósito es crear una tubería. En contraste, las operaciones terminales consumen clases, lo que resulta en un resultado final. Collect es una operación de reducción, al igual que la reducción, puede aceptar varios métodos como parámetros y acumular elementos en la corriente en un resultado resumido. El enfoque específico se define definiendo una nueva interfaz coleccionista.
Coleccionistas predefinidos
La siguiente es una breve demostración del coleccionista básico incorporado. La fuente de datos simulada es la siguiente:
final ArrayList<Dish> dishes = Lists.newArrayList( new Dish("pork", false, 800, Type.MEAT), new Dish("beef", false, 700, Type.MEAT), new Dish("chicken", false, 400, Type.MEAT), new Dish("french fries", true, 530, Type.OTHER), new Dish("rice", true, 350, Type.OTHER), new Dish("season fruta ", verdadero, 120, type.other), nuevo plato (" pizza ", verdadero, 550, type.other), nuevo plato (" langostinos ", falsos, 300, type.fish), nuevo plato (" salmón ", falso, 450, type.fish));Valor máximo, valor mínimo, valor promedio
// ¿Por qué devolver opcional? ¿Qué hacer si la transmisión es nula? Optinal tiene mucho sentido en este momento opcional <hec> Mostcaloriedish = plieTes.stream (). Max (comparador.comparingint (plato :: getCalories)); opcional <hect> mincaloriedish = plato.stream (). Min (comparator.comparingint (plato :: getCalories)); doble avgalorías = = avgalorías de doble = = Dishes.stream (). coleccione (coleccionistas.avingInt (plato :: getCalories)); intSummaryStatistics ResumenStatistics = plies.stream (). recolectar (coleccionistas. Long Count = SummaryStatistics.getCount (); int max = summaryStatistics.getMax (); int min = summaryStatistics.getmin (); larga suma = summaryStatistics.getSum ();
Estos simples indicadores estadísticos tienen las funciones de coleccionistas incorporadas de los coleccionistas, especialmente para funciones de unboxing de tipo numérico, que serán mucho menos costosos que operar directamente el tipo de embalaje.
Conecte el coleccionista
¿Quieres armar los elementos de la transmisión?
// Conecte directamente la cadena Join1 = plies.stream (). MAP (DISH :: GetName) .Collect (Collectors.Joining ()); // String de coma Join2 = Dishes.Stream (). Map (DISH :: GetName) .Collect (Collectors.JoNeRe (","));tolista
List <String> Names = plies.stream (). Map (Dish :: GetName) .Collect (tolist ());
Mapee la transmisión original en una transmisión de un solo elemento y colóquela como una lista.
toset
Set <proy> tipos = platos.stream (). Map (plato :: getType) .collect (coleccions.toset ());
Recopile el tipo como un conjunto, y puede repetirlo.
tope
MAP <type, plato> bytype = plies.stream (). Recolecte (tomap (plato :: getType, d -> d));
A veces puede ser necesario convertir una matriz en un mapa para caché, que facilita múltiples cálculos y adquisiciones. Tomap proporciona las funciones de generación de los métodos K y V
Los anteriores son casi los coleccionistas más utilizados, y básicamente son suficientes. Pero como principiante, la comprensión lleva tiempo. Para comprender realmente por qué esto se puede usar para recolectar, debe verificar la implementación interna. Puede ver que estos coleccionistas se basan en java.util.stream.collectors.collectorImpl, que es una clase de implementación de colección mencionada al principio. El coleccionista personalizado aprenderá el uso específico más adelante.
Reducción personalizada
Los pocos anteriores son casos especiales del proceso de reducción definido por el método de reducción de fábrica. De hecho, los coleccionistas. La reducción se puede usar para crear un coleccionista. Por ejemplo, busca suma
Integer TotalCalories = platos.stream (). Recoger (reducir (0, plato :: getCalories, (i, j) -> i + j))); // use la función incorporada en lugar de la función de flecha entero totalcalories2 = plato.stream (). Recolectar (reducir (0, plato :: getCalories, intregero :: sum));
Por supuesto, también puede usar reducir directamente
Opcional <integer> TotalCalories3 = Dishes.stream (). Map (plato :: getCalories) .reduce (entero :: sum);
Aunque está bien, si considera la eficiencia, aún debe elegir lo siguiente
int Sum = plato.stream (). Maptoint (plato :: getCalories) .sum ();
Elija la mejor solución de acuerdo con la situación
Como se mencionó anteriormente, la programación funcional generalmente proporciona múltiples formas de realizar la misma operación. El uso de coleccionistas es más complejo que el uso de API de transmisión. La ventaja es que Collect puede proporcionar un mayor nivel de abstracción y generalización, y es más fácil de reutilizar y personalizar.
Nuestro consejo es explorar diferentes soluciones al problema en cuestión tanto como sea posible, siempre elegir la más profesional, que generalmente es la mejor decisión en términos de legibilidad y rendimiento.
Además de recibir un valor inicial, la reducción también puede usar el primer elemento como valor inicial
Opcional <hech> MostCaloriedish = plieTes.stream () .Collect (reduciendo ((d1, d2) -> d1.getCalories ()> d2.getCalories ()? D1: d2));
Reductora
El uso de la reducción es bastante complicado, y el objetivo es fusionar dos valores en un valor.
Public Static <t, u> coleccionista <t,?, u> reduciendo (identidad u, función <? Super t, "extiende u> mapper, binaryoperator <u> op)
Primero, vi 3 genéricos.
U es el tipo de valor de retorno. Por ejemplo, el calor calculado en la demostración anterior, U es entero.
En cuanto a T, T es el tipo de elemento en la transmisión. Desde la función de la función, podemos saber que la función de mapper es recibir un parámetro t y luego devolver un resultado U. correspondiente al plato en la demostración.
? En el medio de la lista genérica con el recopilador de valor de retorno, esto representa el tipo de contenedor. Por supuesto, un coleccionista necesita un contenedor para almacenar datos. ¿Aquí? Esto significa que el tipo de contenedor es incierto. De hecho, el contenedor aquí es u [].
Sobre los parámetros:
La identidad es el valor inicial del tipo de valor de retorno, que puede entenderse como el punto de partida del acumulador.
Mapper es la función del mapa, y su importancia radica en la conversión de transmisiones en la secuencia de tipo que desea.
OP es la función central, y su función es cómo lidiar con dos variables. Entre ellos, la primera variable es el valor acumulativo, que puede entenderse como suma, y la segunda variable es el siguiente elemento que se calculará. Por lo tanto, se logra la acumulación.
También hay un método sobrecargado para omitir el primer parámetro, lo que significa que el primer parámetro en la secuencia se usa como valor inicial.
Public static <t> coleccionista <t,?, opcional <t>> Reducir (binaryoperator <t> op)
Veamos la diferencia entre el valor de retorno. T representa el valor de entrada y el tipo de valor de retorno, es decir, el tipo de valor de entrada y el tipo de valor de salida son los mismos. Otra diferencia es opcional. Esto se debe a que no hay valor inicial, y el primer parámetro puede ser nulo. Cuando el elemento de transmisión es nulo, es muy significativo devolver opcional.
Mirando la lista de parámetros, solo queda binario operador. El binaryoperator es una interfaz de función triple, el objetivo es calcular dos parámetros del mismo tipo y valores de retorno del mismo tipo. ¿Se puede entender como 1> 2? 1: 2, es decir, encuentre el valor máximo de dos números. Encontrar el valor máximo es una declaración relativamente fácil de entender. Puede personalizar la expresión de Lambda para seleccionar el valor de retorno. Luego, aquí, es recibir el elemento Tipo T de dos transmisiones y devolver el valor de retorno del tipo T. También está bien usar la suma para comprender.
En la demostración anterior, se encuentra que las funciones de Reded and Collect son casi las mismas, ambas devuelven un resultado final. Por ejemplo, podemos usar el efecto de reducción de tolistas:
//Manually implement toListCollector --- Abuse of reduce, immutable regulations---cannot parallel List<Integer> calories = dishes.stream().map(Dish::getCalories) .reduce(new ArrayList<Integer>(), (List<Integer> l, Integer e) -> { l.add(e); return l; }, (List<Integer> l1, List <integer> l2) -> {l1.addall (l2);Permítanme explicar las prácticas anteriores.
<u> U reduzca (identidad u bifunción <u ,? super t, u> acumulador, binaryoperator <u> combinador);
U es el tipo de valor de retorno, aquí está la lista
Bifunción <u ,? Super t, u> acumulador es un acumulador, y su objetivo es acumular valores y reglas de cálculo para elementos individuales. Aquí está el funcionamiento de la lista y los elementos, y finalmente regresa la lista. Es decir, agregue un elemento a la lista.
El combinador de binaryoperator <u> es un combinador, y el objetivo es fusionar dos variables de tipos de valor de retorno en uno. Aquí está la fusión de dos listas.
Hay dos problemas con esta solución: uno es un problema semántico y el otro es un problema práctico. El problema semántico es que el método reducido tiene como objetivo combinar dos valores para generar un nuevo valor, que es una reducción inmutable. En cambio, el diseño del método de recolección es cambiar el contenedor y acumular los resultados a salir. Esto significa que el fragmento de código anterior está abusando del método de reducción porque cambia la lista como un acumulador en su lugar. La semántica incorrecta para usar el método Reducir también crea un problema práctico: esta reducción no puede funcionar en paralelo, porque la modificación concurrente de la misma estructura de datos por múltiples subprocesos puede destruir la lista en sí. En este caso, si desea la seguridad de los subprocesos, debe asignar una nueva lista a la vez, y la asignación de objetos a su vez afectará el rendimiento. Es por eso que Collect es adecuado para expresar reducciones en contenedores mutables y, lo que es más importante, es adecuado para operaciones paralelas.
Resumen: Reduce es adecuado para la reducción de contenedores inmutables, la recolección es adecuada para la reducción de contenedores mutables. Collect es adecuado para el paralelismo.
Agrupamiento
La base de datos a menudo se encuentra con la necesidad de sumar grupal y proporciona al grupo por primitivo. En Java, si sigue el estilo de instrucción (bucles de escritura manualmente), será muy engorroso y propenso a los errores. Java 8 proporciona soluciones funcionales.
Por ejemplo, agrupe el plato por tipo. Similar al Tomap anterior, pero el valor de agrupación no es un plato, sino una lista.
MAP <escriba, lista <hect>> DishesByType = plies.stream (). Collect (GroupingBy (Dish :: GetType));
aquí
Public static <t, k> coleccionista <t,?, mapa <k, lista <t>> Groupingby (function <? Super t, "extiende k> clasificador)
El clasificador de parámetros es una función, diseñada para recibir un parámetro y convertirlo a otro tipo. La demostración de arriba es convertir el plato del elemento de flujo en tipo de tipo, y luego agrupar la corriente de acuerdo con el tipo. Su agrupación interna se implementa a través de HashMap. Groupingby (clasificador, hashmap :: nuevo, aguas abajo);
Además de agrupar de acuerdo con la función de propiedad del elemento de flujo en sí, también puede personalizar la base de agrupación, como la agrupación de acuerdo con el rango de calor.
Dado que ya sabe que el parámetro de GroupingBy es la función y el tipo de parámetro de función es Dish, puede personalizar el clasificador como:
GetCaloriclevel de calorículo privado (plato d) {if (d.getCalories () <= 400) {return caloriclevel.diet; } else if (d.getCalories () <= 700) {return caloriclevel.normal; } else {return caloriclevel.fat; }}Simplemente pase en los parámetros
MAP <Caloriclevel, List <Ent>> DishesByLevel = Dishes.stream () .Collect (GroupingBy (this :: getCaloriclevel));
Agrupación multinivel
Groupingby también sobrecarga varios otros métodos, como
Public Static <t, K, A, D> Collector <t,?, Map <K, D >> Groupingby (function <? Super t, "Extends K> classificador, coleccionista <? Super t, a, d> aguas abajo)
Hay muchos genéricos y horrores. Vamos a tener una breve comprensión. Un clasificador también es un clasificador, que recibe el tipo de elemento de la transmisión y devuelve una base para la cual desea agrupar, es decir, la cardinalidad de la base de agrupación. Entonces T representa el tipo de elemento actual de la secuencia, y K representa el tipo de elemento de la agrupación. El segundo parámetro es aguas abajo, y el río abajo es un coleccionista de colección. Este tipo de elemento colector es una subclase de T, el contenedor de tipo de contenedor es A, y el tipo de valor de retorno de reducción es D. es decir, la k del grupo se proporciona a través del clasificador, y el valor del grupo se reduce a través del colector del segundo parámetro. Sucede que el código fuente de la demostración anterior es:
public static <t, k> coleccionista <t,?, map <k, list <t>> GroupingBy (function <? Super t, "extiende k> clasificador) {return groupingby (classificador, tolist ()); }Use tolista como el coleccionista Redy, y el resultado final es una lista <nec>, por lo que el tipo de valor del grupo finaliza es la lista <sec>. Luego, el tipo de valor puede determinarse de manera análoga por la reducción del colector, y hay decenas de millones de recolectores de reducción. Por ejemplo, quiero agrupar el valor nuevamente, y la agrupación también es un tipo de reducción.
// mapa de agrupación de niveles múltiples <tipo, mapa <caloriclevel, list <hect >>> bytypeAndCalory = plato.stream (). Coleccionar (groupingby (plato :: getType, groupingBy (this :: getCaloricle)); System.out.println("------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- System.out.println ("/t" + nivel);Los resultados de la verificación son:
--------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------- [Plato (nombre = carne de res, vegetariano = falso, calorías = 700, Tipo = carne)] -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------- tipo = otro)]
Resumen: Los parámetros centrales de Groupingby son los generadores K y los generadores V. El generador V puede ser cualquier tipo de colector de colección.
Por ejemplo, el generador V puede calcular el número, implementando así el recuento de selección (*) en la instrucción SQL de la tabla un grupo por tipo
Map <type, long> typescount = plato.stream (). Collect (GroupingBy (Dish :: GetType, Counting ()));
SQL Search Group select MAX(id) from table A group by Type
MAP <tipo, opcional < -plato >> MostCaloricByType = plato.stream () .Collect (GroupingBy (plato :: getType, maxby (comparador.comparingint (plato :: getCalories)))));
Opcional aquí no tiene sentido, porque ciertamente no es nulo. Entonces tuve que sacarlo. Usando coleccionar y
Map <type, plato> MostCaloricByType = plieTes.stream () .Collect (GroupingBy (plato :: getType, coleccionando y Then (maxby (comparador.comparingint (plato: getCalories)), opcional :: get)));
Parece que el resultado sale aquí, pero la idea no está de acuerdo. Compila la alarma amarilla y la cambia a:
MAP <type, plato> MostCaloricByType = plies.stream () .Collect (tomap (plato :: getType, function.Identity (), binaryoperator.maxby (comparando una entrada (plato :: getCalories)))));
Sí, Groupingby se convierte en tomap, la clave sigue siendo tipo, el valor sigue siendo plato, ¡pero hay un parámetro más! ! Aquí respondemos al pozo al principio. La demostración de Tomap al principio es para una fácil comprensión. Si realmente se usa, será asesinado. Sabemos que reorganizar una lista en un mapa inevitablemente enfrentará el mismo problema. Cuando K es lo mismo, ¿V lo anula o lo ignora? El método de demostración anterior es insertar K nuevamente y lanzar una excepción directamente cuando K esté presente:
java.lang.illegalstateException: plato de clave duplicada (name = cerdo, vegetal = falso, calorías = 800, type = carne) en java.util.stream.collectores.lambda $ throwingmerger $ 0 (coleccionistas.java:133)
La forma correcta es proporcionar funciones para manejar conflictos. En esta demostración, el principio de manejo de conflictos es encontrar el más grande, que solo cumple con nuestros requisitos para agrupar y encontrar el más grande. (Realmente no quiero hacer el aprendizaje funcional Java 8, siento que hay dificultades de problemas de rendimiento en todas partes)
Continuar con la asignación de sql de la base de datos, select sum(score) from table a group by Type
Map <type, integer> totalcaloriesbytype = plato.stream () .collect (groupingby (plato :: getType, sumingint (plato :: getCalories)));
Sin embargo, el método de mapeo genera otro coleccionista que se usa a menudo junto con GroupingBy. Este método recibe dos parámetros: una función transforma elementos en la secuencia, y el otro recopila los objetos de resultados transformados. El propósito es aplicar una función de mapeo a cada elemento de entrada antes de la acumulación, de modo que el colector que reciba elementos de un tipo particular puede adaptarse a diferentes tipos de objetos. Permítanme ver un ejemplo práctico de usar este coleccionista. Por ejemplo, desea obtener qué válvulas caloriculares hay en el menú para cada tipo de plato. Podemos combinar los coleccionistas de Groupingby y Mapping de la siguiente manera:
MAP <tipo, establecer <Caloriclevel>> CaloricleVelsByType = plieTes.stream () .Collect (GroupingBy (Dish :: GetType, Mapping (este :: GetCaloricel, toset ())));
El TOSET aquí usa el hashset de forma predeterminada, y también puede especificar manualmente la implementación específica ToCollection (Hashset :: nueva)
Dividir
La partición es un caso especial de agrupación: un predicado (una función que devuelve un valor booleano) se usa como una función de clasificación, que se denomina función de partición. La función de partición devuelve un valor booleano, lo que significa que el tipo clave del mapa agrupado es booleano, por lo que se puede dividir en hasta dos grupos: verdadero o falso. Por ejemplo, si usted es vegetariano, es posible que desee separar el menú de vegetariano y no vegetariano:
MAP <boolean, list <hect>> particionedmenu = plieTes.stream (). Recolectar (particioningby (plato :: isvegegetarian));
Por supuesto, el uso del filtro puede lograr el mismo efecto:
List <ec> vegetariandishes = platos.stream (). Filtro (plato :: isvegetarian) .collect (coleccionadors.tolist ());
La ventaja de la partición es guardar dos copias, lo cual es útil cuando desea clasificar una lista. Al mismo tiempo, como Groupingby, PartitioningBy tiene métodos sobrecargados, que pueden especificar el tipo de valor de agrupación.
Mapa <boolean, map <escriba, lista <hect >>> vegetariandishesbytype = plies.stream () .Collect (PartitioningBy (Dish :: Isvegegetarian, GroupingBy (Dish :: GetType))); Map <Boolean, Integer> VegetarianDothalories = Dishes.stream () .Collect (PartitioningBy (plato: plato:: VegetarianDishalorías. sumingint (plato :: getCalories))); mapa <boolean, plato> MostCaloricPartitionedByVegegetarian = platos.stream () .Collect (PartitioningBy (plato :: isvegeganteian, coleccionando y Then (Maxby (Comparación (plato :: getCalories)), opcional :: get)));
Como el último ejemplo de usar el recopilador PartitioningBy, dejamos de lado el modelo de datos del menú para ver un ejemplo más complejo e interesante: dividir las matrices en números prime y no predominantes.
Primero, defina una función de partición principal:
privado booleano isprime (int candidato) {int candidaterooT = (int) math.sqrt ((doble) candidato); return intstream.rangeclosed (2, candidateroot) .nonematch (i -> candidato % i == 0);}Luego encuentre los números prime y no predominales de 1 a 100
Map <boolean, list <integer>> partitionPrimes = intstream.rangecLosed (2, 100) .Boxed () .Collect (PartitioningBy (this :: isPrime));