web-dev-qa-db-fra.com

Java - mappe une liste d'objets à une liste avec les valeurs de leurs attributs de propriété

J'ai la classe ViewValue définie comme suit:

class ViewValue {

private Long id;
private Integer value;
private String description;
private View view;
private Double defaultFeeRate;

// getters and setters for all properties
}

Quelque part dans mon code, j'ai besoin de convertir une liste d'instances ViewValue en une liste contenant les valeurs des champs id de ViewValue correspondantes.

Je le fais en utilisant la boucle foreach:

List<Long> toIdsList(List<ViewValue> viewValues) {

   List<Long> ids = new ArrayList<Long>();

   for (ViewValue viewValue : viewValues) {
      ids.add(viewValue.getId());
   }

   return ids;

}

Existe-t-il une meilleure approche de ce problème?

33
mgamer

EDIT: Cette réponse est basée sur l'idée que vous devrez faire des choses similaires pour différentes entités et différentes propriétés ailleurs dans votre code. Si vous seulement devez convertir la liste des ViewValues ​​en une liste de Longs par ID, alors restez avec votre code d'origine. Si vous voulez une solution plus réutilisable, lisez la suite ...

Je déclarerais une interface pour la projection, par exemple.

public interface Function<Arg,Result>
{
    public Result apply(Arg arg);
}

Ensuite, vous pouvez écrire une seule méthode de conversion générique:

public <Source, Result> List<Result> convertAll(List<Source> source,
    Function<Source, Result> projection)
{
    ArrayList<Result> results = new ArrayList<Result>();
    for (Source element : source)
    {
         results.add(projection.apply(element));
    }
    return results;
}

Ensuite, vous pouvez définir des projections simples comme celle-ci:

private static final Function<ViewValue, Long> ID_PROJECTION =
    new Function<ViewValue, Long>()
    {
        public Long apply(ViewValue x)
        {
            return x.getId();
        }
    };

Et appliquez-le comme ceci:

List<Long> ids = convertAll(values, ID_PROJECTION);

