Перейти к основному содержимому

Коллекции Apache Commons против Google Guava

· 8 мин. чтения

1. Обзор

В этом руководстве мы сравним две библиотеки с открытым исходным кодом на основе Java: Apache Commons и Google Guava . Обе библиотеки имеют богатый набор функций с множеством служебных API, в основном в области коллекций и ввода-вывода.

Для краткости здесь мы опишем лишь несколько наиболее часто используемых из структуры коллекций вместе с примерами кода. Мы также увидим краткое изложение их различий.

Кроме того, у нас есть сборник статей для глубокого погружения в различные общие ресурсы и утилиты Guava .

2. Краткая история двух библиотек

Google Guava — это проект Google, в основном разработанный инженерами организации, хотя сейчас он находится в открытом доступе. Основной мотивацией для его запуска было включение дженериков, представленных в JDK 1.5 , в Java Collections Framework или JCF и расширение его возможностей.

С момента своего создания библиотека расширила свои возможности и теперь включает в себя графы, функциональное программирование, объекты диапазона, кэширование и манипулирование строками .

Apache Commons начинался как проект в Джакарте, чтобы дополнить основной API коллекций Java, и в конечном итоге стал проектом Apache Software Foundation. За прошедшие годы он расширился до обширного репертуара многократно используемых компонентов Java в различных других областях, включая (но не ограничиваясь) создание образов, ввод-вывод, криптографию, кэширование, работу в сети, проверку и объединение объектов.

Поскольку это проект с открытым исходным кодом, разработчики из сообщества Apache продолжают дополнять эту библиотеку, чтобы расширить ее возможности. Однако они уделяют большое внимание поддержанию обратной совместимости .

3. Зависимость от Maven

Чтобы включить Guava, нам нужно добавить его зависимость в наш pom.xml :

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>

Информацию о последней версии можно найти на Maven .

Для Apache Commons все немного иначе. В зависимости от утилиты, которую мы хотим использовать, мы должны добавить именно ее. Например, для коллекций нам нужно добавить:

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>

В наших примерах кода мы будем использовать commons-collections4 .

Давайте перейдем к веселой части сейчас!

4. Двунаправленные карты

Карты, доступ к которым можно получить с помощью их ключей, а также значений, называются двунаправленными картами. JCF не имеет этой функции.

Давайте посмотрим, как наши две технологии предлагают их. В обоих случаях мы возьмем пример дней недели, чтобы получить название дня по его номеру и наоборот.

4.1. BiMap Гуавы

Guava предлагает интерфейс — BiMap , как двунаправленную карту. Его можно создать с помощью одной из его реализаций EnumBiMap , EnumHashBiMap , HashBiMap или ImmutableBiMap .

Здесь мы используем HashBiMap :

BiMap<Integer, String> daysOfWeek = HashBiMap.create();

Его заполнение похоже на любую карту в 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");

И вот несколько тестов JUnit, подтверждающих эту концепцию:

@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 от Apache ``

Точно так же Apache предоставляет нам свой интерфейс BidiMap :

BidiMap<Integer, String> daysOfWeek = new TreeBidiMap<Integer, String>();

Здесь мы используем TreeBidiMap . Однако существуют и другие реализации, такие как DualHashBidiMap и DualTreeBidiMap .

Чтобы заполнить его, мы можем указать значения, как мы сделали для BiMap выше.

Его использование также очень похоже:

@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));
}

В нескольких простых тестах производительности эта двунаправленная карта отставала от своего аналога Guava только во вставках. Было намного быстрее извлекать ключи, а также значения .

5. Сопоставьте ключи с несколькими значениями

Для случая использования, когда мы хотели бы сопоставить несколько ключей с разными значениями, например, набор продуктовых корзин для фруктов и овощей, две библиотеки предлагают нам уникальные решения.

5.1. Мультикарта Гуавы

Во-первых, давайте посмотрим, как создать и инициализировать MultiMap :

Multimap<String, String> groceryCart = ArrayListMultimap.create();

groceryCart.put("Fruits", "Apple");
groceryCart.put("Fruits", "Grapes");
groceryCart.put("Fruits", "Strawberries");
groceryCart.put("Vegetables", "Spinach");
groceryCart.put("Vegetables", "Cabbage");

Затем мы используем пару тестов JUnit, чтобы увидеть его в действии:

@Test
public void givenMultiValuedMap_whenFruitsFetched_thenFruitsReturned() {
List<String> fruits = Arrays.asList("Apple", "Grapes", "Strawberries");
assertEquals(fruits, groceryCart.get("Fruits"));
}

@Test
public void givenMultiValuedMap_whenVeggiesFetched_thenVeggiesReturned() {
List<String> veggies = Arrays.asList("Spinach", "Cabbage");
assertEquals(veggies, groceryCart.get("Vegetables"));
}

