Collezioni Apache Commons vs Google Guava

1. Panoramica

In questo tutorial, confronteremo due librerie open source basate su Java: Apache Commons e Google Guava . Entrambe le librerie hanno un ricco set di funzionalità con molte API di utilità principalmente nelle raccolte e nell'area I / O.

Per brevità, qui descriveremo solo una manciata di quelli più comunemente usati dal framework delle collezioni insieme agli esempi di codice. Vedremo anche un riepilogo delle loro differenze.

Inoltre, abbiamo una raccolta di articoli per un'immersione profonda in vari comuni e utilità Guava .

2. Breve storia delle due biblioteche

Google Guava è un progetto Google, sviluppato principalmente dagli ingegneri dell'organizzazione, sebbene ora sia stato reso open source. La motivazione principale per avviarlo era includere i generici introdotti in JDK 1.5 in Java Collections Framework , o JCF, e migliorarne le capacità.

Fin dal suo inizio, la libreria ha ampliato le sue capacità e ora include grafici, programmazione funzionale, oggetti intervallo, memorizzazione nella cache e manipolazione di stringhe .

Apache Commons è iniziato come un progetto di Jakarta per integrare l'API principale delle raccolte Java e alla fine è diventato un progetto della Apache Software Foundation. Nel corso degli anni, si è espanso in un vasto repertorio di componenti Java riutilizzabili in varie altre aree, tra cui (ma non limitato a) imaging, I / O, crittografia, caching, networking, convalida e pool di oggetti.

Poiché si tratta di un progetto open source, gli sviluppatori della comunità Apache continuano ad aggiungere a questa libreria per espandere le sue capacità. Tuttavia, fanno molta attenzione a mantenere la compatibilità con le versioni precedenti .

3. Dipendenza da Maven

Per includere Guava, dobbiamo aggiungere la sua dipendenza al nostro pom.xml :

 com.google.guava guava 29.0-jre 

Le informazioni sulla versione più recente possono essere trovate su Maven.

Per Apache Commons, è un po 'diverso. A seconda dell'utilità che vogliamo utilizzare, dobbiamo aggiungere quella in particolare. Ad esempio, per le collezioni, dobbiamo aggiungere:

 org.apache.commons commons-collections4 4.4 

Nei nostri esempi di codice, utilizzeremo commons-collections4 .

Passiamo ora alla parte divertente!

4. Mappe bidirezionali

Le mappe a cui è possibile accedere tramite le loro chiavi, così come i valori, sono note come mappe bidirezionali. JCF non dispone di questa funzione.

Vediamo come li offrono le nostre due tecnologie. In entrambi i casi, faremo un esempio di giorni della settimana per ottenere il nome del giorno dato il suo numero e viceversa.

4.1. BiMap di Guava

Guava offre un'interfaccia - BiMap , come mappa bidirezionale. Può essere istanziato con una delle sue implementazioni EnumBiMap , EnumHashBiMap , HashBiMap o ImmutableBiMap .

Qui stiamo usando HashBiMap :

BiMap daysOfWeek = HashBiMap.create();

Il suo popolamento è simile a qualsiasi mappa in Java:

daysOfWeek.put(1, "Monday"); daysOfWeek.put(2, "Tuesday"); daysOfWeek.put(3, "Wednesday"); daysOfWeek.put(4, "Thursday"); daysOfWeek.put(5, "Friday"); daysOfWeek.put(6, "Saturday"); daysOfWeek.put(7, "Sunday");

Ed ecco alcuni test JUnit per dimostrare il concetto:

@Test public void givenBiMap_whenValue_thenKeyReturned() { assertEquals(Integer.valueOf(7), daysOfWeek.inverse().get("Sunday")); } @Test public void givenBiMap_whenKey_thenValueReturned() { assertEquals("Tuesday", daysOfWeek.get(2)); }

4.2. BidiMap di Apache

Allo stesso modo, Apache ci fornisce la sua interfaccia BidiMap :

BidiMap daysOfWeek = new TreeBidiMap();

Qui stiamo usando TreeBidiMap . Tuttavia, ci sei altre implementazioni, come DualHashBidiMap e DualTreeBidiMap così .

