Comment joindre deux listes en Java?


Conditions: ne modifiez pas les listes originales; JDK uniquement, pas de bibliothèques externes. Points bonus pour un one-liner ou une version JDK 1.3.

Y a-t-il un moyen plus simple que:

List<String> newList = new ArrayList<String>();
newList.addAll(listOne);
newList.addAll(listTwo);



Answers


Au sommet de ma tête, je peux le raccourcir d'une ligne:

List<String> newList = new ArrayList<String>(listOne);
newList.addAll(listTwo);



En Java 8:

List<String> newList = Stream.concat(listOne.stream(), listTwo.stream())
                             .collect(Collectors.toList());






Probablement pas plus simple, mais intrigant et moche:

List<String> newList = new ArrayList<String>() { { addAll(listOne); addAll(listTwo); } };

Ne l'utilisez pas dans le code de production ...;)




Une de vos exigences est de préserver les listes originales. Si vous créez une nouvelle liste et utilisez addAll() , vous addAll() le nombre de références aux objets dans vos listes. Cela peut entraîner des problèmes de mémoire si vos listes sont très volumineuses.

Si vous n'avez pas besoin de modifier le résultat concaténé, vous pouvez éviter cela en utilisant une implémentation de liste personnalisée. La classe d'implémentation personnalisée est plus d'une ligne, évidemment ... mais l'utiliser est court et doux.

CompositeUnmodifiableList.java:

public class CompositeUnmodifiableList<E> extends AbstractList<E> {

    private final List<E> list1;
    private final List<E> list2;

    public CompositeUnmodifiableList(List<E> list1, List<E> list2) {
        this.list1 = list1;
        this.list2 = list2;
    }

    @Override
    public E get(int index) {
        if (index < list1.size()) {
            return list1.get(index);
        }
        return list2.get(index-list1.size());
    }

    @Override
    public int size() {
        return list1.size() + list2.size();
    }
}

Usage:

List<String> newList = new CompositeUnmodifiableList<String>(listOne,listTwo);



Pas plus simple, mais sans redimensionnement:

List<String> newList = new ArrayList<>(listOne.size() + listTwo.size());
newList.addAll(listOne);
newList.addAll(listTwo);



Trouvé cette question cherchant à concaténer le nombre arbitraire de listes, ne faisant pas attention aux bibliothèques externes. Alors, peut-être que cela aidera quelqu'un d'autre:

com.google.common.collect.Iterables#concat()

Utile si vous voulez appliquer la même logique à un certain nombre de collections différentes dans un pour ().




Un autre Java 8 one-liner:

List<String> newList = Stream.of(listOne, listTwo)
                             .flatMap(x -> x.stream())
                             .collect(Collectors.toList());

En prime, puisque Stream.of() est variadique, vous pouvez concaténer autant de listes que vous le souhaitez.

List<String> newList = Stream.of(listOne, listTwo, listThree)
                             .flatMap(x -> x.stream())
                             .collect(Collectors.toList());



C'est simple et juste une ligne, mais ajoutera le contenu de listTwo à listOne. Avez-vous vraiment besoin de mettre le contenu dans une troisième liste?

Collections.addAll(listOne, listTwo.toArray());



Un peu plus court serait:

List<String> newList = new ArrayList<String>(listOne);
newList.addAll(listTwo);



Voici une solution java 8 utilisant deux lignes:

List<Object> newList = new ArrayList<>();
Stream.of(list1, list2).forEach(newList::addAll);

Sachez que cette méthode ne doit pas être utilisée si

  • l'origine de newList n'est pas connue et elle peut déjà être partagée avec d'autres threads
  • le flux qui modifie newList est un flux parallèle et l'accès à newList n'est pas synchronisé ou threadsafe

en raison de considérations d'effets secondaires.

Les deux conditions ci-dessus ne s'appliquent pas pour le cas ci-dessus de joindre deux listes, donc c'est sûr.

Basé sur cette réponse à une autre question.




Un peu plus simple:

