[java2] #15 - HashMap
HashMap
- 이름 그대로 Hashing된 Map이며 사전식으로 데이터를 저장한다. 즉 키(Key)와 값(Value) 한 쌍(Entry)으로
데이터를 보관하는 자료 구조이다. - key는 일반적으로 String 타입으로 만들고 value는 저장할 데이터 타입으로 만든다.
- 키(Key)는 맵(Map)에 오직 유일하게 있어야 한다. 즉 같은 맵에 두 개 이상의 키가 존재하면 안된다.
(값은 중복값이어도 상관없다) - Hashtable은 HashMap과 사용법이 거의 동일한 컬렉션이다. 두 클래스의 차이점은 Thread 관점에서
안전하냐(= Hashtable), 안전하지 않지만 속도가 빠르냐(=HashMap)이다.
Map
Key | Value |
나이 | 50 |
이름 | 김OO |
주소 | OO도 OO시 |
Map 인터페이스를 구현한 HashMap은 키를 해싱하여 자료를 저장하고 꺼내오기 때문에 속도가 빠르다.
*hashing : 키 값을 배열의 인덱스로 변환시키는 기술
1. 저장(put) / 읽기(get) / 삭제(remove) / 개수 얻기(size)
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] ar) {
Map<String,Integer> map=new HashMap(); //<키 자료형, 값 자료형>
map.put("A", 100);
map.put("B", 101);
map.put("C", 102);
map.put("C", 103); //중복된 key가 들어갈때는 이전 키,값을 지금의 것으로 업데이트
System.out.println(map);
System.out.println(map.get("A"));
System.out.println(map.get("B"));
System.out.println(map.get("C"));
map.remove("C", 102); //remove()
System.out.println(hmap.size() + " : " + hmap); //size()
}
}
결과
{A=100, B=101, C=103}
100
101
103
map을 println으로 출력하면 중괄호{} 로 묶여서 키와 값이 출력된다.
- put은 키와 값을 map에 저장한다.
없는 key에 value를 할당하면 데이터가 추가되고 사용중인 key에 데이터를 할당하면 데이터가 수정된다. - get은 저장된 데이터 중에서 key에 해당되는 value를 얻어온다. 해당하는 키가 없다면 null을 넘겨준다.
- remove는 HashMap에 저장된 데이터 중에서 key에 해당되는 데이터를 제거한다.
- size는 HashMap에 저장된 데이터의 개수를 얻어온다.
2. containsKey (HashMap에 이미 같은 키가 있으면 덮어쓰지 않는다)
public static void main(String[] ar){
Map<String,Integer> map=new HashMap();
map.put("key1", 100);
map.put("key2", 200);
if(!map.containsKey("key2")) //키가 들어있는지 확인. 있으면 덮어쓰지 않는다.
map.put("key2", 300);
System.out.println(map);
System.out.println(map.get("key1"));
System.out.println(map.get("key2"));
}
결과
{key1=100, key2=200}
100
200
containsKey로 키의 존재여부를 알 수 있다. 비슷한 메서드로는 containsValue가 있는데 이것은 값의 존재여부를
알아보는 메서드이다. 존재시 true, 존재하지 않을 시 false를 반환한다.
3. putIfAbsent
//if(!map.containsKey("key2")) //키가 들어있는지 확인. 있으면 덮어쓰지 않는다.
//map.put("key2", 300);
map.putIfAbsent("key2",300);
if문과 put메서드를 한번에 처리할 수 있다.
4. putAll (Map에 다른 Map을 전부 포함한다.)
public static void main(String[] ar) {
Map<String,Integer> map1=new HashMap();
Map<String,Integer> map2=new HashMap();
//map1 put
map1.put("map1-key1", 100);
map1.put("map1-key2", 200);
//map2 put
map2.put("map2-key3", 300);
map2.put("map2-key4", 400);
System.out.println("map1:"+map1);
System.out.println("map2:"+map2);
//map2에 map1을 합침
map2.putAll(map1);
System.out.println("map2 includes map1:"+map2);
//map1의 키, 값 변경
map1.put("map1-key1", 1000);
//map2에는 영향 없음.
System.out.println("map2 includes map1:"+map2);
}
결과
map1:{map1-key1=100, map1-key2=200}
map2:{map2-key4=400, map2-key3=300}
map2 includes map1:{map2-key4=400, map1-key1=100, map1-key2=200, map2-key3=300}
map2 includes map1:{map2-key4=400, map1-key1=100, map1-key2=200, map2-key3=300}
Map을 통째로 인자로 넘겨주고 싶다면 putAll 메서드를 사용하면 된다. 이 때 주의할 점은 반드시
키와 값의 자료형이 같은 map이어야 한다는 점이다. 다른자료형의 키와 값은 받을 수 없다.
*putAll대신 생성자를 이용해 생성과 동시에 map의 데이터를 전부 넘겨줄 수도 있다.
Map<String,Integer> map2=new HashMap(map1);
5. keySet (모든 키를 순회하는 코드)
list처럼 증가하는 index를 사용할 방법이 없지만 keySet메서드를 이용해 키를 Set으로 넘겨주어
Map에 존재하는 모든 키를 순회할 수 있다.
public static void main(String[] ar) {
Map<String,Integer> map=new HashMap();
map.put("key1",50);
map.put("key2",100);
map.put("key3",150);
map.put("key4",200);
System.out.println("All key-value pairs");
for(String key:map.keySet()) {
System.out.println("{"+key+","+map.get(key)+"}");
}
}
결과
All key-value pairs
{key1,50}
{key2,100}
{key3,150}
{key4,200}
6. Foreach() 메서드로 순환하기
Foreach() 메서드를 사용하기 전에 람다식을 이해하고 있어야 하지만 사용법만 알아도 유용하게 사용할 수 있다.
public static void main(String[] ar) {
Map<String,Integer> hm=new HashMap();
hm.put("http",80);
hm.put("ssh", 22);
hm.put("dns", 53);
hm.put("telnet",23);
hm.put("ftp", 21);
hm.forEach((key,value)->
{
System.out.println("{"+key+","+value+"}");
});
}
(->)가 있는 라인이 람다식이다. Key와 Value를 사용해 -> 이후 동작을 구현해주면 된다.
결과
{ftp,21}
{telnet,23}
{dns,53}
{http,80}
{ssh,22}
7. 내가 만든 객체를 Key로 사용하기
public class Main {
public static void main(String[] ar) {
Person person1=new Person("reakwon","666666-7777777");
Person person2=new Person("putty","123456-1234567");
Person who=new Person("reakwon","666666-7777777");
Map<Person,Integer> map=new HashMap();
map.put(person1, 90);
map.put(person2, 80);
System.out.println("map includes "+who.getName()+"? "+map.containsKey(who));
map.put(who, 70);
System.out.println(map);
}
}
class Person{
private String name;
private String id;
public Person(String name,String id) {
this.name=name;
this.id=id;
}
public String getName() {
return name;
}
@Override
public String toString() {
return this.name;
}
}
결과
map includes reakwon? false
{putty=80, reakwon=70, reakwon=90}
person1과 who를 map이 같은 키로 인식하도록 하려는 의도였지만 결과는 제대로 나오지 않았다.
이 때 사용할 수 있는 여러가지 방법이 있다.
1. equals() Method Overriding
Object 클래스의 equals는 서로 같은 객체인지 아닌지 판별해주는 메소드이다.
@Override
public boolean equals(Object o) {
if(o instanceof Person) {
Person p=(Person)o;
return this.id.equals(p.id) && this.name.equals(p.name);
}
return false;
}
결과
map includes reakwon? false
{putty=80, reakwon=70, reakwon=90}
분명 equals를 오버라이딩해서 같은 객체라고 명시했는데도 false를 반환하고 있다.
equals()를 재정의 할 때 hashcode()도 함께 재정의 해야 하는데 하지 않아서 이런 결과가 나온 것이다.
2. hashCode() Method Overriding
hashCode는 각 객체가 갖는 유일한 값(Code)를 의미한다. Object의 HashCode()는 원래 주소값에 의한
hashCode()로 각 객체가 전부 다른 값을 가지고 있다. HashMap은 우선 hashCode를 비교하고 같을 때만
equals를 수행하여 정말 제대로 같은것인지 판단한다. 그래서 HashMap은 애초에 hashCode()가 반환하는
값이 다르면 equals는 수행하지도 않는다.
그래서 위 코드에서 같은 해시코드를 오버라이딩해서 name과 id에 따라 다른 hashCode를 갖게 만들어 주어야 한다. 구현의 편의성을 위해서 String클래스를 사용한다. String 클래스가 이미 그렇게 다 구현(문자열이 같으면 hashCode도 같고 equals도 true) 이 되어있다. 우리는 위의 예제에서 String을 키로 쓴적이 있었다. String 클래스는 hashCode와 equals가 문자열이 같을때 같은 객체라고 판별할 수 있도록 두 메소드를 전부 재정의한 상태이다.
그래서 아래의 코드를 위의 코드 대신 추가해주면 된다.
@Override
public int hashCode() {
return name.hashCode()+id.hashCode();
}
@Override
public boolean equals(Object o) {
return this.hashCode()==o.hashCode();
}
결과
map includes reakwon? true
{reakwon=70, putty=80}
더 자세한 내용은
2022.07.19 - [JAVA 기본/java2] - [java2] #11 - Object, equals(), hashCode()
[java2] #11 - Object, equals(), hashCode()
- 자바에서 클래스를 선언할 때 extends로 다른 클래스를 상속하지 않으면 java.lang.Object 클래스를 상속한다. 즉 자바의 모든 클래스는 Object클래스의 자식클래스가 되므로 Object는 자바의 최상위 부
yoonddo.tistory.com
8. HashMap의 값을 ArrayList로 넣기
package day06;
import java.util.ArrayList;
import java.util.HashMap;
public class MapTest2 {
public static void main(String[] args) {
HashMap<Student, ArrayList<Integer>> stdInfo = new HashMap<>();
ArrayList<Integer> scores = new ArrayList<>();
scores.add(90);
scores.add(20);
scores.add(100);
stdInfo.put(new Student(1, "한동석"), scores);
stdInfo.get(new Student(1, "한동석")).forEach(System.out::print);
System.out.println("");
stdInfo.get(new Student(1, "한동석")).forEach(score -> System.out.println(score));
for(int score : stdInfo.get(new Student(1, "한동석"))) {
System.out.println(score);
}
}
}
위 코드와 같이 HashMap의 값을 ArrayList로 넣을 수 있다. ArrayList score에 점수 3개를 add하고
HashMap stdInfo 에 Student와 scores를 넣어주면 하나의 키에 여러개의 값을 넣어 출력할 수 있다.
결과
9020100
90
20
100
90
20
100
참고 출처
https://reakwon.tistory.com/151