Кроме того, MultiMap дает нам возможность удалить данную запись или весь набор значений с карты :

@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());
}

Как мы видим, здесь мы сначала удалили Apple из набора Fruits , а затем удалили весь набор Fruits .

5.2. Многозначная карта Apache ``

Опять же, давайте начнем с создания экземпляра MultiValuedMap :

MultiValuedMap<String, String> groceryCart = new ArrayListValuedHashMap<>();

Поскольку его заполнение такое же, как мы видели в предыдущем разделе, давайте быстро рассмотрим его использование:

@Test
public void givenMultiValuedMap_whenFruitsFetched_thenFruitsReturned() {
List<String> fruits = Arrays.asList("Apple", "Grapes", "Strawberries");
assertEquals(fruits, groceryCart.get("Fruits"));
}

@Test
public void givenMultiValuedMap_whenVeggiesFetched_thenVeggiesReturned() {
List<String> veggies = Arrays.asList("Spinach", "Cabbage");
assertEquals(veggies, groceryCart.get("Vegetables"));
}

Как мы видим, его использование тоже самое!

Однако в этом случае у нас нет возможности удалить одну запись, например, Apple из Fruits. Мы можем удалить только весь набор Fruits :

@Test
public void givenMultiValuedMap_whenFuitsRemoved_thenVeggiesPreserved() {
assertEquals(5, groceryCart.size());

groceryCart.remove("Fruits");
assertEquals(2, groceryCart.size());
}

6. Сопоставьте несколько ключей с одним значением

Здесь мы возьмем пример широты и долготы, которые будут сопоставлены с соответствующими городами:

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");

Теперь посмотрим, как этого добиться.

6.1. Стол Гуавы ``

Guava предлагает свою таблицу , которая удовлетворяет приведенному выше варианту использования:

Table<String, String, String> cityCoordinates = HashBasedTable.create();

И вот некоторые обычаи, которые мы можем из этого извлечь:

@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"));
}

Как мы видим, мы можем получить представление Set для строк, столбцов и значений.

Таблица также предлагает нам возможность запрашивать ее строки или столбцы .

Давайте рассмотрим таблицу фильмов, чтобы продемонстрировать это:

Table<String, String, String> 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");

И вот несколько примеров самоочевидных поисков, которые мы можем выполнить в нашей таблице фильмов : ``

@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"));
}

Однако Table ограничивает нас сопоставлением только двух ключей со значением . У нас пока нет альтернативы в Guava для сопоставления более двух ключей с одним значением.

6.2. MultiKeyMap Apache ``

Возвращаясь к нашему примеру с cityCoordinates , вот как мы можем манипулировать им с помощью MultiKeyMap :

@Test
public void givenCoordinatesMultiKeyMap_whenQueried_thenOK() {
MultiKeyMap<String, String> cityCoordinates = new MultiKeyMap<String, String>();

// 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());
}

Как видно из приведенного выше фрагмента кода, чтобы получить те же утверждения, что и для таблицы Guava , нам пришлось перебрать MultiKeyMap .

Однако MultiKeyMap также предлагает возможность сопоставления более двух ключей со значением . Например, это дает нам возможность отображать дни недели как будни или выходные:

@Test
public void givenDaysMultiKeyMap_whenFetched_thenOK() {
days = new MultiKeyMap<String, String>();
days.put("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Weekday");
days.put("Saturday", "Sunday", "Weekend");

assertFalse(days.get("Saturday", "Sunday").equals("Weekday"));
}

7. Коллекции Apache Commons против Google Guava

По словам его инженеров , Google Guava родилась из-за необходимости использовать дженерики в библиотеке, которых Apache Commons не предлагал . Он также соответствует требованиям API коллекций к тройнику. Еще одним важным преимуществом является то, что он находится в активной разработке и часто выходят новые версии.

Однако Apache предлагает преимущество, когда речь идет о производительности при извлечении значения из коллекции. Тем не менее, Guava по-прежнему выигрывает с точки зрения времени вставки.

Хотя в наших примерах кода мы сравнивали только API-интерфейсы коллекций, Apache Commons в целом предлагает гораздо больший набор функций по сравнению с Guava .

8. Заключение

В этом руководстве мы сравнили некоторые функции, предлагаемые Apache Commons и Google Guava, особенно в области структуры коллекций.

Здесь мы лишь коснулись того, что могут предложить две библиотеки.

Более того, это не сравнение «или-или». Как продемонстрировали наши примеры кода, каждая из двух функций уникальна, и могут быть ситуации, когда обе могут сосуществовать .

Как всегда, исходный код доступен на GitHub .