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 .