Guava: Google-ova otvorena Java alatka, neverovatno moćna
01,Prošlost i sadašnjost
Zdravo, ja sam Guava.
Otvorenog sam koda od strane kompanije Google i trenutno na GitHub-u imam 39.9k vernih pratilaca, što svedoči o mojoj popularnosti.

U mom se telu uglavnom nalaze ovi često korišćeni moduli: kolekcije [collections], keširanje [caching], podrška za primitivne tipove [primitives support], biblioteke za istovremeni rad [concurrency libraries], uobičajene anotacije [common annotations], obrada stringova [string processing], I/O itd. U novoj verziji JDK-a sam direktno ugrađen, pa možete zamisliti koliko sam dobar — ne mogu a da se ne ponosim.
Da kažem ovako: kada dobro naučite kako da me koristite, bićete srećniji u programiranju i pisaćete elegantniji kod!
02,Uključivanje Guava-e
Ako želite da me koristite u Maven projektu, prvo morate u fajl pom.xml dodati moju zavisnost.
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>Jedan zahtev: verzija JDK-a mora biti 8 ili novija.
03,Osnovne alatke
Doug Lea, autor paketa java.util.concurrent, jednom je rekao: "null je zaista užasan". Tony Hoare, dobitnik Turingove nagrade i autor algoritma brzog sortiranja, a ujedno i tvorac null-a, izrekao je nešto slično: "Korišćenje null-a koštalo me je milijardu dolara." Zbog toga ja koristim Optional da predstavim objekat koji može biti null.