Per popolarlo, possiamo inserire i valori come abbiamo fatto per BiMap sopra.

Anche il suo utilizzo è abbastanza simile:

@Test public void givenBidiMap_whenValue_thenKeyReturned() { assertEquals(Integer.valueOf(7), daysOfWeek.inverseBidiMap().get("Sunday")); } @Test public void givenBidiMap_whenKey_thenValueReturned() { assertEquals("Tuesday", daysOfWeek.get(2)); }

In pochi semplici test delle prestazioni, questa mappa bidirezionale è rimasta indietro rispetto alla sua controparte Guava solo negli inserimenti. È stato molto più veloce nel recupero di chiavi e valori .

5. Mappare le chiavi su più valori

Per un caso d'uso in cui vorremmo mappare più chiavi a valori diversi, come una raccolta di carrelli della spesa per frutta e verdura, le due biblioteche ci offrono soluzioni uniche.

5.1. MultiMap di Guava

Per prima cosa, vediamo come istanziare e inizializzare MultiMap :

Multimap groceryCart = ArrayListMultimap.create(); groceryCart.put("Fruits", "Apple"); groceryCart.put("Fruits", "Grapes"); groceryCart.put("Fruits", "Strawberries"); groceryCart.put("Vegetables", "Spinach"); groceryCart.put("Vegetables", "Cabbage");

Quindi, utilizzeremo un paio di test JUnit per vederlo in azione:

@Test public void givenMultiValuedMap_whenFruitsFetched_thenFruitsReturned() { List fruits = Arrays.asList("Apple", "Grapes", "Strawberries"); assertEquals(fruits, groceryCart.get("Fruits")); } @Test public void givenMultiValuedMap_whenVeggiesFetched_thenVeggiesReturned() { List veggies = Arrays.asList("Spinach", "Cabbage"); assertEquals(veggies, groceryCart.get("Vegetables")); } 

Additionally, MultiMap gives us the ability to remove a given entry or an entire set of values from the map:

@Test public void givenMultiValuedMap_whenFuitsRemoved_thenVeggiesPreserved() { assertEquals(5, groceryCart.size()); groceryCart.remove("Fruits", "Apple"); assertEquals(4, groceryCart.size()); groceryCart.removeAll("Fruits"); assertEquals(2, groceryCart.size()); }

As we can see, here we first removed Apple from the Fruits set and then removed the entire Fruits set.

5.2. Apache's MultiValuedMap

Again, let's begin with instantiating a MultiValuedMap:

MultiValuedMap groceryCart = new ArrayListValuedHashMap();

Since populating it is the same as we saw in the previous section, let's quickly look at the usage:

@Test public void givenMultiValuedMap_whenFruitsFetched_thenFruitsReturned() { List fruits = Arrays.asList("Apple", "Grapes", "Strawberries"); assertEquals(fruits, groceryCart.get("Fruits")); } @Test public void givenMultiValuedMap_whenVeggiesFetched_thenVeggiesReturned() { List veggies = Arrays.asList("Spinach", "Cabbage"); assertEquals(veggies, groceryCart.get("Vegetables")); }

As we can see, its usage is also the same!

However, in this case, we don't have the flexibility to remove a single entry, such as Apple from Fruits.We can only remove the entire set of Fruits:

@Test public void givenMultiValuedMap_whenFuitsRemoved_thenVeggiesPreserved() { assertEquals(5, groceryCart.size()); groceryCart.remove("Fruits"); assertEquals(2, groceryCart.size()); }

6. Map Multiple Keys to One Value

Here, we'll take an example of latitudes and longitudes to be mapped to respective cities:

cityCoordinates.put("40.7128° N", "74.0060° W", "New York"); cityCoordinates.put("48.8566° N", "2.3522° E", "Paris"); cityCoordinates.put("19.0760° N", "72.8777° E", "Mumbai");

Now, we'll see how to achieve this.

6.1. Guava's Table

Guava offers its Table that satisfies the above use case:

Table cityCoordinates = HashBasedTable.create();

And here are some usages we can derive out of it:

