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

750

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

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

List<String> newList = new ArrayList<String>();
newList.addAll(listOne);
newList.addAll(listTwo);
Роберт Аткинс
источник
5
Если вы делаете это исключительно для итерационных целей, см. Другой вопрос - есть решения для google guava и java 8 stackoverflow.com/questions/4896662/…
Борис Треухов
Решение Java 8 с утилитарным методом: stackoverflow.com/a/37386846/1216775
akhil_mittal
Прочитав некоторые ответы, я извиняюсь, что спросил.
Энтони Ратледж

Ответы:

593

В Java 8:

List<String> newList = Stream.concat(listOne.stream(), listTwo.stream())
                             .collect(Collectors.toList());
Дейл Эмери
источник
82
Gawd, это вещь в Java 8? Технически вы выиграли, я думаю, но это чертовски длинная линия :-)
Роберт Аткинс
4
Для случайного читателя вот более короткое решение, использующее также Java _ Streams: stackoverflow.com/a/34090554/363573
Стефан
7
Это некрасиво, но, по крайней мере, свободно и может использоваться без многострочных лямбд. Я действительно хотел бы, чтобы был свободный addAll, который возвратил объединенный список.
Усман Исмаил
6
Полагаю, стоит отметить, что из этого тоже очень легко получить внятный список, например так:List<String> newList = Stream.concat(listOne.stream(), listTwo.stream()).distinct().collect(Collectors.toList());
Роджер
1
Альтернатива конкату: поток потоковStream.of(listOne, listTwo).flatMap(Collection::stream).collect(Collectors.toList())
Питер
570

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

List<String> newList = new ArrayList<String>(listOne);
newList.addAll(listTwo);
AdamC
источник
156
Хотя вы технически правы, вы сократили его на одну строку, асимметрия этого меня беспокоит. Достаточно того, что я счастлив «потратить» лишнюю линию.
Роберт Аткинс
13
Нет ли здесь проблемы, когда целочисленный массив newList будет инициализирован до размера listOne, а затем потенциально должен расширяться при добавлении всех элементов из listTwo? Было бы лучше взять размер каждого списка и использовать его для определения размера нового массива?
Эрик
2
Это было решение, которое работало лучше всего для меня. Я сравнил производительность разных решений, которые вышли победителями, вместе с созданием пустого списка, а затем и addAll()того, и другого. Я перепробовал все те, которые предлагают не копировать списки, и они приводят к большим накладным расходам, которые нам не понадобились на этот раз.
Мануэльвигарсия
хорошо, но вы можете даже сделать его короче: List list = new ArrayList <> (list1) .addAll (list2);
скорость
1
@ Скорости: нет, это не сработает. addAll(Collection)возвращает boolean.
Стейн Ван Баел
307

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

List<String> newList = ListUtils.union(list1, list2);
Гильермо
источник
52
Хорошо, но требует Apache Commons. Он указал «нет внешних библиотек»
Quantum7
101
@ Quantum7, все еще полезный для других людей;) Кроме того, является ли Apache Commons даже внешней библиотекой? Я ничего не начинаю без этого!
тстер
28
@Platinum Нет, согласно документации ListUtils.union в точности соответствует коду OP. Но, возможно, вводить в действие операцию SET («Объединение») в контексте списка вводит в заблуждение. Я могу видеть, как вы можете ожидать, что это удалит дубликаты или что-то, но, похоже, метод не делает этого.
Quantum7
24
Избегайте коллекций Apache Commons. Это небезопасно, нет дженериков. Отлично, если вы используете Java 1.4, но для Java 5 и выше, я бы предпочел Google Guava.
Майкл Пифель
11
@MichaelPiefel Последняя коллекция Apache Commons Collections 4 является типобезопасной. С помощью ссылки на метод Java 8 этот вид статических утилит становится очень важным.
Минфай
93

Одно из ваших требований - сохранить оригинальные списки. Если вы создаете новый список и используете его 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);
Кевин К
источник
15
Это реальный ответ на этот вопрос.
Ваут Ливенс
9
Это работоспособное решение, но учтите, что при изменении базовых объектов списка (list1, list2) содержимое этого списка изменяется. Возможно, вам не удастся изменить экземпляр самого CompositeUnmodifiableList , но если вы сможете получить ссылку на исходные списки, вы можете это сделать. Также для тех, кто незнаком: конечный модификатор просто влияет на ссылку на сам объект списка, он не может измениться, но все еще возможно изменение содержимого списка!
jwj
3
@jwj все очень хорошие моменты, спасибо. Название класса, вероятно, заслуживает некоторого объяснения. Я вижу, что этот класс делает что-то очень похожее на Collections.unmodifiableList()метод, который оборачивает список, чтобы сделать его неизменяемым. CompositeUnmodifiableListделает то же самое, за исключением того, что оборачивает два списка и обеспечивает объединенное представление. Все пункты, о CompositeUnmodifiableListкоторых вы говорите , также верны Collections.unmodifiableList().
Кевин К
2
Конструктор может принятьList<? extends E>
Патрик Паркер
84

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

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

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