Primer koda je prikazan ispod.
Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // vraća true
possible.get(); // vraća 5Moj stariji brat Java dodao je klasu Optional u JDK 8, očigledno pozajmljenu od mene, ali se njegova i moja donekle razlikuju.
- Moj Optional je abstract, što znači da može imati objekte podklasa; bratov je final, što znači da nema podklasa.
- Moj Optional implementira interfejs Serializable i može se serijalizovati; bratov ne može.
- Neke od mojih metoda se takođe razlikuju od bratovih.
Korišćenje Optional-a, pored toga što daje null-u semantiku i povećava čitljivost, ima najveću prednost u tome što predstavlja zaštitu od nemara: Optional vas tera da aktivno razmišljate o situaciji kada referenca nedostaje, jer referencu morate eksplicitno izvući iz Optional-a.
Pored Optional-a, pružam i:
- proveru argumenata
- uobičajene Object metode, poput Objects.equals i Objects.hashCode; klasa Objects uvedena u JDK 7 pruža iste metode i, naravno, inspiraciju je crpila iz mene.
- moćnije komparatore
04,Kolekcije
Prvo da kažem nešto o tome zašto su potrebne nepromenljive kolekcije.
- Garantuju bezbednost niti. U istovremenim programima, upotreba nepromenljivih kolekcija i obezbeđuje bezbednost niti i znatno povećava efikasnost pri istovremenom radu (u poređenju sa pristupom zasnovanim na zaključavanju).
- Ako objekat ne mora da podržava operacije izmene, nepromenljiva kolekcija će sačuvati troškove prostora i vremena.
- Može se tretirati kao konstanta, i objekti u kolekciji kasnije neće biti izmenjeni.
U poređenju sa nepromenljivim kolekcijama koje pruža JDK, moj Immutable je zaista nepromenljiv. Zašto to kažem? Pogledajte sledeći primer.
Sledeći kod koristi Collections.unmodifiableList(list) iz JDK-a da dobije kolekciju koja se ne može menjati, unmodifiableList.
List list = new ArrayList();
list.add("Lei Jun");
list.add("Steve Jobs");
List unmodifiableList = Collections.unmodifiableList(list);
unmodifiableList.add("Ma Yun");Pri pokretanju koda pojaviće se sledeći izuzetak:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.Collections$UnmodifiableCollection.add(Collections.java:1060)
at com.itwanger.guava.NullTest.main(NullTest.java:29)Dobro, pri izvršavanju unmodifiableList.add() bačen je izuzetak UnsupportedOperationException, što ukazuje na to da Collections.unmodifiableList() vraća nepromenljivu kolekciju. Ali je li zaista tako?
Možete zameniti unmodifiableList.add() sa list.add().
List list = new ArrayList();
list.add("Lei Jun");
list.add("Steve Jobs");
List unmodifiableList = Collections.unmodifiableList(list);
list.add("Ma Yun");Kada ponovo izvršite, program ne prijavljuje grešku i primetićete da je u unmodifiableList zaista jedan element više. Šta to znači?
Collections.unmodifiableList(…) ne implementira zaista nepromenljivu kolekciju: kada se izvorna kolekcija izmeni, elementi u nepromenljivoj kolekciji se menjaju zajedno sa njom.
Ja ne pravim takvu grešku — pogledajte sledeći kod.
List<String> stringArrayList = Lists.newArrayList("Lei Jun","Steve Jobs");
ImmutableList<String> immutableList = ImmutableList.copyOf(stringArrayList);
immutableList.add("Ma Yun");Pri pokušaju immutableList.add() biće bačen UnsupportedOperationException. U izvornom kodu sam metod add() već označio kao zastareli.
/**
* Guaranteed to throw an exception and leave the collection unmodified.
*
* @throws UnsupportedOperationException always
* @deprecated Unsupported operation.
*/
@CanIgnoreReturnValue
@Deprecated
@Override
public final boolean add(E e) {
throw new UnsupportedOperationException();
}Kada pokušate stringArrayList.add() da izmenite izvornu kolekciju, immutableList se zbog toga neće promeniti.
Pored nepromenljivih kolekcija, pružam i nove tipove kolekcija, na primer:
- Multiset, u koji se jednaki elementi mogu dodavati više puta. Posmatran kao obična Collection, ponaša se kao neuređeni ArrayList; posmatran kao
Map<E, Integer>, pruža i operacije upita koje zadovoljavaju očekivanja u pogledu performansi. - Multimap, koji lako omogućava mapiranje jednog ključa na više vrednosti.
- BiMap, posebna vrsta Map-e koja pomoću
inverse()može obrnuti mapiranje ključ-vrednost unutarBiMap<K, V>; garantuje jedinstvenost vrednosti, pavalues()vraća Set umesto obične Collection.
05,Obrada stringova
String predstavlja nepromenljiv niz karaktera koji se nakon kreiranja ne može promeniti. U svakodnevnom radu se stringovi koriste vrlo često, a vešto rukovanje njima može znatno povećati našu efikasnost.
Pružam spojnicu — Joiner, koja pomoću separatora može povezati niz stringova. Sledeći kod vratiće "Lei Jun; Steve Jobs", a pomoću metode useForNull(String) možete nekim stringom zameniti null, umesto da null prosto ignorišete kao metodom skipNulls().
Joiner joiner = Joiner.on("; ").skipNulls();
return joiner.join("Lei Jun", null, "Steve Jobs");Pružam i razdvajač — Splitter, koji može razdvojiti niz stringova prema zadatom separatoru.
Splitter.on(',')
.trimResults()
.omitEmptyStrings()
.split("Lei Jun,Steve Jobs,, Chenmo Wang Er");06,Keširanje
Keš je u mnogim scenarijima prilično koristan. Znate da je trošak dobavljanja neke vrednosti visok; naročito kada vrednost treba dobiti više puta, treba razmotriti upotrebu keša.
Cache koji pružam veoma je sličan sa ConcurrentMap, ali nisu potpuno isti. Osnovna razlika je u tome što ConcurrentMap stalno čuva sve dodate elemente dok se eksplicitno ne uklone. Nasuprot tome, moj Cache, radi ograničavanja zauzeća memorije, obično je podešen da automatski odbacuje elemente.
Ako ste voljni da potrošite deo memorijskog prostora radi povećanja brzine, možete predvideti da će neki ključevi biti upitani više od jednom, a ukupna količina podataka u kešu neće preći kapacitet memorije — onda možete koristiti Cache.
Evo primera da osetite kako to izgleda.
@Test
public void testCache() throws ExecutionException, InterruptedException {
CacheLoader cacheLoader = new CacheLoader<String, Animal>() {
// ako se element ne pronađe, poziva se ovde
@Override
public Animal load(String s) {
return null;
}
};
LoadingCache<String, Animal> loadingCache = CacheBuilder.newBuilder()
.maximumSize(1000) // kapacitet
.expireAfterWrite(3, TimeUnit.SECONDS) // vreme isteka
.removalListener(new MyRemovalListener()) // osluškivač uklanjanja
.build(cacheLoader); //
loadingCache.put("pas", new Animal("Wangcai", 1));
loadingCache.put("maca", new Animal("Tom", 3));
loadingCache.put("vuk", new Animal("Huitailang", 4));
loadingCache.invalidate("maca"); // ručno brisanje
Animal animal = loadingCache.get("vuk");
System.out.println(animal);
Thread.sleep(4 * 1000);
// vuk je automatski istekao, dobavljanje vraća null i baca grešku
System.out.println(loadingCache.get("vuk"));
}
/**
* Osluškivač uklanjanja elemenata iz keša
*/
class MyRemovalListener implements RemovalListener<String, Animal> {
@Override
public void onRemoval(RemovalNotification<String, Animal> notification) {
String reason = String.format("key=%s,value=%s,reason=%s", notification.getKey(), notification.getValue(), notification.getCause());
System.out.println(reason);
}
}
class Animal {
private String name;
private Integer age;
public Animal(String name, Integer age) {
this.name = name;
this.age = age;
}
}U CacheLoader-u je redefinisan metod load, koji se poziva kada upit u keš ne pogodi; ovde sam direktno vratio null, što zapravo, kada nema pogotka, baca izuzetak CacheLoader returned null for key.
MyRemovalListener, kao osluškivač kada keširani element istekne, automatski poziva metod onRemoval kada neki element istekne; treba imati na umu da je ovaj metod sinhron, pa ako ovde potraje duže, blokiraće se dok se obrada ne završi.
LoadingCache je glavni objekat za rad sa kešom, a najčešće se koriste njegovi metodi put i get.
07,Alatke za kolekcije
Alatka za kolekcije Lists iz paketa com.google.common.collect je takođe veoma moćna.
Kreiranje prazne kolekcije
Ponekad želimo da kreiramo praznu kolekciju. Tada možemo koristiti metod newArrayList klase Lists, na primer:
List<Integer> list = Lists.newArrayList();Brza inicijalizacija kolekcije
Ponekad želimo u kolekciju odmah da inicijalizujemo neke elemente. Tada možemo koristiti metod newArrayList klase Lists, na primer:
List<Integer> list = Lists.newArrayList(1, 2, 3);Rezultat izvršavanja:
[1, 2, 3]Kartezijev proizvod
Ako želite da izračunate Kartezijev proizvod dve kolekcije, metod cartesianProduct klase Lists može vam pomoći:
List<Integer> list1 = Lists.newArrayList(1, 2, 3);
List<Integer> list2 = Lists.newArrayList(4,5);
List<List<Integer>> productList = Lists.cartesianProduct(list1,list2);
System.out.println(productList);Rezultat izvršavanja:
[[1, 4], [1, 5], [2, 4], [2, 5], [3, 4], [3, 5]]Paginacija
Ako želite jednu veliku kolekciju podeliti u nekoliko manjih kolekcija, možete koristiti metod partition klase Lists:
List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5);
List<List<Integer>> partitionList = Lists.partition(list, 2);
System.out.println(partitionList);Rezultat izvršavanja:
[[1, 2], [3, 4], [5]]U ovom primeru lista ima 5 elemenata; kolekciju list sam po veličini 2 podelio u 3 strane, odnosno u 3 manje kolekcije.
Ovo je jedan od mojih omiljenih metoda i često ga koristim u projektima.
Na primer, imamo zahtev: sada imamo 5000 id-ova i treba pozvati interfejs za grupno pretraživanje korisnika kako bismo dobili podatke o korisnicima. Ali ako biste direktno pretražili 5000 korisnika, vreme odziva interfejsa u jednom pozivu moglo bi biti veoma sporo. Ako se pređe na paginiranu obradu, pri kojoj se svaki put pretražuje samo 500 korisnika i asinhrono pozove interfejs 10 puta, nestaje problem sporog odziva u jednom pozivu.
Obrada toka
Ako želimo da kolekciju pretvorimo u drugi oblik, možemo koristiti metod transform klase Lists. Na primer:
List<String> list = Lists.newArrayList("a","b","c");
List<String> transformList = Lists.transform(list, x -> x.toUpperCase());
System.out.println(transformList);Pretvorio je mala slova u velika.
Obrtanje redosleda
Lists ima metod za obrtanje redosleda, reverse. Na primer:
List<Integer> list = Lists.newArrayList(3, 1, 2);
List<Integer> reverseList = Lists.reverse(list);
System.out.println(reverseList);Rezultat izvršavanja:
[2, 1, 3]Izvorni redosled liste je 312, a nakon obrtanja pomoću metoda reverse postao je 213.
Lists ima i druge korisne alatke; ovde samo bacam mamac, a zainteresovani čitaoci mogu ih detaljnije proučiti.

08,Epilog
Gore su predstavljene funkcije koje smatram najčešće korišćenim; kao jezgro Java razvoja otvorenog koda od kompanije Google, lično smatram da je praktičnost prilično visoka (zar ne? hehe). Kada ga uključite u svoj projekat, ne samo da brzo možete implementirati neke uobičajene funkcije u razvoju, već možete i učiniti kod elegantnijim i sažetijim.
Mislim da je primenljiv na svaki Java projekat; što se tiče ostalih funkcija, poput heširanja, sabirnice događaja, matematičkih operacija i refleksije, to prepuštam vama da otkrijete.

