Podczas rozmowy o pracę możesz zostać spytany o sposób na pewne proste operacje do wykonania na obiektach w Javie.
Przykładem takiego pytania może być prośba o wydajne pogrupowanie Stringów według ich długości.
Jak mogła by wyglądać taka implementacja? #
Najprościej skorzystać z API do Strumieni - Stream API
.
Wystarczy wejściową kolekcję Stringów zamienić na strumień i skorzystać z dedytkowanego collectora w metodzie collect
.
Collectors#groupingBy(Function
super
T,?
extends
K>
classifier)
W nim możemy przekazać dzięki jakiej funkcji chcemy pogrupować nasze obiekty.
Jeśli ma to być długość Stringa to wystarczy nam metoda String#length
.
Całość mogłaby wyglądać jak na fragmencie poniżej.
List<String>
words
=
List.of("apple",
"banana",
"cherry",
"date",
"elderberry",
"fig",
"grape");
Map<Integer,
List<String>>
groupedWords
=
words.stream()
.collect(Collectors.groupingBy(String::length));
A wynik działania jest następujący.
{
3:
["fig"],
4:
["date"],
5:
["apple",
"grape"],
6:
["banana",
"cherry"],
10:
["elderberry"]
}
Jak policzyć liczbę elementów w grupie? #
W tym celu należy użyć przeciążenia metody groupingBy
przyjmującej drugi argument Collector super T,A,D> downstream
.
Collector#groupingBy(Function
super
T,?
extends
K>
classifier,
Collector
super
T,A,D>
downstream)
Aby policzyć liczbę elementów trafiających do jednej grupy potrzebujemy więc zastosować Collectors.counting()
.
Tak wygląda to w całości.
List<String>
words
=
List.of("apple",
"banana",
"cherry",
"date",
"elderberry",
"fig",
"grape");
Map<Integer,
Set<String>>
groupedWords
=
words.stream()
.collect(Collectors.groupingBy(String::length,
Collectors.counting());
A wynik powinien być następujący.
{
3:
1,
4:
1,
5:
2,
6:
2,
10:
1
}
Jak wykorzystać do tego współbieżność? #
Wystarczy, iż zamiast zwykłego strumienia utworzysz strumień współbieżny - metodą Collection#parallelStream()
:
Map<Integer,
List<String>>
groupedWords
=
words.parallelStream()
.collect(Collectors.groupingBy(String::length));
Lub Stream#parallel()
.
Map<Integer,
List<String>>
groupedWords
=
words.stream().parallel()
.collect(Collectors.groupingBy(String::length));
Pamiętaj, iż ta operacja wykona się na współdzielonej puli wątków -
ForkJoinPool
.
Jak wykorzystać do tego standardowe API Javy? #
Możemy pokusić się o samodzielną implementację.
Przykładowa mogłaby wyglądać jak na fragmencie poniżej.
public
Map<Integer,
List<String>>
groupByLength(List<String>
words)
{
Map<Integer,
List<String>>
groups
=
new
HashMap<>();
words.forEach(word
->
{
groups.compute(word.length(),
(integer,
list)
->
{
List<String>
newList
=
list
!=
null
?
list
:
new
ArrayList<>();
newList.add(word);
return
newList;
});
});
return
groups;
}
var
groups
=
groupByLength("apple",
"banana",
"cherry",
"date",
"elderberry",
"fig",
"grape")
I ponownie otrzymany wynik to:
{
3:
["fig"],
4:
["date"],
5:
["apple",
"grape"],
6:
["banana",
"cherry"],
10:
["elderberry"]
}
Podsumowanie #
Najprostszym sposobem grupowania elementów w Javie jest skorzystanie z API strumieni, obiektu Collector
i jego wbudowanej statycznej metody groupingBy
.
Jeśli chcesz możesz skorzystać z przetwarzania współbieżnego, a także zdefiniować efekt końcowy grupowania - na przykład zliczenie ile obiektów trafia do tej samej grupy.