¿Método de aplicación de mapa/colección Java equivalente?
-
09-09-2019 - |
Pregunta
Me gustaría aplicar una función a una colección de Java, en este caso particular un mapa.¿Existe una buena manera de hacer esto?Tengo un mapa y me gustaría simplemente ejecutar trim() en todos los valores del mapa y que el mapa refleje las actualizaciones.
Solución
Con lambdas de Java 8, este es un un trazador de líneas:
map.replaceAll((k, v) -> v.trim());
En aras de la historia, aquí es una versión sin lambdas:
public void trimValues(Map<?, String> map) {
for (Map.Entry<?, String> e : map.entrySet()) {
String val = e.getValue();
if (val != null)
e.setValue(val.trim());
}
}
O, en términos más generales:
interface Function<T> {
T operate(T val);
}
public static <T> void replaceValues(Map<?, T> map, Function<T> f)
{
for (Map.Entry<?, T> e : map.entrySet())
e.setValue(f.operate(e.getValue()));
}
Util.replaceValues(myMap, new Function<String>() {
public String operate(String val)
{
return (val == null) ? null : val.trim();
}
});
Otros consejos
No sé una manera de hacer eso con el JDK bibliotecas que no sea su respuesta aceptada, sin embargo Google Colecciones le permite hacer lo siguiente, con el com.google.collect.Maps
clases y com.google.common.base.Function
:
Map<?,String> trimmedMap = Maps.transformValues(untrimmedMap, new Function<String, String>() {
public String apply(String from) {
if (from != null)
return from.trim();
return null;
}
}
La mayor diferencia de este método con el que se propone es que proporciona una vista a su mapa original, lo que significa que, si bien es siempre sincronizado con el mapa original, el método apply
podría ser invocada muchas veces si usted está manipular dicho mapa en gran medida.
Existe un método Collections2.transform(Collection<F>,Function<F,T>)
similar para colecciones.
Si usted puede modificar su recogida en el lugar o no depende de la clase de los objetos de la colección.
Si los objetos son inmutables (que las cadenas son) entonces no se puede simplemente tomar los elementos de la colección y modificarlos - en lugar que necesita para iterar sobre la colección, llama a la función correspondiente, y luego poner la resultante volver valor.
podría ser excesivo para algo como esto, pero hay una serie de muy buenas utilidades para este tipo de problemas en el biblioteca Apache Commons Colecciones .
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "a ");
map.put("key2", " b ");
map.put("key3", " c");
TransformedMap.decorateTransform(map,
TransformerUtils.nopTransformer(),
TransformerUtils.invokerTransformer("trim"));
Lo recomiendo altamente el Jakarta Commons Cookbook de O'Reilly.
Terminé usando una mutación de la respuesta de @erickson, mutada a:
- devolver un nuevo
Collection
, no modificar en su lugar - devolver
Collection
s con elementos de tipo igual al tipo de retorno delFunction
- soporte de mapeo sobre los valores de un mapa o los elementos de una lista
Código:
public static interface Function<L, R> {
L operate(R val);
}
public static <K, L, R> Map<K, L> map(Map<K, R> map, Function<L, R> f) {
Map<K, L> retMap = new HashMap<K, L>();
for (Map.Entry<K, R> e : map.entrySet()) retMap.put(e.getKey(), f.operate(e.getValue()));
return retMap;
}
public static <L, R> List<L> map(List<R> list, Function<L, R> f) {
List<L> retList = new ArrayList<L>();
for (R e : list) retList.add(f.operate(e));
return retList;
}
Vas a tener que recorrer en iteración todas las entradas y recortar cada valor de cadena. Desde cuerdas es inmutable que tendrá que volver a ponerlo en el mapa. Un mejor enfoque podría ser la de recortar los valores a medida que se colocan en el mapa.
He llegado con una clase "Mapper"
public static abstract class Mapper<FromClass, ToClass> {
private Collection<FromClass> source;
// Mapping methods
public abstract ToClass map(FromClass source);
// Constructors
public Mapper(Collection<FromClass> source) {
this.source = source;
}
public Mapper(FromClass ... source) {
this.source = Arrays.asList(source);
}
// Apply map on every item
public Collection<ToClass> apply() {
ArrayList<ToClass> result = new ArrayList<ToClass>();
for (FromClass item : this.source) {
result.add(this.map(item));
}
return result;
}
}
Que utilizo así:
Collection<Loader> loaders = new Mapper<File, Loader>(files) {
@Override public Loader map(File source) {
return new Loader(source);
}
}.apply();
También puede echar un vistazo a Google Colecciones