JAVA/JAVA2

[java2] #15 - HashMap

yoonddo 2022. 7. 25. 16:59

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으로 출력하면 중괄호{} 로 묶여서 키와 값이 출력된다.

  1. put은 키와 값을 map에 저장한다.
    없는 key에 value를 할당하면 데이터가 추가되고 사용중인 key에 데이터를 할당하면 데이터가 수정된다.
  2. get은 저장된 데이터 중에서 key에 해당되는 value를 얻어온다. 해당하는 키가 없다면 null을 넘겨준다.
  3. remove는 HashMap에 저장된 데이터 중에서 key에 해당되는 데이터를 제거한다.
  4. 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