Как мне объединить два списка в Java?

Условия: не изменять первоначальные списки; Только JDK, без внешних библиотек. Бонусные баллы за однострочную версию или версию JDK 1.3.

Есть ли более простой способ, чем:

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

35 ответов

В Java 8:

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

Вне головы я могу сократить его на одну строку:

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

Вы можете использовать библиотеку Apache commons-collection:

List<String> newList = ListUtils.union(list1, list2);

Еще одна однострочная версия Java 8:

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

В качестве бонуса, так как Stream.of() Вы можете объединить столько списков, сколько захотите.

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

Одно из ваших требований - сохранить оригинальные списки. Если вы создаете новый список и используете addAll()вы фактически удваиваете количество ссылок на объекты в ваших списках. Это может привести к проблемам с памятью, если ваши списки очень большие.

Если вам не нужно изменять объединенный результат, вы можете избежать этого, используя собственную реализацию списка. Очевидно, что пользовательский класс реализации состоит из нескольких строк, но его использование короткое и приятное.

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

Использование:

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

Наверное, не проще, но интригующе и некрасиво

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

Не используйте его в производственном коде...;)

Не проще, но без изменения размера накладных расходов:

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

Нашел этот вопрос, стремясь объединить произвольное количество списков, не обращая внимания на внешние библиотеки. Так что, возможно, это поможет кому-то еще:

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

Полезно, если вы хотите применить одну и ту же логику к нескольким различным коллекциям в одной for().

Предлагаемое решение предназначено для трех списков, хотя оно может применяться и для двух списков. В Java 8 мы можем использовать Stream.of или Stream.concat как:

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 принимает два потока в качестве входных данных и создает ленивый каскадный поток, элементами которого являются все элементы первого потока, за которыми следуют все элементы второго потока. Поскольку у нас есть три списка, мы использовали этот метод (Stream.concat) два раза.

Мы также можем написать служебный класс с методом, который принимает любое количество списков (используя varargs) и возвращает объединенный список в виде:

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

Тогда мы можем использовать этот метод как:

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

Вот решение Java 8 с использованием двух строк:

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

Помните, что этот метод не следует использовать, если

  • происхождение newList неизвестно и может уже использоваться совместно с другими потоками
  • поток, который изменяет newList это параллельный поток и доступ к newList не синхронизирован или не безопасен

из-за побочных эффектов.

Оба вышеперечисленных условия не применяются для вышеуказанного случая объединения двух списков, так что это безопасно.

На основании этого ответа на другой вопрос.

Это просто и всего одна строка, но добавит содержимое listTwo в listOne. Вы действительно должны поместить содержимое в третий список?

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

Немного проще:

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

Немного короче будет:

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

Вы можете создать свой универсальный метод утилит Java 8 для объединения любого количества списков.

@SafeVarargs
public static <T> List<T> concat(List<T>... lists) {
    return Stream.of(lists).flatMap(List::stream).collect(Collectors.toList());
}

В Java 8 (другой способ):

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

Вы можете сделать oneliner, если целевой список предварительно объявлен.

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

Еще одно решение лайнера с использованием Java8 поток, так как flatMap решение уже выложено, вот решение без flatMap

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

или же

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

код

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

выход

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

Мы можем объединить 2 списка с помощью java8 с двумя подходами.

    List<String> list1 = Arrays.asList("S", "T");
    List<String> list2 = Arrays.asList("U", "V");

1) Использование concat:

    List<String> collect2 = Stream.concat(list1.stream(), list2.stream()).collect(toList());
    System.out.println("collect2 = " + collect2); // collect2 = [S, T, U, V]

2) Использование flatMap:

    List<String> collect3 = Stream.of(list1, list2).flatMap(Collection::stream).collect(toList());
    System.out.println("collect3 = " + collect3); // collect3 = [S, T, U, V]

Почти из ответов предлагается использовать ArrayList.

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

Предпочитайте использовать LinkedList для эффективных операций добавления.

Добавление ArrayList имеет амортизацию O(1), но в худшем случае O(n), поскольку размер массива необходимо изменить и скопировать. В то время как LinkedList add всегда является постоянным O(1).

больше информации /questions/2531676/kogda-ispolzovat-linkedlist-poverh-arraylist-v-java/2531677#2531677

Самые умные на мой взгляд:

/**
 * @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;
}

Вы можете сделать это с помощью статического импорта и вспомогательного класса

Примечание: обобщение этого класса может быть улучшено

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

}

Тогда вы можете делать такие вещи, как

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

Версия Java 8 с поддержкой соединения по ключу объекта:

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());
}
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;
}
public static <T> List<T> merge(@Nonnull final List<T>... list) {
    // calculate length first
    int mergedLength = 0;
    for (List<T> ts : list) {
      mergedLength += ts.size();
    }

    final List<T> mergedList = new ArrayList<>(mergedLength);

    for (List<T> ts : list) {
      mergedList.addAll(ts);
    }

    return mergedList;
  }

Используйте вспомогательный класс.

Я предлагаю:

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

Мой любимый способ, использующий свободный API и Гуаву:

      List<String> combined = ImmutableList.<String>builder().addAll(list1).addAll(list2).build()

Если вы хотите сделать это статически, вы можете сделать следующее.

В примерах используются 2 EnumSets в естественном порядке (==Enum-порядок) A, B и присоединяется тогда в ALL список.

public static final EnumSet<MyType> CATEGORY_A = EnumSet.of(A_1, A_2);
public static final EnumSet<MyType> CATEGORY_B = EnumSet.of(B_1, B_2, B_3);

public static final List<MyType> ALL = 
              Collections.unmodifiableList(
                  new ArrayList<MyType>(CATEGORY_A.size() + CATEGORY_B.size())
                  {{
                      addAll(CATEGORY_A);
                      addAll(CATEGORY_B);
                  }}
              );

Вот подход с использованием потоков и Java 8, если ваши списки имеют разные типы, и вы хотите объединить их в список другого типа.

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

Я не утверждаю, что это просто, но вы упомянули бонус для однострочников;-)

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

Недалеко от одной строки, но я думаю, что это самое простое:

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

for(String w:newList)
        System.out.printf("%s ", w);
Другие вопросы по тегам