백엔드 개발자
2024. 7. 15. 22:26
- 키 key 와 값 value 의 쌍
- 키와 값의 자료형은 다양하게 가능
- 예시
- 학생 번호 (숫자) / 학생 이름 (문자열)
- 과목명 (문자열) / 성적 코드 (문자)
- 반 번호 (문자열) / 학생들 (학생 인스턴스의 ArrayList)
- 키값은 중복될 수 없음(키값은 set과 성질이 같다)
- 같은 키에 다시 값을 넣으면 기존 값 대체
맵 사용 예시
Map<String, Double> nameHeightHMap = new HashMap<>();
nameHeightHMap.put("김철수", 176.8);
nameHeightHMap.put("이장신", 187.4);
nameHeightHMap.put("박숏달", 152.3);
nameHeightHMap.put("정기준", 171.2);
Map<Side, ArrayList<Unit>> sideUnitsHMap = new HashMap<>();
sideUnitsHMap.put(
Side.BLUE,
new ArrayList<>(
Arrays.asList(
new Swordman(Side.BLUE),
new Knight(Side.BLUE),
new MagicKnight(Side.BLUE))
)
);
- 제네릭 부분에 맞게 키와 value값을 세팅해주면 된다.
// 💡 putAll : 대상 맵으로부터 전부 가져옴
Map<Integer, String> numNameHMapClone = new HashMap<>();
numNameHMapClone.putAll(numNameHMap);
- 새 해시맵을 생성한 후 putAll로 복사하는 방법.
Double leeHeight = nameHeightHMap.get("이장신");
// ⚠️ 잘못된 키 입력시 null 반환
String wrong1 = numNameHMap.get(0);
- get으로 가져올 수 있는데, 잘못된 값을 키로 넣으면 null을 반환한다.
// 💡 keySet 메소드 - 키들의 Set(인터페이스) 반환
Set<Integer> numSet = numNameHMap.keySet();
Set<Integer> numHSet = new HashSet<>();
numHSet.addAll(numSet);
// keySet을 활용한 for-each
for (Integer n : numNameHMap.keySet()) {
System.out.println(numNameHMap.get(n));
}
for (Side side : sideUnitsHMap.keySet()) {
for (Unit unit : sideUnitsHMap.get(side)) {
System.out.println(unit);
}
}
- keySet()을 이용하면 키들만 모아서 Set으로 반환할 수 있다. 그래서 addAll로 set을 복사한 상황.
- KeySet으로 키만을 뽑아올 수 있기 때문에, foreach 문으로 키값에 대한 value값을 하나씩 뽑아올 수 있는 것이다.
- sideUnitsHMap의 경우 Map<Side, ArrayList<Unit>>으로 되어 있기 때문에 첫 for문으로 ArrayList<Unit>을 꺼내고, 여기서 한번더 for문을 써서 리스트의 요소들을 뽑아낸다.
// 💡 containsKey / containsValue : 키 / 값 포함되는 쌍 있는지 확인
boolean contains1 = nameHeightHMap.containsKey("박숏달");
boolean contains2 = nameHeightHMap.containsKey("장롱달");
boolean contains3 = nameHeightHMap.containsValue(171.2);
- contains로 키,값이 포함된 쌍이 있는지 확인하여 true,false로 반환한다.
// 💡 getOrDefault : 키에 해당하는 쌍이 없을 시 지정한 디폴트 값 반환
String defName = numNameHMap.getOrDefault(10, "김대타");
Horse defHorse = atkrHrsHMap.getOrDefault(new MagicKnight(Side.BLUE), new Horse(40));
- 디폴트값을 지정해서 쌍이 없으면 반환하게 만들 수 있는 getOrDefault메서드
// 💡 Entry 인터페이스 : 맵의 각 요소, 키와 값을 가짐
// - 클래스 살펴볼 것
Set<Map.Entry<Integer, String>> numNameES = numNameHMap.entrySet();
for (Map.Entry<Integer, String> entry : numNameES) {
int key = entry.getKey();
String value = entry.getValue();
System.out.printf("k: %d, v: %s%n", key, value);
System.out.println(entry);
}
- 맵은 내부적으로 Entry라는 인터페이스로 구성되어 있어서, 2=a, 3=c 이런식으로 출력되는 것을 볼 수 있다.
- 키, 값 쌍으로 구성되어 있다.
// remove, clear, isEmpty 메소드들
numNameHMap.remove(1); // 주어진 키가 있다면 삭제
numNameHMap.remove(2, "황대장"); // 주어진 키와 값의 쌍이 있다면 삭제
boolean isEmpty1 = nameHeightHMap.isEmpty();
nameHeightHMap.clear();
boolean isEmpty2 = nameHeightHMap.isEmpty();
참조형
Map<Attacker, Horse> atkrHrsHMap = new HashMap<>();
Swordman kenshin = new Swordman(Side.RED);
Knight lancelot = new Knight(Side.BLUE);
atkrHrsHMap.put(kenshin, new Horse(40));
atkrHrsHMap.put(lancelot, new Horse(50));
atkrHrsHMap.put(new MagicKnight(Side.BLUE), new Horse(60));
- 맵에 참조형 데이터를 넣을때, 키값을 new로 생성해서 넣고, 주소값을 따로 저장하지 않는다면 그 키/값 쌍을 꺼내는 방법은 get으로는 불가능하다.
// ⭐️ 키의 값이 참조형일 경우 변수 등에 저장되어 있어야 함
Horse kenshinHorse = atkrHrsHMap.get(kenshin);
Horse lancelotHorse = atkrHrsHMap.get(lancelot);
// ⚠️ 일반적인 방법으로는 꺼낼 수 없음
Horse wrongHorse = atkrHrsHMap.get(new MagicKnight(Side.BLUE));
// 이런 식으로밖에 꺼낼 수 없음
for (Attacker u : atkrHrsHMap.keySet()) {
System.out.println(u + " : " + atkrHrsHMap.get(u));
}
- new로 생성하는 순간 참조값이 달라지므로 맵에 들어가 있는 값과는 달라지게 되어 찾을 수 없다.
- foreach문의 KeySet으로 꺼내와서 찾는 방법 말고는 없으므로, 참조형일때는 변수등에 저장해두는 것이 좋다.
해시맵과 트리맵
- 키를 저장하는 방식에 있어 해시셋/트리셋과 같음
- 해시맵 : 키의 해시코드 / 키
- 트리맵 : 키를 트리 형태로 저장
- 정렬 무관 빠른 접근시에는 해시맵, 키순으로 정렬 필요시 트리맵
// ⭐️ 키 순으로 정렬됨 확인
TreeMap<Integer, String[]> classKidsTMap = new TreeMap<>();
classKidsTMap.put(3, new String[] {"서아", "이준", "아린"});
classKidsTMap.put(9, new String[] {"하윤", "서준", "지호"});
classKidsTMap.put(1, new String[] {"이서", "하준", "아윤"});
classKidsTMap.put(7, new String[] {"지안", "은우", "예준"});
classKidsTMap.put(5, new String[] {"서윤", "시우", "하은"});
- 출력시 키순으로 오름차순 정렬된다.
// 트리 전용 메소드들
int firstKey = classKidsTMap.firstKey();
int lastKey = classKidsTMap.lastKey();
Map.Entry<Integer, String[]> firstEntry = classKidsTMap.firstEntry();
Map.Entry<Integer, String[]> lastEntry = classKidsTMap.lastEntry();
- 맨처음과 맨끝값의 키를 가져올 수 있다.
- firstEntry를 사용하면 처음의 키/값 쌍을 가져올 수 있다.
int ceil4 = classKidsTMap.ceilingKey(4);
Map.Entry<Integer, String[]> floor4 = classKidsTMap.floorEntry(4);
- ceilingKey는 주어진 키보다 같거나 큰 가장 작은키를 반환하고, 없으면 null을 반환한다.
- floorEntry는 주어진 키보다 같거나 그보다 작은 가장큰 키값 쌍을 반환하고, 없으면 null을 반환한다.
Map.Entry<Integer, String[]> pollF1 = classKidsTMap.pollFirstEntry();
Map.Entry<Integer, String[]> pollF2 = classKidsTMap.pollFirstEntry();
Map.Entry<Integer, String[]> pollL1 = classKidsTMap.pollLastEntry();
Map.Entry<Integer, String[]> pollL2 = classKidsTMap.pollLastEntry();
- Set과 마찬가지로 요소를 빼내고 출력해준다.
Map의 동시성 체크 - fail fast
Map을 순환하다 내부 컬렉션이 변경되면 동시성관련 ConcurrentModificationException 에러가 발생한다.
왜?
자바 컬렉션에서는 Fail-fast 메커니즘이라는게 있어서 반복중 데이터 변조나 일관성이 깨지면 즉시 오류를 발생시키는 방식이 존재한다.
어떻게 감지하지?
modCount라는 변수를 내부적으로 사용한다. 컬렉션 크기가 변경될때마다 증가.
그러면 어떻게 하면 될까?
1. 이터레이터의 remove를 이용한다.
2. fail-safe한 방식을 사용한다. ConcurrentHashMap이라던가 CopyOnWriteArrayList라던가
출처
학습 페이지
www.inflearn.com