List<String> newList = new ArrayList<String>(listOne);
newList.addAll(listTwo);



Vous pouvez faire un oneliner si la liste cible est prédéclarée.

(newList = new ArrayList<String>(list1)).addAll(list2);



La solution proposée concerne trois listes, bien qu'elle puisse également être appliquée à deux listes. En Java 8, nous pouvons utiliser Stream.of ou Stream.concat comme:

List<String> result1 = Stream.concat(Stream.concat(list1.stream(),list2.stream()),list3.stream()).collect(Collectors.toList());
List<String> result2 = Stream.of(list1,list2,list3).flatMap(Collection::stream).collect(Collectors.toList());

Stream.concat prend deux flux en entrée et crée un flux paresseusement concaténé dont les éléments sont tous les éléments du premier flux suivi de tous les éléments du deuxième flux. Comme nous avons trois listes, nous avons utilisé cette méthode deux fois ( Stream.concat ).

Nous pouvons aussi écrire une classe utilitaire (disons StringUtils) avec une méthode qui prend n'importe quel nombre de listes (en utilisant varargs ) et retourne une liste concaténée comme:

public static <T> List<T> concatenatedList(List<T>... collections) {
        return Arrays.stream(collections).flatMap(Collection::stream).collect(Collectors.toList()); 
}

Ensuite, nous pouvons utiliser cette méthode comme:

List<String> result3 = StringUtils.concatenatedList(list1,list2,list3);



En Java 8 (dans l'autre sens):

List<?> newList = 
Stream.of(list1, list2).flatMap(List::stream).collect(Collectors.toList());



une autre solution de ligne utilisant le flux Java8 , puisque la solution flatMap est déjà postée, voici une solution sans flatMap

List<E> li = lol.stream().collect(ArrayList::new, List::addAll, List::addAll);

ou

List<E> ints = Stream.of(list1, list2).collect(ArrayList::new, List::addAll, List::addAll);

code

    List<List<Integer>> lol = Arrays.asList(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6));
    List<Integer> li = lol.stream().collect(ArrayList::new, List::addAll, List::addAll);
    System.out.println(lol);
    System.out.println(li);

sortie

[[1, 2, 3], [4, 5, 6]]
[1, 2, 3, 4, 5, 6]



Le plus intelligent à mon avis:

/**
 * @param smallLists
 * @return one big list containing all elements of the small ones, in the same order.
 */
public static <E> List<E> concatenate (final List<E> ... smallLists)
{
    final ArrayList<E> bigList = new ArrayList<E>();
    for (final List<E> list: smallLists)
    {
        bigList.addAll(list);
    }
    return bigList;
}



Version Java 8 avec prise en charge de la jointure par clé d'objet:

public List<SomeClass> mergeLists(final List<SomeClass> left, final List<SomeClass> right, String primaryKey) {
    final Map<Object, SomeClass> mergedList = new LinkedHashMap<>();

    Stream.concat(left.stream(), right.stream())
        .map(someObject -> new Pair<Object, SomeClass>(someObject.getSomeKey(), someObject))
        .forEach(pair-> mergedList.put(pair.getKey(), pair.getValue()));

    return new ArrayList<>(mergedList.values());
}



Vous pouvez le faire avec une importation statique et une classe d'aide

nb la génération de cette classe pourrait probablement être améliorée

public class Lists {

   private Lists() { } // can't be instantiated

   public static List<T> join(List<T>... lists) {
      List<T> result = new ArrayList<T>();
      for(List<T> list : lists) {
         result.addAll(list);
      }
      return results;
   }

}

Ensuite, vous pouvez faire des choses comme

import static Lists.join;
List<T> result = join(list1, list2, list3, list4);



public static <T> List<T> merge(List<T>... args) {
    final List<T> result = new ArrayList<>();

    for (List<T> list : args) {
        result.addAll(list);
    }

    return result;
}



Utilisez une classe Helper.

Je suggère:

