Я новичок в Java, я только что узнал о map
и flatMap
.
Когда 2-й список должен быть преобразован в 1-й список, это было реализовано, как показано ниже.
List<List<Integer>> list_2d = List.of(List.of(1, 2), List.of(3, 4));
List<Integer> lst1 = list_2d
.stream()
.flatMap(arr -> arr.stream())
.collect(Collectors.toList());
printAll(lst1); // [1, 2, 3, 4]
Но мне кажется, что это можно реализовать и без flatMap
.
Есть ли способ сделать код с той же логикой, просто используя map
, а не flatMap
?
Просто спрашиваю, потому что, если map
может заменить все flatMap
, нет причин запоминать flatMap
. Я всегда стремлюсь к простым и базовым вещам.
@ОлеВ.В. Просто потому, что если карта может заменить всю плоскую карту, то незачем запоминать плоскую карту. Я всегда преследую простые и основные вещи.
@frhyme - I always pursue simple and basic thing.
- flatMap
действительно простая и основная вещь. Одна из причин, по которой Java стала настолько популярной, заключается в этих простых конструкциях/типах/операциях; в противном случае в C/C++ нам приходилось иметь дело со многими сложностями только потому, что не было таких простых и базовых вещей, например. подумайте о символах и строках в Java и C/C++.
Вы можете удалить flatMap
, если выполните плоское отображение во время collect
:
List<Integer> lst1 = list_2d
.stream()
.collect(Collectors.flatMapping (List::stream, Collectors.toList()));
Однако я не вижу преимущества использования Collectors.flatMapping
вместо flatMap
.
Вы также можете избавиться от Collectors.flatMapping
, используя reduce
напрямую.
Чтобы ответить на поставленный вопрос, есть и другие способы, но я бы не рекомендовал ни один из тех, которые я могу придумать. Например, вы можете использовать сокращение:
List<List<Integer>> list2d = List.of(List.of(1, 2), List.of(3, 4));
List<Integer> lst1 = list2d
.stream()
.reduce((l1, l2) -> {
ArrayList<Integer> concatenated = new ArrayList<>(l1);
concatenated.addAll(l2);
return concatenated;
})
.orElse(List.of()); // or else empty list
System.out.println(lst1);
Вывод такой же как у вас:
[1, 2, 3, 4]
Но ваш код гораздо легче понять, чем мой. Я предлагаю вам придерживаться его.
Просто потому, что если карта может заменить всю плоскую карту, то нет причин запомнить flatMap. Я всегда преследую простые и основные вещи.
Вы уже получили самое простое и основное, что есть. Кроме того, чтобы использовать весь потенциал потоков, есть много вызовов методов, которые вам нужно будет использовать время от времени. После многолетнего использования потоков я все еще иногда ищу их в Javadoc. Мне пришлось искать детали reduce()
для этого ответа. Не ждите, что у вас все будет в голове. Я знаю flatMap()
наизусть, так как это часто практично.
Редактировать: Только из академического интереса: обычно вы не можете заменить flatMap()
на map()
. Или вы бы использовали map()
с самого начала. Но наоборот: вы всегда можете заменить map()
на flatMap()
. Вы просто не хотели бы. Например, если бы у нас было:
List<String> strings = List.of("short", "somewhat longer");
List<Integer> lengths = strings.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(lengths);
[5, 15]
Если бы по какой-то странной причине мы могли помнить только flatMap()
, а не map()
, мы могли бы сделать:
List<Integer> lengths = strings.stream()
.flatMap(s -> Stream.of(s.length()))
.collect(Collectors.toList());
Я думаю, ясно видеть, что все, что это приносит нам, — это ненужные осложнения. Лучше запомнить как map()
, так и flatMap()
, по крайней мере, достаточно хорошо, чтобы иметь возможность найти их, когда они нам понадобятся.
Возможно, это не то, что вы ищете, но если цель состоит в том, чтобы добавить все подсписки в один результирующий список, использование для каждого также может быть вариантом с точки зрения простого и базового:
List<List<Integer>> list_2d = List.of(List.of(1, 2), List.of(3, 4));
List<Integer> result = new ArrayList<>();
list_2d.forEach(list -> result.addAll(list));
System.out.println(result);
Поскольку уже есть несколько ответов с разными реализациями, я попытаюсь показать разницу между map
и flatMap
.
Операция map
используется для замены каждого элемента потока ровно на один другой элемент. В вашем примере возможными вариантами использования могут быть либо выполнение некоторой агрегации в списке (например, суммирование всех элементов), либо получение List<Integer>
, содержащего все суммы отдельных списков. Или вы можете изменить структуру данных, например, на набор, что даст List<Set<Integer>>
. Количество элементов в результирующем списке всегда будет равно 2, поскольку в исходном списке было 2 элемента.
С другой стороны, операция flatMap
используется для преобразования одного элемента в ноль или более элементов. Рассмотрим семейство классов, состоящее из нескольких людей. Если вы откуда-то получили список Семей, но вас интересуют только отдельные люди, вы можете flatMap
Семейный список превратить в список Людей.
Разница между flatMap
и реализациями, приведенными в других ответах, заключается в типе операции: flatMap
— это промежуточная операция, то есть результатом является Stream
. collect
и reduce
являются терминальными операциями, что означает, что результат будет другим (в вашем примере: List<Integer>
. Если вы хотите выполнить больше потоковых операций (например, filter
или другой map
), вам придется создать еще один Stream
из результирующего списка.
Чтобы проиллюстрировать это, рассмотрим список, указанный в вашем вопросе, и в результате вы хотите получить все четные числа из списка. Я использую collect
решение из этого ответа. Как видите, использование flatMap
избавляет от ненужного создания списка, а также от некоторых строк кода.
public static void usingFlatMap() {
List<List<Integer>> originalList = Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4));
List<Integer> result = originalList
.stream()
.flatMap(list -> list.stream())
.filter(number -> number % 2 == 0)
.collect(Collectors.toList());
System.out.println(result); // [2, 4]
}
public static void usingCollect() {
List<List<Integer>> originalList = Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4));
List<Integer> intermediateList = originalList
.stream()
.collect(Collectors.flatMapping(List::stream, Collectors.toList()));
List<Integer> result = intermediateList
.stream()
.filter(number -> number % 2 == 0)
.collect(Collectors.toList());
System.out.println(result); // [2, 4]
}
Вы можете перебирать этот 2d-список и добавлять элементы (целые числа) из него в другой 1d-список:
List<List<Integer>> list_2d = List.of(List.of(1, 2), List.of(3, 4));
List<Integer> list_1d = new ArrayList<>();
IntStream.range(0, list_2d.size()).forEach(i ->
IntStream.range(0, list_2d.get(i).size()).forEach(j ->
list_1d.add(list_2d.get(i).get(j))));
System.out.println(list_1d); // [1, 2, 3, 4]
List<List<Integer>> list_2d = List.of(List.of(1, 2), List.of(3, 4));
List<Integer> list_1d = new ArrayList<>();
list_2d.forEach(list ->
list.forEach(element ->
list_1d.add(element)));
System.out.println(list_1d); // [1, 2, 3, 4]
Я не думаю, что вы можете преобразовать один элемент Stream в несколько элементов Stream без использования flatMap.
map
преобразует только один элемент в один элемент другого типа.