Table의 차원 확장 전략
Guava의 Table은 행과 열의 교차점에 값을 저장하는 2차원 자료구조다. 실무에서는 단순한 2차원 구조로는 부족한 경우가 많다. 예를 들어 시간, 지역, 제품 카테고리, 매출 지표 등 여러 기준으로 데이터를 분석해야 할 때가 있다.
이런 상황에서 Table을 중첩하거나 Map과 결합하여 마치 다차원 배열처럼 활용할 수 있다. 물론 실제로는 2차원 구조를 여러 겹으로 포개어 사용하는 것이다.
중첩 Table로 3차원 데이터 구현
가장 직관적인 방법은 Table의 셀 값으로 또 다른 Table을 담는 것이다. 시계열 데이터를 다루는 예를 살펴보자. 날짜, 지역, 지표명을 각각의 차원으로 삼아 데이터를 조직화할 수 있다.
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class DimensionalTableTest {
@Test
void threeDimensionalLookup() {
// 날짜 × 지역 → (지표명 × 수치)
Table<String, String, Table<String, Integer>> timeSeries = HashBasedTable.create();
Table<String, Integer> northMetrics = HashBasedTable.create();
northMetrics.put("Revenue", 850_000);
northMetrics.put("Cost", 620_000);
Table<String, Integer> southMetrics = HashBasedTable.create();
southMetrics.put("Revenue", 720_000);
southMetrics.put("Cost", 580_000);
timeSeries.put("2024-Q1", "North", northMetrics);
timeSeries.put("2024-Q1", "South", southMetrics);
// 3차원 점 조회: 2024-Q1, North 지역의 Revenue
Table<String, Integer> regionResult = timeSeries.get("2024-Q1", "North");
assertThat(regionResult.get("Revenue")).isEqualTo(850_000);
}
}외부 Table의 행키는 날짜, 열키는 지역을 담당한다. 셀에 들어간 내부 Table이 지표명과 수치를 관리한다. 이렇게 하면 세 개의 키로 원하는 값을 정확히 찾아낼 수 있다.
다차원 순회 패턴
중첩 구조의 단점은 탐색 코드가 복잡해진다는 것이다. 깊이 우선 방식으로 각 층을 파고들며 순회해야 한다.
@Test
void iterateWithDepthFirst() {
Table<String, String, Table<String, Integer>> inventory = buildSampleInventory();
for (String period : inventory.rowKeySet()) {
for (String zone : inventory.columnKeySet()) {
Table<String, Integer> metrics = inventory.get(period, zone);
if (metrics == null) continue;
metrics.forEach((metric, amount) ->
System.out.printf("%s | %s | %s = %d%n", period, zone, metric, amount)
);
}
}
}순회 시 주의할 점은 특정 (행, 열) 조합에 값이 없으면 null이 반환될 수 있다는 것이다. 따라서 방어적으로 처리하거나, 데이터를 넣을 때 모든 조합을 채워야 한다.
Map과 결합한 4차원 이상 확장
3차원으로도 부족하다면 Map을 한 겉 더 씌우면 된다. 예를 들어 연도별로 Table을 분리하여 관리하는 방식이다.
@Test
void fourDimensionalWithMapWrapper() {
// 연도 → (분기 × 지역 → (지표 × 수치))
Map<Integer, Table<String, String, Table<String, Integer>>> annualRecords = new HashMap<>();
Table<String, Integer> q1North = HashBasedTable.create();
q1North.put("NetProfit", 230_000);
Table<String, String, Table<String, Integer>> q1Table = HashBasedTable.create();
q1Table.put("Q1", "North", q1North);
annualRecords.put(2024, q1Table);
// 4차원 접근: 2024년 Q1 북부 NetProfit
Integer profit = annualRecords.get(2024)
.get("Q1", "North")
.get("NetProfit");
assertThat(profit).isEqualTo(230_000);
}차원이 늘어날수록 타입 선언이 복잡해지는 것이 단점이다. 실무에서는 별도의 래퍼 클래스를 만들어 타입 안전성을 확보하고, 잡한 중첩 타입을 숨기는 것이 좋다.
구조 설계 시 고려사항
무작정 중첩만 하면 오히려 코드 가독성이 떨어진다. 다음 원칙을 지키면 유지보수성을 높일 수 있다.
- 의미 있는 타입 별칭 사용:
Map<String, Table<...>>대신 전용 클래스나 레코드로 포장 - Null 안전성 확보:
get()호출 시 Optional이나 기본값 패턴 적용 - 불변성 고려: 데이터 생성 후 변경이 없다면
ImmutableTable활용 - 캐싱 최적화: 빈번한 조회 패턴을 분석해 적절한 인덱스 구조 설계
Guava Table은 본질적으로 2차원이지만, 적절한 조합과 추상화를 통해 복잡한 다차원 데이터 모델을 우아하게 표현할 수 있다.