Как только вы поймете, что то, что вы делаете со списком Foo
и списком Bar
, является одной и той же операцией, вы можете решить эту проблему более легко. Эта операция является groupingBy , где id
служит ключом, а List
служит значениями. Вы добавляете условие, что отображение не должно создаваться, если значения являются пустым списком.
Это был бы простой случай группировки Bar
s:
bars.stream()
.filter(b -> !b.getValues().isEmpty())
.collect(toMap(b -> b.id(), b -> b.getValues()));
и Foo
s:
foos.stream()
.filter(f -> !f.getBars().isEmpty())
.collect(toMap(f -> f.id(), f -> groupBars(f.getBars())));
, где groupBars
выполняет первый блок кода. Проблема с группировкой bars
состоит в том, что вы дважды вызываете getValues
, что, как вы упоминаете, стоит дорого. Проблема с группировкой foos
состоит в том, что вы фильтруете, прежде чем узнаете, будет ли сопоставление с пустым значением (результат groupBars
).
Как показано в Не в ответе JD , решение обеих проблем - кэширование / сохранение. Для Bar
s вы хотите кэшировать результат getValues
. Для Foo
s вы хотите кэшировать результат groupBars
, чтобы вы могли фильтровать его. Способ кеширования в потоке с помощью объекта контейнера; в этом случае Map.Entry
работает хорошо, но вы можете использовать любой объект хранения данных. Затем вы отображаете свои данные в свой объект (получая их поток), и вы можете получить их данные в разных частях потока.
Следующий метод группирует список Bar
s по их id
:
static Map<Integer, List<Number>> groupByIdBar(List<Bar> bars) {
return bars.stream()
.map(b -> Map.entry(b.id(), b.getValues()))
.filter(e -> !e.getValue().isEmpty())
.collect(toMap(Entry::getKey, Entry::getValue));
}
где e.getValue()
- это поисковый вызов из нашего «кэша».
Аналогично, этот метод сгруппирует Foo
s по id
:
static Map<Integer, Map<Integer, List<Number>>> groupByIdFoo(List<Foo> foos) {
return foos.stream()
.map(f -> Map.entry(f.id(), groupByIdBar(f.getBars())))
.filter(e -> !e.getValue().isEmpty())
.collect(toMap(Entry::getKey, Entry::getValue));
}
, где e.getValue()
- результат groupByIdBar
, который позволяет постобработку (фильтрацию).
Вы заметите, что методы очень похожи. Это потому, что, как было указано в начале, мы выполняем ту же операцию над ними.
Результат, который вы хотите получить, получается groupByIdFoo(foos)
.
Зацикливание может быть проще в этом случае:
Map<Integer, Map<Integer, List<Number>>> mapping = new HashMap<>();
for (Foo f : foos) {
List<Bar> bars = f.getBars();
if (!bars.isEmpty()) {
Map<Integer, List<Number>> bMap = new HashMap<>();
for (Bar b : bars) {
if (!b.getValues().isEmpty()) {
bMap.put(b.id(), b.getValues());
}
}
if (!bMap.isEmpty()) {
mapping .put(f.id(), bMap);
}
}
}
Если вы решите пойти по этому пути, вы можете оптимизировать код. Например, вы можете создать экземпляр bMap
при первом столкновении с непустым b.getValues()
.
Для набора данных
Bar b1 = new Bar(1);
b1.values = List.of(10, 20, 30, 40, 50);
Bar b2 = new Bar(2);
b2.values = List.of();
Bar b3 = new Bar(3);
b3.values = List.of(60, 70);
Foo f1 = new Foo(11);
f1.bars = List.of(b1, b2, b3);
Foo f2 = new Foo(22);
f2.bars = List.of();
Foo f3 = new Foo(33);
f3.bars = List.of(b2);
Foo f4 = new Foo(44);
f4.bars = List.of(b1);
List<Foo> foos = List.of(f1, f2, f3, f4);
Оба дают результат:
{11={1=[10, 20, 30, 40, 50], 3=[60, 70]}, 44={1=[10, 20, 30, 40, 50]}}