Ответ 1
Если вы используете Java 8, вы можете сделать что-то вроде этого:
someMap.values().forEach(someList::addAll);
Скажем, у меня есть Map<? extends Object, List<String>>
Я могу легко получить значения карты и перебрать ее, чтобы создать один List<String>
.
for (List<String> list : someMap.values()) {
someList.addAll(list);
}
Есть ли способ сгладить его одним выстрелом?
List<String> someList = SomeMap.values().flatten();
Если вы используете Java 8, вы можете сделать что-то вроде этого:
someMap.values().forEach(someList::addAll);
Использование Java 8 и если вы предпочитаете не создавать экземпляр List
самостоятельно, как в предлагаемом (и принятом) решении
someMap.values().forEach(someList::addAll);
Вы можете сделать все это, потоковое с помощью этого оператора:
List<String> someList = map.values().stream().flatMap(c -> c.stream()).collect(Collectors.toList());
Кстати, должно быть интересно знать, что на Java 8 принятая версия кажется действительно самой быстрой. Он имеет примерно то же время, что и
for (List<String> item : someMap.values()) ...
и это путь быстрее, чем чистое потоковое решение. Вот мой маленький тестовый код. Я явно не называю его эталоном, чтобы избежать итогового обсуждения контрольных ошибок.;) Я делаю каждый тест дважды, чтобы, надеюсь, получить полную скомпилированную версию.
Map<String, List<String>> map = new HashMap<>();
long millis;
map.put("test", Arrays.asList("1", "2", "3", "4"));
map.put("test2", Arrays.asList("10", "20", "30", "40"));
map.put("test3", Arrays.asList("100", "200", "300", "400"));
int maxcounter = 1000000;
System.out.println("1 stream flatmap");
millis = System.currentTimeMillis();
for (int i = 0; i < maxcounter; i++) {
List<String> someList = map.values().stream().flatMap(c -> c.stream()).collect(Collectors.toList());
}
System.out.println(System.currentTimeMillis() - millis);
System.out.println("1 parallel stream flatmap");
millis = System.currentTimeMillis();
for (int i = 0; i < maxcounter; i++) {
List<String> someList = map.values().parallelStream().flatMap(c -> c.stream()).collect(Collectors.toList());
}
System.out.println(System.currentTimeMillis() - millis);
System.out.println("1 foreach");
millis = System.currentTimeMillis();
for (int i = 0; i < maxcounter; i++) {
List<String> mylist = new ArrayList<String>();
map.values().forEach(mylist::addAll);
}
System.out.println(System.currentTimeMillis() - millis);
System.out.println("1 for");
millis = System.currentTimeMillis();
for (int i = 0; i < maxcounter; i++) {
List<String> mylist = new ArrayList<String>();
for (List<String> item : map.values()) {
mylist.addAll(item);
}
}
System.out.println(System.currentTimeMillis() - millis);
System.out.println("2 stream flatmap");
millis = System.currentTimeMillis();
for (int i = 0; i < maxcounter; i++) {
List<String> someList = map.values().stream().flatMap(c -> c.stream()).collect(Collectors.toList());
}
System.out.println(System.currentTimeMillis() - millis);
System.out.println("2 parallel stream flatmap");
millis = System.currentTimeMillis();
for (int i = 0; i < maxcounter; i++) {
List<String> someList = map.values().parallelStream().flatMap(c -> c.stream()).collect(Collectors.toList());
}
System.out.println(System.currentTimeMillis() - millis);
System.out.println("2 foreach");
millis = System.currentTimeMillis();
for (int i = 0; i < maxcounter; i++) {
List<String> mylist = new ArrayList<String>();
map.values().forEach(mylist::addAll);
}
System.out.println(System.currentTimeMillis() - millis);
System.out.println("2 for");
millis = System.currentTimeMillis();
for (int i = 0; i < maxcounter; i++) {
List<String> mylist = new ArrayList<String>();
for (List<String> item : map.values()) {
mylist.addAll(item);
}
}
System.out.println(System.currentTimeMillis() - millis);
И вот результаты:
1 stream flatmap
468
1 parallel stream flatmap
1529
1 foreach
140
1 for
172
2 stream flatmap
296
2 parallel stream flatmap
1482
2 foreach
156
2 for
141
Редактировать 2016-05-24 (через два года):
Выполнение того же теста с использованием реальной версии Java 8 (U92) на той же машине:
1 stream flatmap
313
1 parallel stream flatmap
3257
1 foreach
109
1 for
141
2 stream flatmap
219
2 parallel stream flatmap
3830
2 foreach
125
2 for
140
Кажется, что есть ускорение для последовательной обработки потоков и еще больших накладных расходов для параллельных потоков.
При поиске "java 8 flatten" это единственное упоминание. И дело не в выравнивании потока. Так что для большого блага я просто оставил его здесь.
.flatMap(Collection::stream)
Я также удивлен, что никто не дал одновременный ответ java 8 на исходный вопрос, который
.collect(ArrayList::new, ArrayList::addAll, ArrayList::addAll);
Если вы используете Eclipse Collections, вы можете использовать Iterate.flatten().
MutableMap<String, MutableList<String>> map = Maps.mutable.empty();
map.put("Even", Lists.mutable.with("0", "2", "4"));
map.put("Odd", Lists.mutable.with("1", "3", "5"));
MutableList<String> flattened = Iterate.flatten(map, Lists.mutable.empty());
Assert.assertEquals(
Lists.immutable.with("0", "1", "2", "3", "4", "5"),
flattened.toSortedList());
flatten()
является частным случаем более общего RichIterable.flatCollect().
MutableList<String> flattened =
map.flatCollect(x -> x, Lists.mutable.empty());
Примечание. Я являюсь коммиттером для коллекций Eclipse.
Предлагаемый коллегой:
listOfLists.stream().flatMap(e -> e.stream()).collect(Lists.toList())
Мне нравится это лучше, чем forEach().
Нет, более короткий метод. Вы должны использовать цикл.
Обновление Apr 2014: Наконец-то появилась Java 8. В новой версии вы можете использовать метод Iterable.forEach
для перемещения по коллекции без использования явного цикла.
Обновление Nov 2017:. Найден этот вопрос случайно при поиске современного решения. Закончено с reduce
:
someMap.values().stream().reduce(new ArrayList(), (accum, list) -> {
accum.addAll(list);
return accum;
}):
Это позволяет избежать зависимости от изменяемого внешнего состояния forEach(someList::addAll)
служебных данных flatMap(List::stream)
.
Если вы просто хотите перебирать значения, вы можете избежать всех этих методов addAll.
Все, что вам нужно сделать, это написать класс, который инкапсулирует вашу карту и реализует Iterator:
public class ListMap<K,V> implements Iterator<V>
{
private final Map<K,List<V>> _map;
private Iterator<Map.Entry<K,List<V>>> _it1 = null;
private Iterator<V> _it2 = null;
public ListMap(Map<K,List<V>> map)
{
_map = map;
_it1 = map.entrySet().iterator();
nextList();
}
public boolean hasNext()
{
return _it2!=null && _it2.hasNext();
}
public V next()
{
if(_it2!=null && _it2.hasNext())
{
return _it2.next();
}
else
{
throw new NoSuchElementException();
}
nextList();
}
public void remove()
{
throw new NotImplementedException();
}
private void nextList()
{
while(_it1.hasNext() && !_it2.hasNext())
{
_it2 = _it1.next().value();
}
}
}
Хорошим решением для подкатегории Карты карт является сохранение, по возможности, данных в Guava Table
.
https://github.com/google/guava/wiki/NewCollectionTypesExplained#table
Итак, например, Map<String,Map<String,String>>
заменяется на Table<String,String,String>
, который уже flattend. Фактически, документы говорят, что реализация HashBasedTable
, Table
Hash, по существу, поддерживается HashMap<R, HashMap<C, V>>