Pergunta

Eu gostaria de aplicar uma função para uma coleção Java, neste caso particular um mapa. Existe uma boa maneira de fazer isso? Eu tenho um mapa e gostaria de guarnição apenas run () em todos os valores no mapa e ter o mapa refletir as atualizações.

Foi útil?

Solução

Com lambdas de Java 8, este é um um forro:

map.replaceAll((k, v) -> v.trim());

Por uma questão de história, aqui está uma versão sem 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());
  }
}

Ou, de modo mais geral:

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();
  }
});

Outras dicas

Eu não sei de uma maneira de fazer isso com o JDK bibliotecas que não seja a sua resposta aceita, no entanto Google Collections permite que você faça a coisa seguinte, com as classes com.google.collect.Maps e 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;
  }
}

A maior diferença desse método com o proposto é que ele fornece uma visão para o mapa original, o que significa que, embora seja sempre em sincronia com o seu mapa original, o método apply poderia ser invocado muitas vezes se você estiver manipular o referido mapa fortemente.

Existe um método Collections2.transform(Collection<F>,Function<F,T>) semelhante para coleções.

Se você pode modificar sua coleção no local ou não depende da classe dos objetos na coleção.

Se esses objetos são imutáveis ??(que Strings são), então você não pode simplesmente pegar os itens da coleção e modificá-los - em vez disso você vai precisar iterar sobre a coleção, chamar a função relevante e, em seguida, colocar o resultante valor de volta.

pode ser um exagero para algo como isso, mas há um número de realmente bons utilitários para esses tipos de problemas na Apache Commons Collections biblioteca.

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"));

Eu recomendo o Jakarta Commons Cookbook de O'Reilly.

Acabei usando uma mutação de resposta de @ Erickson, mutado para:

  • retornar um novo Collection, não modificar no lugar
  • Collections retorno com elementos do tipo igual ao tipo de retorno da Function
  • suporte de mapeamento sobre qualquer dos valores de um mapa ou os elementos de uma 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;
}

Você vai ter que iterar sobre todas as entradas e aparar cada valor de cadeia. Desde String é imutável você vai ter que re-colocá-lo no mapa. Uma abordagem melhor seria para aparar os valores que eles forem colocados no mapa.

Eu vim com uma classe "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 eu usar assim:

Collection<Loader> loaders = new Mapper<File, Loader>(files) {
    @Override public Loader map(File source) {
        return new Loader(source);
    }           
}.apply();

Você também pode dar uma olhada no Google Collections

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top