1. 概要

このチュートリアルでは、2つのJavaベースのオープンソースライブラリであるApacheCommonsとGoogleGuavaを比較します。 どちらのライブラリにも、主にコレクションとI/O領域に多くのユーティリティAPIを備えた豊富な機能セットがあります。

簡潔にするために、ここでは、コレクションフレームワークから最も一般的に使用されるもののほんの一握りをコードサンプルとともに説明します。 それらの違いの要約も表示されます。

さらに、さまざまなコモンズとGuavaユーティリティを深く掘り下げるための記事のコレクションがあります。

2. 2つの図書館の簡単な歴史

Google GuavaはGoogleプロジェクトであり、現在はオープンソースになっていますが、主に組織のエンジニアによって開発されています。 それを開始する主な動機は、JDK1.5で導入されたジェネリックをJavaCollectionsFrameworkまたはJCFに組み込み、その機能を強化することでした。

開始以来、ライブラリはその機能を拡張し、グラフ、関数型プログラミング、範囲オブジェクト、キャッシング、およびString操作が含まれるようになりました。

Apache Commonsは、コアJavaコレクションAPIを補完するJakartaプロジェクトとして始まり、最終的にApacheSoftwareFoundationのプロジェクトになりました。 何年にもわたって、イメージング、I / O、暗号化、キャッシング、ネットワーキング、検証、オブジェクトプーリングなど、他のさまざまな分野で再利用可能な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にはこの機能はありません。

2つのテクノロジーがどのようにそれらを提供するかを見てみましょう。 どちらの場合も、曜日の例を使用して、その日の名前を取得します。その逆も同様です。

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. ApacheのBidiMap

同様に、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));
}

いくつかの簡単なパフォーマンステストでは、 この双方向マップ挿入においてのみ、グアバの対応物に遅れをとった。 キーと値のフェッチがはるかに高速でした

5. キーを複数の値にマップする

果物や野菜の食料品のカートコレクションなど、複数のキーを異なる値にマッピングするユースケースの場合、2つのライブラリは独自のソリューションを提供します。

5.1. グアバの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());
}

ご覧のとおり、ここでは最初にAppleFruitsセットから削除し、次にFruitsセット全体を削除しました。

5.2. ApacheのMultiValuedMap

ここでも、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から削除する柔軟性はありません。削除できるのはのセット全体のみです。 ] 果物:

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

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

6. 複数のキーを1つの値にマップする

ここでは、それぞれの都市にマッピングされる緯度と経度の例を取り上げます。

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を提供しています。

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

そして、映画テーブルで実行できるいくつかのサンプルの自明の検索を次に示します。

@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では、2つのキーのみを値にマップするように制限されています。 Guavaには、2つ以上のキーを1つの値にマップするための代替手段はまだありません。

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は、3つ以上のキーを値にマップする可能性も提供します。 たとえば、曜日を平日または週末としてマッピングする機能が提供されます。

@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. ApacheCommonsCollectionsと Google Guava

エンジニアによると、 Google Guavaは、ApacheCommonsが提供していなかったジェネリックをライブラリで使用する必要性から生まれました。 また、TシャツのコレクションAPI要件に従います。 もう1つの大きな利点は、新しいリリースが頻繁にリリースされることで活発に開発されていることです。

ただし、Apacheは、コレクションから値をフェッチする際のパフォーマンスに関して優位性を提供します。 挿入時間の点では、グアバはまだケーキを取ります。

コードサンプルではコレクションAPIのみを比較しましたが、 Apache Commonsは全体として、Guavaと比較してはるかに広い範囲の機能を提供します。

8. 結論

このチュートリアルでは、特にコレクションフレームワークの領域で、ApacheCommonsとGoogleGuavaによって提供される機能のいくつかを比較しました。

ここでは、2つのライブラリが提供するもののほんの一部にすぎません。

さらに、それはどちらか-または比較ではありません。 コードサンプルが示すように、 2つのそれぞれに固有の機能があり、両方が共存できる状況が存在する可能性があります

いつものように、ソースコードはGitHubから入手できます。