public static <E> Collection<E> addAll(Collection<E> dest, Collection<? extends E>... src) {
    for(Collection<? extends E> c : src) {
        dest.addAll(c);
    }

    return dest;
}

public static void main(String[] args) {
    System.out.println(addAll(new ArrayList<Object>(), Arrays.asList(1,2,3), Arrays.asList("a", "b", "c")));

    // does not compile
    // System.out.println(addAll(new ArrayList<Integer>(), Arrays.asList(1,2,3), Arrays.asList("a", "b", "c")));

    System.out.println(addAll(new ArrayList<Integer>(), Arrays.asList(1,2,3), Arrays.asList(4, 5, 6)));
}



Je ne prétends pas que c'est simple, mais vous avez mentionné le bonus pour les one-liners ;-)

Collection mergedList = Collections.list(new sun.misc.CompoundEnumeration(new Enumeration[] {
    new Vector(list1).elements(),
    new Vector(list2).elements(),
    ...
}))



Pas très près du one-liner, mais je pense que c'est le plus simple:

List<String> newList = new ArrayList<String>(l1);
newList.addAll(l2);

for(String w:newList)
        System.out.printf("%s ", w);



Voici une approche utilisant streams et java 8 si vos listes ont des types différents et que vous voulez les combiner à une liste d'un autre type.

public static void main(String[] args) {
    List<String> list2 = new ArrayList<>();
    List<Pair<Integer, String>> list1 = new ArrayList<>();

    list2.add("asd");
    list2.add("asdaf");
    list1.add(new Pair<>(1, "werwe"));
    list1.add(new Pair<>(2, "tyutyu"));

    Stream stream = Stream.concat(list1.stream(), list2.stream());

    List<Pair<Integer, String>> res = (List<Pair<Integer, String>>) stream
            .map(item -> {
                if (item instanceof String) {
                    return new Pair<>(0, item);
                }
                else {
                    return new Pair<>(((Pair<Integer, String>)item).getKey(), ((Pair<Integer, String>)item).getValue());
                }
            })
            .collect(Collectors.toList());
}



List<?> newList = ListUtils.combine(list1, list2);

Oh, vous devez implémenter la méthode de combine :-)




public class TestApp {

/**
 * @param args
 */
public static void main(String[] args) {
    System.out.println("Hi");
    Set<List<String>> bcOwnersList = new HashSet<List<String>>();
    List<String> bclist = new ArrayList<String>();
    List<String> bclist1 = new ArrayList<String>();
    List<String> object = new ArrayList<String>();
    object.add("BC11");
    object.add("C2");
    bclist.add("BC1");
    bclist.add("BC2");
    bclist.add("BC3");
    bclist.add("BC4");
    bclist.add("BC5");
    bcOwnersList.add(bclist);
    bcOwnersList.add(object);

    bclist1.add("BC11");
    bclist1.add("BC21");
    bclist1.add("BC31");
    bclist1.add("BC4");
    bclist1.add("BC5");

    List<String> listList= new ArrayList<String>();
    for(List<String> ll : bcOwnersList){
        listList = (List<String>) CollectionUtils.union(listList,CollectionUtils.intersection(ll, bclist1));
    }
    /*for(List<String> lists : listList){
        test = (List<String>) CollectionUtils.union(test, listList);
    }*/
    for(Object l : listList){
        System.out.println(l.toString());
    }
    System.out.println(bclist.contains("BC"));

}

}



Je ne peux pas améliorer le deux lignes dans le cas général sans introduire votre propre méthode d'utilitaire, mais si vous avez des listes de chaînes et que vous êtes prêt à supposer que ces chaînes ne contiennent pas de virgules, vous pouvez en extraire -doublure:

List<String> newList = new ArrayList<String>(Arrays.asList((listOne.toString().subString(1, listOne.length() - 1) + ", " + listTwo.toString().subString(1, listTwo.length() - 1)).split(", ")));

Si vous déposez les génériques, cela devrait être compatible avec le JDK 1.4 (bien que je ne l'ai pas testé). Également non recommandé pour le code de production ;-)