Як з'єднати два списки на Java?


749

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

Чи є простіший спосіб, ніж:

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

5
Якщо ви робите це виключно для цілей ітераційних побачити інше питання - є Google гуави і Java 8 Рішення stackoverflow.com/questions/4896662 / ...
Борис Треухов

8 Рішення Java з допомогою методу корисності: stackoverflow.com/a/37386846/1216775
akhil_mittal

Прочитавши деякі відповіді, вибачте, що запитав.
Ентоні Рутлідж

Відповіді:


590

На Java 8:

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

82
Гауд, це річ у Java 8? З технічної точки зору ви перемагаєте, я думаю, але це чорт довгої черги :-)
Роберт Аткінс

4
Для випадкового читача ось коротке рішення з використанням також Java _ Streams: stackoverflow.com/a/34090554/363573
Stephan

7
Це некрасиво, але, принаймні, добре говорить, і його можна використовувати без багатолінійних лямбдів. Я дуже хотів би, щоб було вільне додавання, яке повернуло стислий список.
Усман Ісмаїл

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())
Пітер

569

Вгорі голови я можу скоротити його на один рядок:

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

156
Хоча ти технічно правильний, ти скоротив його на один рядок, асиметрія цього помилка мене. Досить, що я щасливіше "витратити" зайву лінію.
Роберт Аткінс

13
Чи не існує тут проблеми, коли інтеральний масив newList буде ініціалізований до розміру listOne, а потім повинен потенційно розширюватися при додаванні всіх елементів з listTwo? Було б краще взяти розмір кожного списку і використовувати його для розміру нового масиву?
Ерік

2
Це рішення, яке найкраще працювало для мене. Я зробив порівняння щодо продуктивності різних рішень, це вийшло переможцем, разом зі створенням порожнього списку, а потім addAll()обох. Я спробував усіх тих, хто пропонує не копіювати списки, і в результаті цього є великі накладні витрати, які нам цього разу не потрібні.
manuelvigarcia

приємно, але ви можете навіть скоротити його: List list = новий ArrayList <> (list1) .addAll (list2);
швидкість

1
@velocity: ні, це не спрацює. addAll(Collection)повертає a boolean.
Штійн Ван Баель

306

Ви можете використовувати бібліотеку колекцій Apache commons :

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

52
Приємно, але потрібні апаші. Він уточнив "немає зовнішніх бібліотек"
Quantum7

101
@ Quantum7, як і раніше корисний для інших людей;) Також, чи є apache спільнотою навіть зовнішню бібліотеку? Я нічого не починаю без цього!
tster

28
@Platinum Ні, згідно з документом ListUtils.union точно рівносильно коду OP. Але, можливо, вводити в оману використання операції SET ("Союз") у контексті списку. Я бачу, як ви можете розраховувати на видалення дублікатів чи чогось іншого, але, здається, метод цього не робить.
Quantum7

24
Уникайте колекцій Apache Commons. Це не безпечно, немає дженериків. Чудово, якщо ви використовуєте Java 1.4, але для Java 5 і вище я вважаю за краще Google Guava.
Майкл Пієфель

11
@MichaelPiefel Останні колекції Apache Commons 4 є безпечними для типу. З посиланням на метод Java 8, цей вид статичних утиліт стає дуже важливим.
mingfai

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
Це справжня відповідь на це питання.
Wouter Lievens

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
Некрасивий і злий, як і майже будь-яке використання подвійної дужки ініціалізації. Однак це коротше;)
Jorn,

4
@MarnixKlooster: Eclipse знає, що не слід користуватися ним і робить його неприємним у використанні ;-)
Йоахім Зауер

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 імператор

73

Не простіше, але без зміни накладних витрат:

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

55

Виявив це питання, шукаючи об'єднати довільну кількість списків, не враховуючи зовнішніх бібліотек. Тож, можливо, це допоможе комусь іншому:

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

Корисно, якщо ви хочете застосувати ту саму логіку до кількох різних колекцій в одній для ().


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

Ви, ймовірно, готові сказати Список <String> result1 = Stream.concat (Stream.concat (list1.stream (), list2.stream ()), list3.stream ()). Collection (Collectors.toList ()); у вашого першого оператора. Будь ласка, виправте це.
WebComer

44

Ось рішення java 8 за допомогою двох рядків:

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

Майте на увазі, що цей метод не слід застосовувати, якщо

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

через міркування побічної дії.

Обидві вищевказані умови не стосуються зазначеного вище випадку приєднання двох списків, тому це безпечно.

Виходячи з цієї відповіді на інше питання.


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

11
Не зміна оригінальних списків було одним із критеріїв, але це корисно мати тут як приклад для ситуацій, коли це не є обмеженням.
Роберт Аткінс

1
Спасибі чи ще простіший listOne.addAll (listTwo)
Jay

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


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]

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

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

4

Використовуйте клас Helper.

Я пропоную:

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 завжди є постійним 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. Якщо ви використовували це у своєму коді, ваш код, ймовірно, не працює на не-Sun (або не-Oracle зараз) JDK / JRE.
Адріан Шум

@AdrianShum Чи існують інші JDK / JRE, ніж Oracle? Це мене здивувало. Навіть якщо обмежитися найпоширенішими функціональними можливостями API, відновлення цих речей, можливо, займе віки ...
Егор Ганс,

1
JVM досить багато. Найчастіше зустрічається в корпоративному світі - IBM, який, iirc, поєднаний з веб-сферою
Адріан Шум

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

0

Якщо ви хочете зробити це статично, ви можете зробити наступне.

У прикладах використовується 2 EnumSets у природному порядку (== Enum-order) 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);
                  }}
              );

Це створило б новий анонімний клас. Не рекомендується підхід!
kravemir

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

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.