Jak szybciej filtrować kolekcje w Java? Rozwiązanie to CQEngine 

04-11-2021

Każdy z nas na początku drogi programowania w Java, trafił na problem filtrowania zawartości kolekcji. Nie raz i nie dwa w naszych kodach musimy przefiltrować dane. To konieczne gdy chcemy coś z nimi zrobić i odpowiednio je przeprocesować. 

Java a filtrowanie kolekcji

Nastanie epoki wyrażeń lambda oraz pewnych uproszczeń w stosunku do tego, jak można modelować pętle warunkowe ułatwiło nam zycie. Java zaczęła być bardziej czytelna dla początkujących. Jednakże zostaje jeden aspekt, którego standard Javy nie posiada w swoim asortymencie  – zaawansowanego filtrowania elementów strumienia na podobieństwo LINQ, będącego częścią standardu języka C#.

filtrowanie kolekcji w Java
Często się zdarza, że w naszych kodach musimy przefiltrować dane

LINQ – proste tworzenie zapytań

LINQ to nic innego jak system pozwalający na proste tworzenie zapytań do obiektów. I to bez potrzeby odpytywania prawdziwej bazy danych za pomocą zapytań SQL/no-SQL. Są one bardzo intuicyjne. W krótkim czasie użytkownicy są w stanie w prosty i przejrzysty sposób zamodelować odpowiednie zapytania dla potrzebnych im zasobów.

CQEngine – szybkie filtrowanie kolekcji

W ostatnim czasie natknąłem się na bibliotekę znaną jako CQEngine. Pozwala ona odwzorowanie tej samej funkcjonalności w Javie z całkiem niezłą wydajnością. CQEngine stworzył Niall Gallagher pod patronatem firmy Jetbrains. CQEngine prócz wywołania metod odpowiadających operacjom  pozwala również na pisanie zapytań SQLopodobnych oraz we własnej notacji,  które są następnie interpretowane przez bibliotekę w odpowiednie zapytanie.

I zapytania, i tworzenie kolekcji

CQEngine prócz tworzenia zapytań na zasadzie LINQ pozwala również na opcjonalne definiowane i budowanie indeksów (są to obiekty, które na bazie danych mają na celu poprawę szybkości dostępu do danych) tym samym nie polega jak typowe strumienie  na iteracji po elementach a na „inteligentnym” poruszaniu się po kolekcji elementów, na jakich przyszło mu pracować. 

Same indeksy są podzielone na podtypy, pozwalające na wyspecyfikowanie jak konkretne pola mają być przeszukiwane.

Aby zacząć przygodę z CQEngine, możemy zaimportować naszą bibliotekę za pomocą maven’a: 

Dodatkowo zalecam dodanie zależności Typetools, pozwalającej na przetwarzanie atrybutów w formacie lambda: 

Przykładowe zapytania osadzimy na następującym modelu danych: 

Testujemy CQEngine 

W naszym przypadku testowym zdefiniowaliśmy klasę Student oraz klasę Course. Zawierają one podstawowe informacje o studentach i kursach na jakie obecnie uczęszczają.

Aby móc przeszukiwać nasze kolekcje, musimy zdefiniować atrybuty. Możemy pozwolić to zrobić automatycznie klasie za pomocą  

Jednakże w tym momencie rozwiązanie to nadaje się tylko do POJO. Nie uwzględni też generowania atrybutów dla kolekcji znajdujących się w naszych definicjach klas. Stąd też w przykładnie jawnie zdefiniowaliśmy co i pod jaką nazwą chcemy pytać.

Z używaniem atrybutów odnośnie wewnętrznych kolekcji wiąże się potrzeba napisania dedykowanych łączników z klasy główniej. Możemy wykorzystać do tego MultiValueAttribute z odpowiednim mapowaniem do interesującej nas cechy atrybutu klasy. Dodatkowo, dla poprawienia czytelności zastosowano @Value z pakietu Lombok-a.

Nasze przykłady filtrowania kolekcji

Przykłady oprzemy o następującą listę studentów  

W odróżnieniu od typowej bazy danych – nie jesteśmy w stanie w ramach biblioteki dynamicznie odpytywać atrybutów klasy podrzędnej z poziomu klasy nadrzędnej inaczej niż nie definiując atrybut „przekazujący” interesujące nas wartości do możliwych do zapytania. Możemy zobaczyć poniżej zamodelowanie takiego atrybutu:

wraz z jego wykorzystaniem w ramach testu: 

Poniżej kilka mniejszych przykładów zastosowania funkcji dostarczonych przez bibliotekę: 

Możliwość zagnieżdżania się funkcji pozwala nam na zamodelowanie zapytania wraz z podwarunkami. Każde wykonanie funkcji zwraca nam ResultSet, który następnie możemy zmapować do pożądanej przez nas kolekcji bądź dalej przetwarzać w ramach wyrażeń lambda.

Same wyniki zapytań możemy również sortować po ich wykonaniu, co widzimy w przypadku „queryFour” –  wynik filtrowania został posortowany malejąco na podstawie numerów indeksów.

SQL-like i modelowanie zapytania

W przypadku składni „SQL-like”  jesteśmy w stanie modelować zapytania w formie podobnej do funkcyjnej oraz dalej operujemy w ramach wyznaczonych przez nas atrybutów.

CQN i przeszukiwanie zmiennych

Biblioteka również dostarcza możliwość przeszukiwania zmiennych za pomocą składni CQN – umożliwiającej zagnieżdżanie się warunków przy zachowaniu czytelności.

Dokładniejszy opis biblioteki można znaleźć na stronie projektu CQEngine.