залп
источник
44
Уродливый и злой, как почти любое использование инициализации двойной скобки. Это короче, хотя;)
Йорн
4
@MarnixKlooster: Eclipse знает, что вы не должны его использовать, и делает его непривлекательным ;-)
Joachim Sauer
20
Хотя это физически одна строка, я не считаю это «одной строкой».
splungebob
11
почему люди ненавидят анонимные инициализаторы блоков
NimChimpsky
18
@NimChimpsky Я думаю, что это в основном потому, что это не просто инициализатор анонимного блока, но вы фактически создаете анонимный подкласс ArrayList. При этом, если вы доверяете результатам этого вопроса «Двойная скобка» , создается впечатление, что ненависть к DBI - это в основном вопрос стилистического вкуса и микрооптимизации. Насколько я могу судить, нет никаких серьезных штрафов за это. Подлый недостаток был бы, если бы вы когда-нибудь попытались сравнить его класс, потому что это не будет ArrayList.
Патрик
75

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

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

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

List<String> newList = Stream.of(listOne, listTwo, listThree)
                            .flatMap(Collection::stream)
                            .collect(Collectors.toList());
отметка
источник
35
x -> x.stream()можно заменить на Collection::stream.
Мартин
10
... или даже с List::stream.
MC Emperor
73

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

List<String> newList = new ArrayList<>(listOne.size() + listTwo.size());
newList.addAll(listOne);
newList.addAll(listTwo);
Мартин
источник
55

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

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

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

Юрий Гейниш
источник
9
Например: Lists.newArrayList (Iterables.concat (list1, list2));
meilechh
вам следует позвонить com.google.common.collect.Iterators#concat(java.util.Iterator<? extends java.util.Iterator<? extends T>>)вместо Iterables#concat(); потому что позже все еще копируют элементы во временную ссылку!
Боб
45

Java 8 ( Stream.ofи Stream.concat)

Предлагаемое решение предназначено для трех списков, хотя оно может применяться и для двух списков. В 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> concatenateLists(List<T>... collections) {
        return Arrays.stream(collections).flatMap(Collection::stream).collect(Collectors.toList()); 
}

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

List<String> result3 = Utils.concatenateLists(list1,list2,list3);
akhil_mittal
источник
Вы, вероятно, готовы сказать List <String> result1 = Stream.concat (Stream.concat (list1.stream (), list2.stream ()), list3.stream ()). Collect (Collectors.toList ()); у вашего первого оператора. Пожалуйста, исправь это.
WebComer
44

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

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

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

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

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

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

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

SpaceTrucker
источник
12
Если я не ошибаюсь, это на самом деле не рекомендуется - docs.oracle.com/javase/8/docs/api/java/util/stream/… Пожалуйста, смотрите раздел побочных эффектов. > Побочные эффекты в поведенческих параметрах для потоковых операций, как правило, не приветствуются, поскольку они часто могут привести к невольным нарушениям требования о безгражданстве, а также другим угрозам безопасности потоков. Так что в этом случае лучше использовать Collectors.toList ()
Антон Баланюк
@AntonBalaniuc Вопрос в том, действительно ли это побочный эффект. В этот момент newListне наблюдается ни один другой поток. Но вы правы, что этого, вероятно, не следует делать, если неизвестно, откуда newListпришло значение (например, если оно newListбыло передано в качестве параметра.
SpaceTrucker
2
Мне любопытно; почему .forEach(newList::addAll);вместо .collect(Collectors.toList());?
11684
4
@ 11684 потому что коллекционер собирает List<List<Object>>. Вы можете иметь в виду что-то вроде этого: stackoverflow.com/questions/189559/…
SpaceTrucker
@SpaceTrucker Ой, я упустил это. Спасибо за прояснение моего замешательства. Да, я должен был думать flatMap.
11684
34

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

Collections.addAll(listOne, listTwo.toArray());
ceklock
источник
11
Не изменение исходных списков было одним из критериев, но это полезно иметь здесь в качестве примера для ситуаций, когда это не является ограничением.
Роберт Аткинс
1
Спасибо, или даже проще listOne.addAll (listTwo)
Джей
27

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

List<String> newList = new ArrayList<String>(listOne);
newList.addAll(listTwo);
Тим
источник
Это приведет к дублированию строк? То есть строка, которая существует в обоих списках, будет существовать дважды в результирующем списке?
AgentKnopf
4
@ Zainodis Да, могут быть дубликаты. ListСтруктура не накладывает никаких ограничений уникальности. Вы можете удалить обманщиков, делая то же самое с сетами. Set<String> newSet = new HashSet<>(setOne); newSet.addAll(setTwo);
Патрик
20

Немного короче было бы:

List<String> newList = new ArrayList<String>(listOne);
newList.addAll(listTwo);
Йорн
источник
17

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

@SafeVarargs
public static <T> List<T> concat(List<T>... lists) {
    return Stream.of(lists).flatMap(List::stream).collect(Collectors.toList());
}
Даниэль Хари
источник
13

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

(newList = new ArrayList<String>(list1)).addAll(list2);
deterb
источник
11

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

List<?> newList = 
Stream.of(list1, list2).flatMap(List::stream).collect(Collectors.toList());
Нитин джайн
источник
Этот подход уже предложен этим ответом: stackoverflow.com/a/37386846
Мили
9

еще одно линейное решение с использованием 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]
Saravana
источник
1
Я бы добавил, что это решение, вероятно, более производительно, чем используемое flatMap, потому что списки повторяются только один раз, когда они собираются
Стефан
7

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