@Test public void givenCoordinatesTable_whenFetched_thenOK() { List expectedLongitudes = Arrays.asList("74.0060° W", "2.3522° E", "72.8777° E"); assertArrayEquals(expectedLongitudes.toArray(), cityCoordinates.columnKeySet().toArray()); List expectedCities = Arrays.asList("New York", "Paris", "Mumbai"); assertArrayEquals(expectedCities.toArray(), cityCoordinates.values().toArray()); assertTrue(cityCoordinates.rowKeySet().contains("48.8566° N")); }

As we can see, we can get a Set view of the rows, columns, and values.

Table also offers us the ability to query its rows or columns.

Let's consider a movie table to demonstrate this:

Table movies = HashBasedTable.create(); movies.put("Tom Hanks", "Meg Ryan", "You've Got Mail"); movies.put("Tom Hanks", "Catherine Zeta-Jones", "The Terminal"); movies.put("Bradley Cooper", "Lady Gaga", "A Star is Born"); movies.put("Keenu Reaves", "Sandra Bullock", "Speed"); movies.put("Tom Hanks", "Sandra Bullock", "Extremely Loud & Incredibly Close");

And here are some sample, self-explanatory searches that we can do on our moviesTable:

@Test public void givenMoviesTable_whenFetched_thenOK() { assertEquals(3, movies.row("Tom Hanks").size()); assertEquals(2, movies.column("Sandra Bullock").size()); assertEquals("A Star is Born", movies.get("Bradley Cooper", "Lady Gaga")); assertTrue(movies.containsValue("Speed")); }

However, Table limits us to map only two keys to a value. We don't have an alternative as yet in Guava to map more than two keys to a single value.

6.2. Apache's MultiKeyMap

Coming back to our cityCoordinates example, here's how we can manipulate it using MultiKeyMap:

@Test public void givenCoordinatesMultiKeyMap_whenQueried_thenOK() { MultiKeyMap cityCoordinates = new MultiKeyMap(); // populate with keys and values as shown previously List expectedLongitudes = Arrays.asList("72.8777° E", "2.3522° E", "74.0060° W"); List longitudes = new ArrayList(); cityCoordinates.forEach((key, value) -> { longitudes.add(key.getKey(1)); }); assertArrayEquals(expectedLongitudes.toArray(), longitudes.toArray()); List expectedCities = Arrays.asList("Mumbai", "Paris", "New York"); List cities = new ArrayList(); cityCoordinates.forEach((key, value) -> { cities.add(value); }); assertArrayEquals(expectedCities.toArray(), cities.toArray()); }

As we can see from the above code snippet, to arrive at the same assertions as for Guava's Table, we had to iterate over the MultiKeyMap.

However, MultiKeyMap also offers the possibility to map more than two keys to a value. For example, it gives us the ability to map days of the week as weekdays or weekends:

@Test public void givenDaysMultiKeyMap_whenFetched_thenOK() { days = new MultiKeyMap(); days.put("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Weekday"); days.put("Saturday", "Sunday", "Weekend"); assertFalse(days.get("Saturday", "Sunday").equals("Weekday")); }

7. Apache Commons Collections vs. Google Guava

As per its engineers, Google Guava was born out of the need to use generics in the library, which Apache Commons didn't offer. It also follows the collections API requirements to the tee. Another major advantage is that it's in active development with new releases coming out frequently.

However, Apache offers an edge when it comes to performance while fetching a value from a collection. Guava still takes the cake though, in terms of insertion times.

Although we compared only the collections APIs in our code samples, Apache Commons as a whole offers a much bigger gamut of features as compared to Guava.

8. Conclusion

In this tutorial, we compared some of the functionality offered by Apache Commons and Google Guava, specifically in the area of the collections framework.

Qui, abbiamo semplicemente scalfito la superficie di ciò che le due biblioteche hanno da offrire.

Inoltre, non è un confronto. Come hanno dimostrato i nostri esempi di codice, esistono caratteristiche uniche per ciascuna delle due e possono esserci situazioni in cui entrambe possono coesistere .

Come sempre, il codice sorgente è disponibile su GitHub.