12-15__google_collections.pdf

(595 KB) Pobierz
406260720 UNPDF
Biblioteka miesiąca
Google
Collections Library
Kolekcje to nieodłączny element skrzynki narzędziowej każdego
programisty. Jeśli programujesz w Javie i chciałbyś uprościć oraz
zoptymalizować Twój kod odpowiedzialny za obsługę kolekcji, to
trafiłeś na właściwy artykuł. Zapraszam do lektury!
Dowiesz się:
• Co oferuje biblioteka Google Collections Li-
brary i dlaczego warto jej używać.
• Jak można ją wykorzystać w praktyce.
Powinieneś wiedzieć:
• Jak programować w języku Java.
• Solidne podstawy biblioteki Java Collection
Framework.
wanie ) określa kontener podobny do Map ( mapo-
wanie ) z JCF, z tą jednak różnicą, że potrafi on
łączyć wiele wartości z jednym kluczem. Dla
przykładu, jeśli użytkownik biblioteki dwu-
krotnie wywoła metodę put(K, V) na konte-
nerze typu Multimap , przy czym klucz ( K ) za
każdym razem będzie taki sam, zaś wartości
( V ) będą różne, to w rezultacie kontener bę-
dzie zawierał odwzorowania klucza K do oby-
dwu wartości. W przypadku kontenerów ty-
pu Multimap metoda get() zwraca kolekcję za-
wierającą wszystkie wartości powiązane z zada-
nym kluczem:
Poziom
trudności
Przegląd możliwości
Pisząc w największym możliwym skrócie, Go-
ogle Collection Library to zestaw nowych in-
terfejsów kolekcji, ich implementacji oraz klas
pomocniczych. GCL należy postrzegać jako
bibliotekę stanowiącą rozszerzenie Java Col-
lections Framework. Poniżej przedstawio-
na jest lista najbardziej kluczowych składni-
ków GCL:
W dzisiejszych czasach, kiedy progra-
Collection<V> get(K key);
mowanie często polega na składa-
niu aplikacji z gotowych klocków,
a budowane programy są coraz bardziej i bar-
dziej złożone, zestaw podstawowych klas kolek-
cji jest elementem absolutnie podstawowym i
nieodzownym. Z tego względu kolekcje stały
się integralnym składnikiem bibliotek standar-
dowych nowoczesnych języków programowa-
nia. Java nie jest w tym przypadku wyjątkiem.
Standardowy pakiet Java Collection Frame-
work to solidny zestaw kontenerów ogólnego
użytku i pomimo tego, że został on zaprojekto-
wany i zaimplementowany kilkanaście lat temu
– ciągle jest z powodzeniem stosowany. Jednak-
że, użytkownicy JCF mogą od czasu do cza-
su odnieść wrażenie, że pewne operacje na ko-
lekcjach można by wykonywać nieco prościej,
szybciej i efektywnej. Do tego samego wnio-
sku doszli dwaj pracownicy firmy Google: Ke-
vin Bourrillion oraz Jared Levy. Co więcej, na
myśleniu się nie skończyło. W efekcie prac tych
dwóch panów powstała biblioteka Google Col-
lections Library, stanowiąca świetne uzupełnie-
nie i rozszerzenie pakietu Java Collection Fra-
mework. W niniejszym artykule przedstawię
pokrótce genezę tej biblioteki, opiszę jej pod-
stawowe składniki i pokażę jak można zastoso-
wać ją w praktyce.
• Nowe interfejsy kolekcji: Multimap ,
Multiset oraz BiMap .
• Wysoko wydajne, niemodyikowalne (ang.
immutable ) implementacje standardowych
klas kolekcji JCF, np. ImmutableSet .
• Użytkowe klasy Lists , Sets oraz Maps ,
wspomagające pracę ze standardowymi
kontenerami biblioteki JCF.
• Użytkowe klasy Iterators i Iterables
zawierające zestawy statycznych metod
operujących na interfejsach Iterator
i Iterable .
• Klasa Ordering , czyli znacznie ułatwiająca
operacje sortowania obiektów według wie-
lu, potencjalnie złożonych kryteriów.
• Szereg dodatkowych udogodnień związa-
nych z obsługą kontenerów w języku Java.
GCL dostarcza cały szereg implementacji in-
terfejsu Multimap : ArrayListMultimap ,
ForwardingMultimap , HashMultimap ,
ImmutableListMultimap , ImmutableMultimap ,
ImmutableSetMultimap , LinkedHashMultimap ,
LinkedListMultimap , TreeMultimap .
Interfejs Multiset ( wielozbiór ) definiuję ko-
lekcję, która zachowuje się podobnie jak Set
( zbiór ) z JCF, aczkolwiek pozwala na dupli-
kację elementów. Powtarzające się elementy
wielozbioru, zwane są wystąpieniami zaś su-
maryczna ich liczba określana jest jako licznik
wystąpień . Kontener typu Multiset jest w sta-
nie przechować maksymalnie Integer.MAX_
VALUE wystąpień danego elementu. Interfejs
ten dostarcza oczywiście szereg odpowiednich
metod, np. count() zwracającą licznik wystą-
pień dla zadanego obiektu. GCL dostarcza na-
stępujące implementacje interfejsu Multiset :
ConcurrentHashMultiset , EnumMultiset ,
ForwardingMultiset , HashMultiset ,
ImmutableMultiset , LinkedHashMultiset ,
TreeMultiset . Interfejs BiMap ( mapowanie
dwukierunkowe ) definiuje kolekcję-mapowa-
nie, w które gwarantuje unikalność zarówno
kluczy jak i wartości. Dzięki temu mapowa-
nie dwukierunkowe wspiera tzw. widok od-
W kolejnych podpunktach przyjrzymy się
składnikom GCL wymienionym w pierw-
szych pięciu punktach powyższej listy.
Multimap, Multiset oraz BiMap
Multimap , Multiset i BiMap to nowe interfejsy
kolekcji wprowadzone w ramach Google Col-
lections Library. Interfejs Multimap ( wielomapo-
12
12/2009
Eleganckie i efektywne kolekcje w Javie
406260720.037.png 406260720.038.png 406260720.039.png 406260720.040.png 406260720.001.png 406260720.002.png 406260720.003.png 406260720.004.png 406260720.005.png 406260720.006.png
Google Collections Library
wrotny, w którym wartości mapowania stają
się kluczami zaś klucze – wartościami:
samego klucza. Multiset to idealna kolekcja do
budowania histogramów, więc wykorzystałem
ją w celu zliczania wystąpień znaków zawar-
tych w argumentach przekazanych z linii pole-
ceń. BiMap z kolei został użyty jako słownik, któ-
ry można odpytywać w dwie strony.
mutable ). Stałość obiektu to bardzo ważny idiom
programistyczny (bardzo ciekawa dyskusja na
ten temat przedstawiona jest w świetnej książce
p.t. Effective Java, 2nd Edition ). JCF udostępnia
specjalne opakowania na kolekcje, które czynią je
niemodyfikowalnymi, aczkolwiek zarówno efek-
tywność jak i wygoda związana z ich używaniem
pozostawiają wiele do życzenia. GCL oferuje w
zmian specjalizowane, niemodyfikowalne kolek-
cje, które są znacznie prostsze w użyciu, szybsze
i zużywają mniej pamięci. Listing 2 przedstawia
typowy przykłady wykorzystania niemodyfiko-
walnego zbioru liczb całkowitych w stylu JCF.
Kontrprzykład opary na GCF mieści się w
dwóch linijkach kodu:
BiMap<V,K> inverse();
GCL dostarcza następujące implementacje
interfejsu BiMap : EnumBiMap , EnumHashBiMap ,
HashBiMap oraz ImmutableBiMap .
Na Listingu 1 przedstawione są przykłado-
we przypadki użycia przedstawionych powy-
żej trzech nowych typów kolekcji. W przypad-
ku wielomapowania warto zwrócić uwagę na
to, jak można dodawać różne wartości dla tego
Kolekcje niemodyfikowalne
Często zdarza się, że wiemy z góry, jaka będzie za-
wartość danego kontenera (np. tuż po jego stwo-
rzeniu wypełniamy go specyficznym obiektami)
i chcielibyśmy aby reszta naszego programu trak-
towała go jako byt niemodyfikowalny (ang. im-
Listing 1. Proste przypadki użycia kontenerów Multimap, Multiset i BiMap
import com.google.common.collect.* ;
import java.util.List ;
public class GclListing1 {
public static void main ( String [] args )
{
Multimap < String , Integer > mmap = ArrayListMultimap . create ();
mmap . put ( "odd" , 1 );
mmap . put ( "odd" , 3 );
mmap . put ( "odd" , 5 );
mmap . put ( "odd" , 7 );
mmap . put ( "even" , 2 );
mmap . put ( "even" , 4 );
mmap . put ( "even" , 6 );
mmap . put ( "even" , 8 );
System . out . println ( mmap );
System . out . println ( mmap . get ( "even" ));
Multiset < Character > histogram = HashMultiset . create ();
for ( String arg : args )
for ( int i = 0 ; i < arg . length (); ++ i )
histogram . add ( arg . charAt ( i ));
System . out . println ( histogram );
BiMap < String , String > en2PlDict = HashBiMap . create ();
en2PlDict . put ( "bird" , "ptak" );
en2PlDict . put ( "sky" , "niebo" );
en2PlDict . put ( "star" , "gwiazda" );
en2PlDict . put ( "bell" , "dzwon" );
en2PlDict . put ( "tree" , "drzewo" );
en2PlDict . put ( "gate" , "brama" );
en2PlDict . put ( "house" , "dom" );
System . out . println ( en2PlDict );
System . out . println ( en2PlDict . inverse ());
}
}
public static inal ImmutableSet<Integer>
MY_HAPPY_NUMBERS
= ImmutableSet.of(0, 1, 3, 7, 13, 44);
Komentarz porównujący te dwa fragmenty ko-
du wydaje się być zbyteczny... GCF udostępnia
cały szereg niemodyikowalnych kontenerów,
pełna ich lista jest dostępna w dokumentacji
API biblioteki (patrz: ramka W sieci ).
Klasy użytkowe:
Lists, Sets i Maps
Trzy użytkowe klasy wymienione w tytule niniej-
szego podpunktu oferują zestawy statycznych
metod służących przede wszystkim do tworzenie
i manipulacji obiektami kolekcji typu List , Set i
Map . Przeznaczenie tych klas najłatwiej będzie po-
kazać na praktycznym przykładzie. Spójrzmy za-
tem na Listing 3. W pierwszej linijce przedstawio-
nego Listingu konstruujemy nową listę obiektów
typu String . Warto w tym miejscu zauważyć jak
bardzo uproszczony jest ten proces dzięki zasto-
sowaniu metody Lists.newArrayList(...) .
W dalszej kolejności wykorzystujemy algorytm
Lists.transform(...) w celu przekształcenia
zadanej listy zgodnie z przekazanym obiektem
funkcyjnym. W naszym przypadku przekształ-
camy listę napisów na listę odpowiadających im
wartości numerycznych. Miłym dodatkiem w
bibliotece GCL jest klasa Joiner , która pozwa-
la w łatwy sposób przekształcać zadany kontener
w pożądany napis. Po uruchomieniu programu
przedstawionego na Listingu 3 otrzymamy na-
stępujący wynik:
Listing 2. Typowy przykład wykorzystania niemodyikowalnego zbioru liczb całkowitych w stylu JCF
public static inal Set < Integer > MY_HAPPY_NUMBERS ;
static {
Set < Integer > set = new HashSet < Integer >();
set . add ( 0 );
set . add ( 1 );
set . add ( 3 );
set . add ( 7 );
set . add ( 13 );
set . add ( 44 );
MY_HAPPY_NUMBERS = Collections . unmodiiableSet ( set );
}
3, 5, 7
W klasach Lists , Sets i Maps znaleźć można ca-
ły szereg operacji ułatwiających życie programi-
sty pracującego z kontenerami w Javie – zain-
teresowanych Czytelników odsyłam do doku-
mentacji API biblioteki (patrz: ramka W sieci ).
Klasy użytkowe:
Iterators i Iterables
Na nieco podobnej zasadzie jak w przypad-
ku klas użytkowych opisanych w poprzednim
www.sdjournal.org
13
406260720.007.png 406260720.008.png 406260720.009.png 406260720.010.png 406260720.011.png 406260720.012.png 406260720.013.png 406260720.014.png 406260720.015.png 406260720.016.png 406260720.017.png
Biblioteka miesiąca
podpunkcie, klasy Iterators i Iterables
z biblioteki GCL udostępniają szereg metod
– tym razem odpowiedzialnych za uprosz-
czenie procesu iteracji po kolekcjach elemen-
tów. Spójrzmy zatem na Listing 4, który za-
wiera próbkę możliwości zastosowania klasy
Iterables .
Tym razem wykorzystujemy statycz-
ną metodę filter() , która filtruje zada-
ną kolekcję i zwraca drugą kolekcję, w któ-
rej wszystkie obiekty spełniają zadany pre-
dykat. Oprócz operacji filtrowania, klasa
Iterables pozwala wykonywać łączenie ko-
lekcji ( concat ), wyszukiwanie elementów
w kolekcji ( find ), zliczanie elementów w ko-
lekcji ( frequency ) i wiele, wiele innych. Po-
dobnie działa klasa Iterators – tyle, że za-
miast obiektów typu Iterable przyjmuje
obiekty typu Iterator .
Klasa Ordering
Klasa Ordering jest jednym z ciekawszych roz-
wiązań zaaplikowanych w GCL. Pozwala ona
w łatwy i efektywny sposób łączyć różne kom-
paratory w celu wykonywaniu elastycznych po-
równań elementów przechowywanych w kon-
tenerach. Załóżmy, że mając prostą (a wręcz
podręcznikową) klasę Person (patrz: Listing 5)
chcielibyśmy mieć możliwość uszeregowania
obiektów tej klasy w kontekście poszczególnych
jej składowych pól. Zadanie to można bardzo ła-
two zrealizować przy pomocy GCL. Spójrzmy
na Listing 6.
W pierwszej kolejności tworzymy listę
obiektów klasy Person . Następnie konstruuje-
my instancje dwóch komparatorów: pierwszy
z nich porównuje obiekty według nazwiska zaś
drugi – według imienia danej osoby. Ostatnie
dwie linijki pokazują jak można wykorzystać
klasę Ordering . Szczególną uwagę proponuję
zwrócić na dwie ostatnie linijki tego przykła-
du, a w szczególności na fragment:
ordering = ordering.reverse().compound(irs
tNameComparator);
W tym miejscu tworzymy uszeregowanie
odwrotne do istniejącego i dodatkowo, przy
pomocy metody compound , dołączamy do
niego drugi komparator, który będzie brany
pod uwagę w sytuacji, gdy pierwszy kompa-
rator stwierdzi iż obiekty są identyczne.
Jako pożyteczne zadanie dla dociekliwych
Czytelników pozostawiam rozpisanie sobie na
kartce jak będzie wyglądać wyjście przedstawio-
nego programu, a następnie uruchomienie go
na komputerze i zweryfikowanie wyników.
Podsumowanie
W ramach podsumowania niniejszego artyku-
łu przedstawię główne powody, które (w mojej
opinii) sprawiają, że Google Collection Library
to rozwiązanie godne ze wszech miar uwagi:
Listing 3. Przykład wykorzystania klasy Lists
import com.google.common.base.Function ;
import com.google.common.base.Joiner ;
import com.google.common.collect.Lists ;
import java.util.List ;
public class GclListing3 {
public static void main ( String [] args )
{
List < String > strList = Lists . newArrayList ( "3" , "5" , "7" );
List < Integer > intList = Lists . transform ( strList , new Function < String ,
Integer >() {
public Integer apply ( String from ) {
return Integer . parseInt ( from );
}
});
System . out . println ( Joiner . on ( ", " ). join ( intList ));
}
}
• Dojrzałość: w momencie pisania niniej-
szego tekstu GLC dostępna jest w wer-
sji 1.0 RC3 i z tego co obiecują autorzy
– na tym etapie interfejs biblioteki jest
już bardzo stabilny. Interfejs ten będzie
zamrożony w momencie gdy biblioteka
oznaczona będzie wersją 1.0.
• Stabilność i niezawodność: niewiele jest
na świecie miejsc, w których bibliote-
ka kontenerów możne być tak dobrze
przetestowana jak w Google; korzystając
z GCL mamy pewność, iż rozwiązanie
to zostało intensywnie zweryikowane
w najbardziej ekstremalnych warunkach
z możliwych (przetwarzanie olbrzymich
ilości danych w przeróżnych konigura-
cjach), a przy jego projektowaniu, budo-
waniu i optymalizowaniu brali udział
najlepsi specjaliści z branży.
• Spójność z JCF: w odróżnieniu od innych,
podobnych rozwiązań (np. Jakarta Com-
mons Collections) GLC zachowuje peł-
ną kompatybilność z Java Collection Fram-
work – stanowi niejako naturalne rozsze-
rzenie tej biblioteki i kontynuację ilozoii,
którą zapoczątkowali inżynierowie Sun'a.
W szczególności – obsługa typów uogól-
nionych (ang. generics ) jest w GCL zaimple-
mentowana w identyczny sposób jak w JCF.
• Lekkość, niezależność i elastyczność: pacz-
ka z biblioteką waży około 500kB, przy
czym GCL jest rozwiązaniem niezależ-
nym (nie wymaga instalacji 15 innych bi-
bliotek od Google...) i elastycznym (łatwo
dopasować je do swoich potrzeb).
• Dokumentacja: jest pełna i profesjonal-
nie przygotowana. Gorąco polecam jej
przeczytanie – chociażby w celu zapo-
Listing 4. Przykład wykorzystania klasy Iterables
import com.google.common.base.Joiner ;
import com.google.common.base.Predicate ;
import com.google.common.collect.* ;
import java.util.List ;
public class GclListing4 {
public static void main ( String [] args ) {
List < String > names = Lists . newArrayList (
"Ola" ,
"Agnieszka" ,
"Monika" ,
null );
Iterable < String > iltered = Iterables . ilter (
names , new Predicate < String >() {
public boolean apply ( String input ) {
return input == null || input . startsWith ( "O" );
}
});
System . out . println ( Joiner . on ( " and " ). useForNull ( "Rafal" ). join ( iltered ));
}
}
14
12/2009
406260720.018.png 406260720.019.png 406260720.020.png 406260720.021.png 406260720.022.png 406260720.023.png 406260720.024.png
 
Google Collections Library
znania się ze wszystkim komponentami
dostępnymi w ramach GCL.
• Licencja: Apache License 2.0, która daje
możliwość wykorzystania GCL w komer-
cyjnych projektach bez żadnych ograniczeń.
przez Google. W momencie pisania tego arty-
kułu Guava składa się z trzech pakietów:
dobrze współpracuje z Guava i równoległe ko-
rzystanie z tych dwóch rozwiązań daje bar-
dzo dobre efekty. Jednakże jest to temat na od-
dzielny artykuł, który w niedługim czasie Czy-
telnicy SDJ będą mogli przeczytać na łamach
kolumny Biblioteka Miesiąca. Na ten moment
zachęcam do eksperymentów z Google Collec-
tion Library i dziękuję za czas poświęcony na
przeczytanie powyższego tekstu.
com.google.common.primitives
com.google.common.io
com.google.common.util.concurrent
Co więcej – według zapowiedzi autorów, Go-
ogle Collection Library stanie się w niedługim
czasie częścią bibliteki Guava – łączącej w so-
bie cały szereg użytecznych bibliotek i kom-
ponentów ogólnego użytku wypracowanych
Google Collection Library ma zostać dołączo-
na do tego grona w momencie uzyskania statu-
su pełnej wersji 1.0 i zamrożenia API. Na ten
moment wiadomo już jednak, że GCF bardzo
RAFAŁ KOCISZ
Pracuje na stanowisku Dyrektora Technicznego
w irmie Gamelion, wchodzącej w skład Grupy
BLStream. Rafał specjalizuje się w technologiach
związanych z produkcją oprogramowania na plat-
formy mobilne, ze szczególnym naciskiem na two-
rzenie gier. Grupa BLStream powstała, by efektyw-
niej wykorzystywać potencjał dwóch szybko roz-
wijających się producentów oprogramowania –
BLStream i Gamelion. Firmy wchodzące w skład
grupy specjalizują się w wytwarzaniu oprogramo-
wania dla klientów korporacyjnych, w rozwiąza-
niach mobilnych oraz produkcji i testowaniu gier.
Kontakt z autorem: rafal.kocisz@game-lion.com
W Sieci
http://code.google.com/p/google-collections/ – strona domowa Google Collection Library
http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?http://google-collectio
ns.googlecode.com/svn/trunk/javadoc/com/google/common/collect/package-summary.html
dokumentacja API biblioteki GCL
http://www.javalobby.org/articles/google-collections/ – wywiad z autorami GCL przedstawiają-
cy genezę biblioteki
http://publicobject.com/2007/09/series-recap-coding-in-small-with.html – szereg praktycznych
przykładów wykorzystania GCL
http://www.youtube.com/watch?v=ZeO_J2OcHYM – video-prezentacja biblioteki (część pierwsza)
http://www.youtube.com/watch?v=9ni_KEkHfto – video-prezentacja biblioteki (część druga)
Listing 5. Implementacja przykładowej klasy Person
public class Person {
private String irstName ;
private String lastName ;
public Person ( String irstName , String lastName ) {
this . setFirstName ( irstName );
this . setLastName ( lastName );
}
public String getFirstName () {
return irstName ;
}
public void setFirstName ( String irstName ) {
this . irstName = irstName ;
}
public String getLastName () {
return lastName ;
}
public void setLastName ( String lastName ) {
this . lastName = lastName ;
}
@Override
public String toString () {
return getFirstName () + " " + getLastName ();
}
}
Listing 6. Przykład wykorzystania klasy Ordering
import com.google.common.base.Joiner ;
import com.google.common.base.Predicate ;
import com.google.common.collect.* ;
import java.util.Comparator ;
import java.util.List ;
public class GclListing6 {
public static void main ( String [] args ) {
List < Person > persons = Lists . newArrayList (
new Person ( "Jan" , "Kowalski" ),
new Person ( "Adam" , "Malinowski" ),
null ,
new Person ( "Tadeusz" , "Nowak" ),
null ,
new Person ( "Zenon" , "Kowalski" ));
Comparator < Person > lastNameComparator = new
Comparator < Person >() {
public int compare ( Person p1 , Person p2 ) {
return p1 . getLastName (). compareTo ( p2 . getLas
tName ());
}
};
Comparator < Person > irstNameComparator = new
Comparator < Person >() {
public int compare ( Person p1 , Person p2 ) {
return p1 . getFirstName (). compareTo ( p2 . getFi
rstName ());
}
};
Ordering < Person > ordering = Ordering . from ( lastNameC
omparator );
System . out . println ( ordering . nullsLast (). sortedCopy (
persons ));
ordering = ordering . reverse (). compound ( irstNameCom
parator );
System . out . println ( ordering . nullsLast (). sortedCopy (
persons ));
}
}
www.sdjournal.org
15
406260720.025.png 406260720.026.png 406260720.027.png 406260720.028.png 406260720.029.png 406260720.030.png 406260720.031.png 406260720.032.png 406260720.033.png 406260720.034.png 406260720.035.png 406260720.036.png
 
Zgłoś jeśli naruszono regulamin