Table의 한계와 확장 전략
Guava의 Table은 행과 열로 구성된 2차원 자료구조입니다. 실무에서는 시간, 공간, 카테고리 등 여러 기준으로 데이터를 분류해야 하는 경우가 빈발하는데, 이때 단순 2차원 구조로는 표현이 어렵습니다. Table 자체는 고차원을 지원하지 않지만, 적절한 조합을 통해 n차원 데이터를 우회적으로 구현할 수 있습니다.
중첩 Table로 3차원 구현
셀 단위에 또 다른 Table을 배치하면 세 개의 인덱스로 데이터를 조회할 수 있습니다. 예를 들어 Table<R, C, V>의 값 타입 V를 Table<C2, R2, V2>로 대치하는 방식입니다.
일별 지역별 매출·이익 데이터를 관리하는 시나리오를 살펴보겠습니다.
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import org.junit.jupiter.api.Test;
public class TripleIndexTableTest {
@Test
void threeDimensionalLookup() {
// 1차 인덱스: 날짜, 2차 인덱스: 지역, 3차 인덱스: 지표명 → 값
Table<String, String, Table<String, Integer>> timeSeriesCube = HashBasedTable.create();
Table<String, Integer> northMetrics = HashBasedTable.create();
northMetrics.put("Revenue", 1_000_000);
northMetrics.put("Margin", 250_000);
Table<String, Integer> southMetrics = HashBasedTable.create();
southMetrics.put("Revenue", 800_000);
southMetrics.put("Margin", 180_000);
timeSeriesCube.put("2024-12-01", "North", northMetrics);
timeSeriesCube.put("2024-12-01", "South", southMetrics);
// 3차원 조회: 특정일 → 특정지역 → 특정지표
int northRevenue = timeSeriesCube.get("2024-12-01", "North").get("Revenue");
System.out.println("North revenue: " + northRevenue);
}
}순회 패턴 설계
중첩된 구조를 순회할 때는 외부 키 집합을 먼저 확보하고, 내부 Table로 진입하여 재차 순회하는 방식이 자연스럽습니다.
@Test
void iterateNestedLayers() {
Table<String, String, Table<String, Integer>> cube = buildSampleCube();
for (String date : cube.rowKeySet()) {
for (String zone : cube.columnKeySet()) {
Table<String, Integer> metrics = cube.get(date, zone);
if (metrics == null) continue;
metrics.cellSet().forEach(cell ->
System.out.printf("[%s / %s] %s = %d%n",
date, zone, cell.getRowKey(), cell.getValue())
);
}
}
}cellSet()을 활용하면 행키·열키·값을 동시에 얻어 중첩 반복문을 줄일 수 있습니다.
4차원 이상으로 확장: Map과의 결합
차원이 늘어나면 Table만으로는 부족합니다. 최상위 인덱스를 Map으로 분리하고, 그 값에 Table을 매핑하면 4차원, 5차원도 가능합니다.
@Test
void fourDimensionalStructure() {
// 1차: 연도(Map) → 2차: 월, 3차: 지역(Table) → 4차: 지표명, 값
Map<String, Table<String, String, Table<String, Integer>>> hyperCube =
new HashMap<>();
Table<String, Integer> productMetrics = HashBasedTable.create();
productMetrics.put("UnitsSold", 5_000);
productMetrics.put("ReturnRate", 120);
Table<String, String, Table<String, Integer>> monthlySlice =
HashBasedTable.create();
monthlySlice.put("Seoul", "Electronics", productMetrics);
hyperCube.put("2024", monthlySlice);
// 조회 경로: 연도 → 워치(여기서는 미사용) → 지역/카테고리 → 지표
Table<String, Integer> found = hyperCube.get("2024")
.get("Seoul", "Electronics");
System.out.println("Units sold: " + found.get("UnitsSold"));
}위 예시는 연도-월-지역-카테고리-지표 다섯 개 축을 두 단계 Map과 두 단계 Table로 분산 배치한 형태입니다. 핵심은 각 계층의 책임을 명확히 분리하여 조회 경로를直관적으로 만드는 것입니다.
설계 시 고려사항
- 타입 안전성: 깊이 중첩될수록 제네릭 선언이 복잡해지므로, 의미 있는 타입 별칭(예:
class RegionTable extends HashBasedTable<...>)을 고려하세요. - Null 방어: 중간 계층에서
null이 반환되면 하위 접근 시 NPE가 발생합니다.Table<R, C, V>의get결과에 대해Optional또는 기본값 객체를 활용하세요. - 메모리 오버헤드: 빈 셀을 포함한 완전한 다차원 배열 대비 희소 행렬(sparse matrix) 형태이므로, 데이터 밀도가 극도로 낮은 경우 오히려 유리합니다.