Free Lines Arrow
본문 바로가기
Language/Java

[Java] HashMap, hashCode(), equals()

by skahn1215 2021. 9. 6.
728x90
반응형

HashMap

  • HashMap 이 어떻게 동작하고 저장하는지 알아본다.
  • HashMap 에서 어떻게 비교연산이 이루어 지는지 알아본다.
  • equals 와 hashCode 함수에 대해 알아본다

 

HashMap?

  • HashMap 이란 키와 값을 묶어서 하나의 데이터로 저장한다.
  • 그리고 hashing 을 사용하기 때문에 데이터를 검색하는데 성능이 뛰어나다.
  • hashing 이란 뭘까?

 

 

 

Hashing

해싱이란 해시함수를 이용해서 데이터를 해시 테이블에 저장하고 

검색하는 기법이다.

 

그림

 

  • 1. 키값을 해시함수에 넣는다.
  • 2. 해시함수 결과 값(해시코드)에 해당하는 링크드리스트를 찾는다.
  • 3. 링크드 리스트에 검색한 키와 일치하는 데이터를 저장하거나 또는 데이터를 찾는다.

 

hashCode?

  • Map 을 사용할때 때때로 hashCode를 사용하면 직접 해쉬 값을 만들수 있다.
  • 왜 직접 해쉬 코드를 만들어야 하는 경우가 생길까?
  • 예제를 보자

 

예제

  • 억지스럽지만 멤버가 있고
  • 멤버가 어느 나라 사람인지 국가 코드를 이용한다.
  • 멤버를 키로 국가코드를 값으로 Map 에 저장한다.

 

코드

public class Member {

    private String name;
    private int age;
    private String nickName;

    public Member(String name, int age, String nickName) {
        this.name = name;
        this.age = age;
        this.nickName = nickName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class Main {
    public static void main(String[] args) {
        Member member1 = new Member("skahn",150, "Algorithm");
        Member member2 = new Member("skahn",150, "Algorithm");

        Map<Member, String> members = new HashMap<>();
        members.put(member1, "KR");
        members.put(member2, "KR");
        System.out.println("members map size is "+members.size());
    }
}

 

결과

Member1 hashcode: 1101288798
Member2 hashcode: 942731712
members map size is 2

 

문제점

  • 맵안에 데이터가 2개 저장되었다.
  • 코드를 보면 멤버에 대한 내용은 똑같다 즉 사용자의 실수로 중복저장이 되었다.
  • HashCode 값이 다르기 때문이다.

 

 

hashCode 오버라이딩

hashCode 를 오버라이딩한다면 직접 해쉬코드를 생성할수 있다.

 

코드

public class Member {

    private String name;
    private int age;
    private String nickName;

    public Member(String name, int age, String nickName) {
        this.name = name;
        this.age = age;
        this.nickName = nickName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    @Override
    public int hashCode() {
        final int number = 31;
        int hashCode = 1;

        hashCode = number * hashCode + ((name == null) ? 0 : name.hashCode());
        hashCode = number * hashCode + ((nickName == null) ? 0 : nickName.hashCode());
        hashCode = number * hashCode + age;

        return hashCode;
    }

}
public class Main {
    public static void main(String[] args) {
        Member member1 = new Member("skahn",150, "Algorithm");
        Member member2 = new Member("skahn",150, "Algorithm");

        Map<Member, String> members = new HashMap<>();
        members.put(member1, "KR");
        members.put(member2, "KR");
        System.out.println("members map size is "+members.size());
    }
}

 

결과

Member1 hashcode: -1761403243
Member2 hashcode: -1761403243
members map size is 2

 

문제점

  • 동일한 내용에 대해서 hash 값은 같아졌다.
  • 하지만 똑같이 맵안에 데이터가 2개 저장되었다.
  • 뭐가 문제일까?
  • equasl 도 역시 오버라이딩을 해줘야 한다.

 

반응형

 

HashMap의 equals 함수?

  • 일단 map 의 equals 함수를 열어보면
  • 객체로 비교한다는 것을 알수 있다.
public final boolean equals(Object o) {
    if (o == this)
        return true;
    if (o instanceof Map.Entry) {
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;
        if (Objects.equals(key, e.getKey()) &&
        Objects.equals(value, e.getValue()))
        return true;
    }
    return false;
}

 

  • 우리는 member1 member2 를 각각 생성했다.
  • HashMap에 저장될때 비교를 수행하게 된다.
  • hashcode 가 같아도 객체를 비교했을때 다르다고 나온다.
  • 그렇기 때문에 두번 저장됐던 것이다.

 

equals 오버라이딩

자그럼 equals 함수를 오버라이딩 해서

중복체크를 다시 해보자.

 

코드

public class Member {

    private String name;
    private int age;
    private String nickName;

    public Member(String name, int age, String nickName) {
        this.name = name;
        this.age = age;
        this.nickName = nickName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int hashCode() {
        final int number = 31;
        int hashCode = 1;

        hashCode = number * hashCode + ((name == null) ? 0 : name.hashCode());
        hashCode = number * hashCode + ((nickName == null) ? 0 : nickName.hashCode());
        hashCode = number * hashCode + age;

        return hashCode;
    }

    @Override
    public boolean equals(Object o) {
        // 객체가 동일하다면 true 중복저장 안됨
        if (this == o) return true;
    
        // 객체가 널이거나 클래스 값이 다르면 다르기 때문에 false
        // member인 경우 클래스가 둘다 동일하기 때문에 그냥 넘어간다.
        if (o == null || getClass() != o.getClass()) return false;
    
        // 멤버 값 비교
        Member member = (Member) o;
    
        // 나이, 이름, 닉네임을 비교해서 다른지 체크한다.
        return age == member.age && Objects.equals(name, member.name) && Objects.equals(nickName, member.nickName);
    }
}
public class Main {
    public static void main(String[] args) {
        Member member1 = new Member("skahn",150, "Algorithm");
        Member member2 = new Member("skahn",150, "Algorithm");

        Map<Member, String> members = new HashMap<>();
        members.put(member1, "KR");
        members.put(member2, "KR");
        System.out.println("members map size is "+members.size());
    }
}

 

결과

  • 우리가 원하는 대로 동일한 내용에 대한 멤버는 한번만 저장되었다.
Member1 hashcode: -1761403243
Member2 hashcode: -1761403243
members map size is 1

 

 

 

 

결론

  • hashCode 를 직접 만들수 있다.
  • equals 를 오버라이딩 할수 있다.
  • hashCode 나 equals 를 오버라이딩을 하면 
  • 둘다 오버라이딩을 해줘야한다.

  • 다음과 같은 순으로 HashMap에 저장이 이루어진다.
    - HashMap에 값이 없을 경우 HashCode 를 생성후 저장
    - HashMap에 값이 있을 경우 HashCode 를 생성후 비교연산을 거친다

 

 

 

 

 

728x90
반응형

'Language > Java' 카테고리의 다른 글

[Java] BigDecimal  (2) 2021.12.11
[Java] Generic Type Erasure  (0) 2021.10.29
[Java] getter 와 setter  (0) 2021.09.05
[Java] Null 안전하게 다루기  (0) 2021.08.24
[Java] 직렬화, 역직렬화  (0) 2021.08.23

댓글