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.

¿Fue útil?

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 Collections con elementos de tipo igual al tipo de retorno del Function
  • 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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top