(Évidemment, l'utilisation de contreventements K&R et de lignes plus longues rend la déclaration de projection un peu plus courte :)

Franchement, tout cela serait beaucoup plus agréable avec les expressions lambda, mais tant pis ...

27
Jon Skeet

Vous pouvez le faire dans une seule ligne en utilisant Commons BeanUtils and Collections:
(pourquoi écrire votre propre code quand d'autres l'ont fait pour vous?)

import org.Apache.commons.beanutils.BeanToPropertyValueTransformer;
import org.Apache.commons.collections.CollectionUtils;

...

List<Long> ids = (List<Long>) CollectionUtils.collect(viewValues, 
                                       new BeanToPropertyValueTransformer("id"));
33
Ulf Lindback

Nous pouvons le faire en une seule ligne de code en utilisant Java 8

List<Long> ids = viewValues.stream().map(ViewValue::getId).collect(Collectors.toList());

Pour plus d'informations: Java 8 - Streams

Utilisez les collections Google. Exemple:

    Function<ViewValue, Long> transform = new Function<ViewValue, Long>() {
        @Override
        public Long apply(ViewValue from) {
            return from.getId();
        }
    };
    List<ViewValue> list = Lists.newArrayList();
    List<Long> idsList = Lists.transform(list, transform);

MISE À JOUR:

Sur Java 8 vous n'avez pas besoin de goyave. Vous pouvez:

import com.example.ViewValue;
import Java.util.ArrayList;
import Java.util.List;
import Java.util.function.Function;
import Java.util.stream.Collectors;

Function<ViewValue, Long> transform = ViewValue::getId;
List<ViewValue> source = new ArrayList<>();
List<Long> result = source.stream().map(transform).collect(Collectors.toList());

Ou juste:

List<ViewValue> source= new ArrayList<>();
List<Long> result = source.stream().map(ViewValue::getId).collect(Collectors.toList());

MISE À JOUR SUIVANTE (La dernière après le changement de nom de Javaslang en Vavr):

Actuellement, il convient de mentionner la solution avec Bibliothèque Javaslang ( http://www.javaslang.io/ ) Bibliothèque Vavr ( http://www.vavr.io/ ). Supposons que nous ayons notre liste d'objets authentiques:

List<ViewValue> source = newArrayList(new ViewValue(1), new ViewValue(2), new ViewValue(2));

Nous pourrions faire une transformation avec la classe List de la bibliothèque Javaslang (à long terme, la collecte n'est pas pratique):

List<Long> result = io.vavr.collection.List.ofAll(source).map(ViewValue::getId).toJavaList();

Mais vous verrez la puissance avec seulement les listes Javaslang:

io.vavr.collection.List<ViewValue> source = javaslang.collection.List.of(new ViewValue(1), new ViewValue(2), new ViewValue(3));
io.vavr.collection.List<Long> res = source.map(ViewValue::getId);

J'encourage à jeter un œil aux collections disponibles et aux nouveaux types sur cette bibliothèque (j'aime particulièrement le type Try). Vous trouverez la documentation à l'adresse suivante: http://www.javaslang.io/javaslang-docs/ http://www.vavr.io/vavr-docs/ .

PS. En raison de l'Oracle et du mot "Java" dans le nom, ils ont dû changer le nom de la bibliothèque de javaslang en autre chose. Ils avaient décidé de Vavr.

29
Przemek Nowak

J'ai implémenté une petite bibliothèque fonctionnelle pour ce cas d'utilisation. L'une des méthodes a cette signature:

<T> List<T> mapToProperty(List<?> objectList, String property, Class<T> returnType)

Qui prend la chaîne et utilise la réflexion pour créer un appel à la propriété, puis il renvoie une liste soutenue par la objectList où get et l'itérateur sont implémentés à l'aide de cet appel de propriété.

Les fonctions mapToProperty sont implémentées en termes de fonction de carte générale qui prend une fonction comme mappeur, tout comme un autre article décrit. Très utile.

Je vous suggère de lire sur la programmation fonctionnelle de base et en particulier de jeter un œil aux Functors (objets implémentant une fonction de carte)

Edit: La réflexion ne doit vraiment pas être chère. La JVM s'est beaucoup améliorée dans ce domaine. Assurez-vous simplement de compiler l'invocation une fois et de la réutiliser.

Edit2: Exemple de code

public class MapExample {
    public static interface Function<A,R>
    {
        public R apply(A b);
    }

    public static <A,R> Function<A,R> compilePropertyMapper(Class<A> objectType, String property, Class<R> propertyType)
    {
        try {
            final Method m = objectType.getMethod("get" + property.substring(0,1).toUpperCase() + property.substring(1));

            if(!propertyType.isAssignableFrom(m.getReturnType()))
                throw new IllegalArgumentException(
                    "Property "+property+" on class "+objectType.getSimpleName()+" is not a "+propertyType.getSimpleName()
                );

            return new Function<A,R>() 
            {
                @SuppressWarnings("unchecked")
                public R apply(A b)
                {
                    try {
                        return (R)m.invoke(b);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                };
            };

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static <T1,T2> List<T2> map(final List<T1> list, final Function<T1,T2> mapper)
    {
        return new AbstractList<T2>()
        {
            @Override
            public T2 get(int index) {
                return mapper.apply(list.get(index));
            }

            @Override
            public int size() {
                return list.size();
            }
        };
    }

    @SuppressWarnings("unchecked")
    public static <T1,T2> List<T2> mapToProperty(List<T1> list, String property, Class<T2> propertyType)
    {
        if(list == null)
            return null;
        else if(list.isEmpty())
            return Collections.emptyList();

        return map(list,compilePropertyMapper((Class<T1>)list.get(0).getClass(), property, propertyType));
    }
}
4
John Nilsson

Vous pouvez utiliser un wrapper:

public class IdList impements List<Long>
{
    private List<ViewValue> underlying;

    pubic IdList(List<ViewValue> underying)
    {
        this.underlying = underying;
    }

    public Long get(int index)
    {
        return underlying.get(index).getId()
    }

    // other List methods
}

bien que ce travail soit encore plus fastidieux, il pourrait améliorer les performances.

Vous pouvez également implémenter votre et ma solution de manière générale en utilisant la réflexion, mais cela serait très mauvais pour la perforation.

Theres aucune solution générique courte et facile en Java, j'ai peur. Dans Groovy, vous utiliseriez simplement collect (), mais je pense que cela implique également la réflexion.

3
Michael Borgwardt

Cela dépend de ce que vous faites ensuite avec le List<Long> Et le List<ViewValue>

Par exemple, vous pouvez obtenir des fonctionnalités suffisantes en créant votre propre implémentation de liste qui encapsule un List<ViewValue>, En implémentant iterator() avec une implémentation d'itérateur qui itère sur les ViewValues, en retournant l'id.

2
Stephen Denne

Vous pouvez remplir une carte à partir des propriétés d'une liste d'objets (par exemple, id comme clé et une propriété comme valeur) comme ci-dessous

Map<String, Integer> mapCount = list.stream().collect(Collectors.toMap(Object::get_id, Object::proprty));
0
Pravin