1. 개요

이 사용방법(예제)에서는 Apache CommonsGoogle Guava 라는 두 가지 Java 기반 오픈 소스 라이브러리를 비교 합니다. 두 라이브러리에는 주로 컬렉션 및 I/O 영역에서 많은 유틸리티 API가 포함된 풍부한 기능 세트가 있습니다.

간결함을 위해 여기서는 코드 샘플과 함께 컬렉션 프레임워크에서 가장 일반적으로 사용되는 몇 가지만 설명합니다. 또한 차이점에 대한 요약도 볼 수 있습니다.

또한 다양한 커먼즈Guava 유틸리티 에 대한 심층 분석을 위한 기사 모음이 있습니다 .

2. 두 도서관의 간략한 역사

Google Guava는 현재 오픈 소스로 제공되고 있지만 주로 조직의 엔지니어가 개발한 Google 프로젝트입니다. 시작하게주된 동기는 JDK 1.5에 도입된 제네릭을 Java Collections Framework 또는 JCF 에 포함하고 그 기능을 향상시키는 것이었습니다 .

처음부터 라이브러리는 기능을 확장했으며 이제 그래프, 함수형 프로그래밍, 범위 개체, 캐싱 및 문자열 조작을 포함합니다.

Apache Commons는 핵심 Java 컬렉션 API를 보완하기 위한 Jakarta 프로젝트로 시작하여 결국 Apache Software Foundation의 프로젝트가 되었습니다. 수년에 걸쳐 이미징, I/O, 암호화, 캐싱, 네트워킹, 유효성 검사 및 개체 풀링을 비롯한(이에 국한되지 않음) 다양한 다른 영역에서 재사용 가능한 Java 구성 요소의 방대한 레퍼토리로 확장되었습니다.

이것은 오픈 소스 프로젝트이므로 Apache 커뮤니티의 개발자는 기능을 확장하기 위해 이 라이브러리에 계속 추가하고 있습니다. 그러나 이전 버전과의 호환성을 유지하기 위해 세심한 주의를 기울 입니다.

3. 메이븐 의존성

Guava를 포함하려면 pom.xml에 의존성을 추가해야 합니다 .

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

최신 버전 정보는 Maven 에서 확인할 수 있습니다 .

Apache Commons의 경우 약간 다릅니다. 사용하려는 유틸리티에 따라 특정 유틸리티를 추가해야 합니다. 예를 들어 컬렉션의 경우 다음을 추가해야 합니다.

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

코드 샘플에서는 commons-collections4 를 사용할 것 입니다.

이제 재미있는 부분으로 뛰어 가자!

4. 양방향 Map

키와 값으로 액세스할 수 있는 맵을 양방향 맵이라고 합니다. JCF에는 이 기능이 없습니다.

두 가지 기술이 제공하는 방법을 살펴보겠습니다. 두 경우 모두 요일의 예를 들어 숫자가 지정된 요일의 이름을 얻거나 그 반대의 경우도 마찬가지입니다.

4.1. 구아바의 BiMap

Guava는 양방향 Map로 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는 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));
}

몇 가지 간단한 성능 테스트에서 이 양방향 맵 삽입에서만 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());
}

보시다시피 여기에서는 먼저 과일 세트 에서 사과 를 제거한 다음 전체 과일 세트 를 제거했습니다 .

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과일 에서 와 같이 단일 항목을 제거할 수 있는 유연성이 없습니다 . 전체 과일 세트만 제거할 수 있습니다 .

@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제공합니다 .

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

보시다시피 행, 열 및 값 집합 보기를 얻을 수 있습니다 .

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 은 값에 두 개의 키만 매핑하도록 제한합니다 . 두 개 이상의 키를 단일 값에 매핑하는 대안은 아직 Guava에 없습니다.

6.2. 아파치의  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 vs. Google Guava

엔지니어에 따르면 Google Guava는 Apache Commons가 제공하지 않는 라이브러리에서 제네릭을 사용해야 하기 때문에 탄생했습니다 . 또한 컬렉션 API 요구 사항을 티에 따릅니다. 또 다른 주요 이점은 새 릴리스가 자주 나오는 활발히 개발되고 있다는 것입니다.

그러나 Apache는 컬렉션에서 값을 가져오는 동안 성능 면에서 우위를 제공합니다. 구아바는 삽입 시간 측면에서 여전히 케이크를 사용합니다.

코드 샘플에서 컬렉션 API만 비교했지만 Apache Commons는 전체적으로 Guava에 비해 훨씬 더 많은 기능을 제공합니다 .

8. 결론

이 예제에서는 Apache Commons와 Google Guava가 제공하는 일부 기능, 특히 컬렉션 프레임워크 영역을 비교했습니다.

여기에서 우리는 두 라이브러리가 제공하는 것의 표면만을 긁었습니다.

게다가, 그것은 둘 중 하나의 비교가 아닙니다. 우리의 코드 샘플에서 보여주듯이 두 가지 각각에 고유한 기능이 있으며 두 가지가 공존할 수 있는 상황이 있을 수 있습니다 .

항상 그렇듯이 소스 코드는 GitHub 에서 사용할 수 있습니다 .

Junit footer banner