/**
 * @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;
}
Оливье Фошо
источник
3
Не забывайте @SafeVarargs!
Радон Росборо
6

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

нб родовые этого класса, вероятно , может быть улучшены

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);
Дейв Чейни
источник
Насколько актуален статический импорт или вспомогательный класс?
Шмосел
6

Версия 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());
}
cslysy
источник
4
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;
}
martyglaubitz
источник
4

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

Я предлагаю:

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)));
}
Алекс
источник
3
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;
  }
Лангустен Густел
источник
2

Мы можем объединить 2 списка, используя java8 с 2 подходами.

    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]
Химанк Батра
источник
1
При ответе на одиннадцатилетний вопрос с тридцатью другими ответами обязательно укажите, к каким новым аспектам вопроса относится ваш ответ, и отметьте, сработали ли эти методы, когда вопрос был задан, или они зависят от функций, которые были введены за эти годы.
Джейсон Аллер
2

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

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

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

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

больше информации https://stackoverflow.com/a/322742/311420

Рэймонд Шенон
источник
0

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

Collection mergedList = Collections.list(new sun.misc.CompoundEnumeration(new Enumeration[] {
    new Vector(list1).elements(),
    new Vector(list2).elements(),
    ...
}))
ддимитров
источник
почему кто-то никогда не должен использовать их?
Дэвид
5
@David, потому что он предназначен для внутреннего использования в JDK. Если вы использовали это в своем коде, ваш код, скорее всего, не будет работать на JDK / JRE не Sun (или не Oracle).
Адриан Шум
@AdrianShum Есть ли другие JDK / JRE, кроме Oracle? Это меня удивит. Даже если он ограничен самой распространенной функциональностью API, перестройка всего этого, вероятно, займет много времени ...
Егор Ханс
1
Существует довольно много JVM. В корпоративном мире чаще всего следует видеть IBM, который, iirc, связан с веб-сферой
Adrian Shum
0

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

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

for(String w:newList)
        System.out.printf("%s ", w);
Нирмал
источник
0

Вот подход с использованием потоков и 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());
}
Shinzou
источник
0

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

В примерах используются 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);
                  }}
              );
Ян Вейц
источник
Это создаст новый анонимный класс. Не рекомендуется подход!
Кравемир
-3
import java.util.AbstractList;
import java.util.List;


/**
 * The {@code ConcatList} is a lightweight view of two {@code List}s.
 * <p>
 * This implementation is <em>not</em> thread-safe even though the underlying lists can be.
 * 
 * @param <E>
 *            the type of elements in this list
 */
public class ConcatList<E> extends AbstractList<E> {

    /** The first underlying list. */
    private final List<E> list1;
    /** The second underlying list. */
    private final List<E> list2;

    /**
     * Constructs a new {@code ConcatList} from the given two lists.
     * 
     * @param list1
     *            the first list
     * @param list2
     *            the second list
     */
    public ConcatList(final List<E> list1, final List<E> list2) {
        this.list1 = list1;
        this.list2 = list2;
    }

    @Override
    public E get(final int index) {
        return getList(index).get(getListIndex(index));
    }

    @Override
    public E set(final int index, final E element) {
        return getList(index).set(getListIndex(index), element);
    }

    @Override
    public void add(final int index, final E element) {
        getList(index).add(getListIndex(index), element);
    }

    @Override
    public E remove(final int index) {
        return getList(index).remove(getListIndex(index));
    }

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

    @Override
    public boolean contains(final Object o) {
        return list1.contains(o) || list2.contains(o);
    }

    @Override
    public void clear() {
        list1.clear();
        list2.clear();
    }

    /**
     * Returns the index within the corresponding list related to the given index.
     * 
     * @param index
     *            the index in this list
     * 
     * @return the index of the underlying list
     */
    private int getListIndex(final int index) {
        final int size1 = list1.size();
        return index >= size1 ? index - size1 : index;
    }

    /**
     * Returns the list that corresponds to the given index.
     * 
     * @param index
     *            the index in this list
     * 
     * @return the underlying list that corresponds to that index
     */
    private List<E> getList(final int index) {
        return index >= list1.size() ? list2 : list1;
    }

}
benez
источник