Map은 키와 값을 짝지어 저장하는 자료 구조예요. 다른 프로그래밍 언어의 딕셔너리와 비슷해요. Map 인터페이스는 Map으로 할 수 있는 연산을 정의해요.
Java에는 여러 가지 Map 구현이 있어요. HashMap은 그중에서도 자주 사용하는 구현이에요.
// Make an instance
Map<String, Integer> fruitPrices = new HashMap<>();
Map 변수를 선언할 때는 위 예시처럼 구체적인 타입이 아니라 Map 타입으로 선언하는 것이 좋아요.
이렇게 해두면 나중에 Map 구현을 바꾸기 쉬워요.
HashMap에는 복사 생성자도 있어요.
// Make a copy of a map
Map<String, Integer> copy = new HashMap<>(fruitPrices);
put으로 Map에 항목을 추가해요.
fruitPrices.put("apple", 100);
fruitPrices.put("pear", 80);
// => { "apple" => 100, "pear" => 80 }
각 키에는 값 하나만 연결할 수 있어요.
같은 키로 put을 호출하면 그 키의 값이 갱신돼요.
fruitPrices.put("pear", 40);
// => { "apple" => 100, "pear" => 40 }
get으로 키에 해당하는 값을 가져와요.
fruitPrices.get("apple"); // => 100
containsKey로 Map에 특정 키가 있는지 확인해요.
fruitPrices.containsKey("apple"); // => true
fruitPrices.containsKey("orange"); // => false
remove로 항목을 제거해요.
fruitPrices.put("plum", 90); // Add plum to map
fruitPrices.remove("plum"); // Removes plum from map
size 메서드는 항목의 개수를 반환해요.
fruitPrices.size(); // Returns 2
keySet이나 values 메서드를 사용하면 Map의 키와 값을 각각 Set과 컬렉션으로 얻을 수 있어요.
fruitPrices.keySet(); // Returns "apple" and "pear" in a set
fruitPrices.values(); // Returns 100 and 80, in a Collection
hashCode와 equals를 사용해요HashMap은 객체의 hashCode와 equals 메서드를 이용해서 키의 값을 어디에 저장하고 어떻게 가져올지 결정해요. 그래서 이 메서드들의 반환 값이 값을 저장할 때와 가져올 때 달라지지 않는 것이 중요해요. 그렇지 않으면 HashMap이 값을 찾지 못할 수도 있어요.
예를 들어, Map의 키로 사용할 다음 클래스가 있다고 해봐요.
public class Stock {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (Objects.equals(Stock.class, obj.getClass()) && obj instanceof Stock other) {
return Objects.equals(name, other.name);
}
return false;
}
}
hashCode와 equals는 setName으로 바꿀 수 있는 name 필드에 의존해요.
hashCode를 바꾸면 예상치 못한 결과가 나올 수 있어요.
Stock stock = new Stock();
stock.setName("Beanies");
Map<Stock, Integer> stockCount = new HashMap<>();
stockCount.put(stock, 80);
stockCount.get(stock); // Returns 80
Stock other = new Stock();
other.setName("Beanies");
stockCount.get(other); // Returns 80 because "other" and "stock" are equal
stock.setName("Choccies");
stockCount.get(stock); // Returns null because hashCode value has changed
stockCount.get(other); // Also returns null because "other" and "stock" are not equal
stock.setName("Beanies");
stockCount.get(stock); // HashCode restored, so returns 80 again
stockCount.get(other); // Also returns 80 again because "other" and "stock" are back to equal
Map을 만드는 또 다른 흔한 방법은 Map.of나 Map.ofEntries를 사용하는 거예요.
// Using Map.of
Map<String, Integer> temperatures = Map.of("Mon", 30, "Tue", 28, "Wed", 32);
// or using Map.ofEntries
Map<String, Integer> temperatures2 = Map.ofEntries(
Map.entry("Mon", 30),
Map.entry("Tue", 28),
Map.entry("Wed", 32)
);
HashMap과 달리 이 메서드들은 Map을 미리 채워 두고, 한 번 만들면 읽기 전용이 돼요.
Map.copyOf는 Map의 읽기 전용 복사본을 만들어요.
Map<String, Integer> readOnlyFruitPrices = Map.copyOf(fruitPrices);
put, remove, clear 같은 메서드를 호출하면 UnsupportedOperationException이 발생해요.