Październik
08
2009

Filtrowanie kolekcji

Słowa kluczowe: , , , , | Kategorie: Java
No Gravatar

Ostatnio cierpię na nadmiar zajęć, co skutkuje znacznym ograniczeniem czasu. Dlatego kolejne wpisy mogą pojawiać się rzadko.

Dzisiaj zajmę się bardzo ogólnym zagadnieniem związanym z językiem Java – natrafiłem na problem filtrowania kolekcji z użyciem wielu różnych kryteriów. Jednak najprostrze rozwiązania nie przynosiły pozytywnych efektów.

Wykonywanie osobnych zapytań dla poszczególnych kryteriów okazało się mało wydajne. Jednorazowe pobranie wszystkich rekordów i sprawdzanie warunków dla każdego z nich okazało się szybsze (sic!):

for (Foo foo : fooCollection) {
    if (condition(foo)) {
        doSomething(foo);
    }
}

Niestety w takiej sytuacji złożone kryteria filtrowania mogą negatywnie wpływać na czytelność kodu. Dlatego pomyślałem nad utworzeniem osobnych kolekcji dla każdego zestawu warunków. Ale to rozwiązanie również miało swoje wady – samo utworzenie nowej kolekcji to iteracja po wszystkich rekordach, a większość z nich byłaby wykorzystywana tylko raz.

Potrzebowałem specjalnego iteratora, który mógłby filtrować kolekcje transparentnie. Dla zachowania hermetyzacji zmienności, kryteria filtrowania wydzieliłem do osobnej strategii:

package pl.info.czerwinski.collections;

public interface Filter<T> {
    public boolean isAccepted(T object);
}

Iterator filtrujący tworzony jest dla wybranej kolekcji (właściwie dla dowolnej implementacji Iterable) oraz wybranej strategii filtra:

package pl.info.czerwinski.collections;

import java.util.Iterator;

public class FilteringIterator<E> implements Iterator<E> {

    private Filter<E> filter;
    private Iterator<E> iterator;
    private E next;

    public FilteringIterator(
            Iterable<E> iterable, Filter<E> filter) {
        this.filter = filter;
        iterator = iterable.iterator();
        next = null;
    }
}

Pomocnicza referencja next pozwoli sprawdzić, czy istnieje kolejny element akceptowany przez filtr, jedynie przy użyciu iteratora.

Implementacja metod interfejsu Iterator wygląda następująco:

public boolean hasNext() {
    do {
        if (!iterator.hasNext()) {
            return false;
        }
        next = iterator.next();
    } while (!filter.isAccepted(next));
    return true;
}

public E next() {
    E current = (next == null) ? iterator.next() : next;
    next = null;
    while (!filter.isAccepted(current)) {
        current = iterator.next();
    }
    return current;
}

public void remove() {
    throw new UnsupportedOperationException(
            "Filtering iterator cannot remove elements.");
}

Powyższy iterator filtruje elementy kolekcji, ale nie pozwala na zastosowanie pętli for-each. Dlatego utworzyłem odpowiedni dekorator implementujący interfejs Iterable:

package pl.info.czerwinski.collections;

import java.util.Iterator;

public class FilteredIterable<E> implements Iterable<E> {

    private Filter<E> filter;
    private Iterable<E> iterable;

    public FilteredIterable(
            Iterable<E> iterable, Filter<E> filter) {
        this.filter = filter;
        this.iterable = iterable;
    }

    public Iterator<E> iterator() {
        return new FilteringIterator<E>(iterable, filter);
    }
}

Teraz już mogłem dowolnie filtrować moją kolekcję:

Iterable<Foo> filtered =
    new FilteredIterable<Foo>(fooCollection, fooFilter);
for (Foo foo : filtered) {
    doSomething(foo);
}

Dodatkową zaletą dekoratora jest możliwość składania wielu filtrów:

Iterable<Foo> filtered =
    new FilteredIterable<Foo>(
            new FilteredIterable<Foo>(
                    new FilteredIterable<Foo>(
                            fooCollection,
                            filter1),
                    filter2),
            filter3);

Opisując filtrowaniu kolekcji zauważyłem, że zastosowałem dwa wzorce projektowe (strategii i dekoratora) – całkowicie nieświadomie. Nie wiem, czy to kwestia dobrze skonstruowanych bibliotek Javy, czy może wypracowane nawyki, ale chyba prowadzenie dziennika internetowego przynosi efekty.


Jedna odpowiedź do “Filtrowanie kolekcji”

  1. Też stosuje podobne podejście, tylko że za pomocą biblioteki Apache Commons Collections i był tam interfejs Predicate ( http://commons.apache.org/collections/api-release/index.html ) . Wadą tej biblioteki jest to że nie jest ona generyczna. ALe istnieje jego generyczna wersja ” Commons-Collections with Generics” http://larvalabs.com/collections/

Napisz Komentarz

*