Contents

Apache Commons Collections 与 Google Guava

1.概述

在本教程中,我们将比较两个基于 Java 的开源库:Apache CommonsGoogle Guava 。这两个库都具有丰富的功能集,其中包含大量实用程序 API,主要位于集合和 I/O 领域。

为简洁起见,这里我们将仅描述集合框架中最常用的几个以及代码示例。我们还将看到它们之间差异的摘要。

此外,我们还收集了一系列文章,用于深入了解各种commons和Guava 实用程序

2. 两个库的简史

Google Guava 是 Google 的一个项目,主要由该组织的工程师开发,虽然现在已经开源。启动它的主要动机是将 JDK 1.5 中引入的泛型包含到 Java Collections FrameworkJCF 中,并增强其功能。

自成立以来,该库已扩展其功能,现在包括图形、函数式编程、范围对象、缓存和String操作。

Apache Commons 最初是作为 Jakarta 项目的一个补充核心 Java 集合 API 的项目,最终成为 Apache 软件基金会的一个项目。多年来,它已扩展到其他各个领域的大量可重用 Java 组件,包括(但不限于)成像、I/O、密码学、缓存、网络、验证和对象池。

由于这是一个开源项目,来自 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. Guava 的BiMap

Guava 提供了一个接口 - BiMap ,作为双向地图。它可以使用其实现之一 EnumBiMapEnumHashBiMapHashBiMapImmutableBiMap来实例化。

这里我们使用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. Apache 的BidiMap

同样,Apache 为我们提供了它的BidiMap 接口:

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

这里我们使用TreeBidiMap。但是,还有其他实现,例如DualHashBidiMapDualTreeBidiMap

要填充它,我们可以像上面对 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));
}

5. 将键映射到多个值

对于我们想要将多个键映射到不同值的用例,例如水果和蔬菜的购物车集合,这两个库为我们提供了独特的解决方案。

5.1. Guava 的MultiMap

首先,让我们看看如何实例化和初始化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());
}

如我们所见,这里我们首先从Fruits集中移除Apple,然后移除整个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

Guava 提供了满足上述用例的Table

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还为我们提供了查询其行或列的能力

让我们考虑一个电影表来证明这一点:

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

以下是我们可以在movies表上进行的一些示例、不言自明的搜索:

@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. Apache 的 MultiKeyMap

回到我们的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 的Table相同的断言,我们必须遍历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 Collections 与 Google Guava

根据其工程师 的说法,Google Guava 的诞生是出于在库中使用泛型的需要,而 Apache Commons 没有提供。它还遵循 tee 的集合 API 要求。另一个主要优势是它正在积极开发中,新版本经常发布。

但是,在从集合中获取值时,Apache 在性能方面提供了优势。不过,就插入时间而言,番石榴仍然占据优势。

尽管我们只比较了代码示例中的集合 API,但与 Guava 相比,Apache Commons 作为一个整体提供了更大范围的功能