ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] equals() 해시코드 비교 및 개념 정리
    Language/Java 2020. 1. 19. 19:50
    728x90
    반응형

    어떤 백준 알고리즘 문제를 풀다가 문자열 비교해서 같으면 카운트하는 문제가 있었는데 == 을 써도 도저히 먹지 않았다. 왜 안되는지 이해가 안돼서 검색을 해보니 문자열 비교는 equals 를 해야한다고 했다. 그래서 그 차이점을 정리하려고 한다. 

     

     

    들어가기전에 알아보기 

    equals메소드 hashCode() 메소드는 모든 클래스의 최상 클래스인 Object의 메소드라는 것을 알아두자.

     

     

     

    2. equals 메소드와 == 연산자

     

    가령 new 연산자를 이용하면 힙 영역에 메모리가 할당된다. 하지만 new를 이용하지 않고 그냥 참조변수에 값을 넣어버리면 상수 풀이라는 곳을 가르키기 때문에 위의 사진처럼 str2와 str3는 같은 곳을 가르키게 된다. 

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    String str1 = new String("abc");
            String str2 = "abc";
            String str3 = "abc";
     
            System.out.println(str1 == str2);          //false
            System.out.println(str1 == str3);          //false
            System.out.println(str2 == str3);          //true
     
            System.out.println(str1.equals(str2));     //true
            System.out.println(str1.equals(str3));     //true
            System.out.println(str2.equals(str3));     //true
     

     

    str1 은 new연산을 이용해서 힙영역에 메모리가 할당이 되었고, str2, str3는 상수풀의 abc 값을 가르키고 있는 상태이다.

    따라서 == 연산자를 이용해서 주소 값을 비교하면 str2와 str3는 값지만 str1과 비교를 하면 false를 반환하게 된다.

    반면에 equals 메소드는 참조하고 있는 값을 비교하기 때문에 전부다 true를 반환한다는 것을 알 수 있다.

     

    ==연산자는 주소 값을 비교해 같으면 true, 다르면 false를 반환한다. 

    equals 메소드는 대상의 내용 값으로 비교해 같으면 true, 다르면 false를 반환한다. 

     

     

     

    다음 예제를 통해서 확인해보자. 

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class StudentTest {
        int id;
        int score;
     
        public StudentTest(int Id, int score) {
            this.id = id;
            this.score = score;
        }
     
        public static void main(String[] args) {
            StudentTest s1 = new StudentTest(2016, 100);
            StudentTest s2 = new StudentTest(2016, 100);
     
            System.out.println(s1.equals(s2));  // false
            System.out.println(s1 == s2);       // false
        }
    }
     
     
     

     

    위의 예제에서 s1.equals(s2)는 true가 아니라 false가 나온다. 내부 값이 같은 객체인데 ==은 안나온다는건 그렇다 쳐도 equals를 사용해도 false가 나오는 이유가 무엇일까? 그건 현재 equals는 Object의 equals 메소드를 불렀기 때문이다. Object의 equals 메소드는 어떻게 구현이 되어있을까?

     

    1
    2
    3
     public boolean equals(Object obj) {
            return (this == obj);
        }
     
     

     

    Object의 equals 메소드 구현체이다. 내부에서 ==연산자를 이용하기 때문에 주소값이 같아야 true를 리턴하게 된다.

    그러면 String형은 왜 주소값이 다르고 내부 값이 같아도 true를 반환하는 것인가? 이유는 String 클래스에서 equals() 메소드를 오버라이딩 했기 때문이다. 

     

     

    String클래스 equals 메소드 구현체

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public boolean equals(Object anObject) {
            if (this == anObject) {
                return true;
            }
            if (anObject instanceof String) {
                String anotherString = (String)anObject;
                int n = value.length;
                if (n == anotherString.value.length) {
                    char v1[] = value;
                    char v2[] = anotherString.value;
                    int i = 0;
                    while (n-- != 0) {
                        if (v1[i] != v2[i])
                            return false;
                        i++;
                    }
                    return true;
                }
            }
            return false;
        }
     
     

     

    위 소스를 보면 Object로 객체를 받아 하나하나 문자열을 비교하고 있다. char은 참조값이 아닌 기본값이기 때문에 == 만으로도 값비교가 가능하다. 그렇기 때문에 String형으로 equals 메소드를 이용할 때는 주소값이 달라도 내용 값이 같으면 true를 반환할 수 있는 것이다.

     

     

    equals 오버라이딩(id가 같으면 true)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public class StudentTest {
        int id;
        int score;
     
        public StudentTest(int Id, int score) {
            this.id = id;
            this.score = score;
        }
     
        @Override
        public boolean equals(Object obj){
            if(obj instanceof StudentTest) {
                StudentTest st = (StudentTest) obj;
                if (this.id == st.id) {
                    return true;
                }
                
                else {
                    return false;
                }
            }
            return false;
    }
     
        public static void main(String[] args) {
            StudentTest s1 = new StudentTest(2016,100);
            StudentTest s2 = new StudentTest(2016,100);
     
            System.out.println(s1.equals(s2));  // true
            System.out.println(s1 == s2);       // false
        }
    }
     
     
     

     

    id값이 같으면 같은 같다라고 기준을 정하고 equals 메소드를 오버라이딩 해서 s1.equals(s2)가 true가 나온다.

     

     

     

     

    hash code란 무엇인가?

    객체 해시코드란 객체를 식별할 하나의 정수값을 말한다. Object의 hashCode() 메소드는 객체의 메모리 번지를 이용해서 해시코드를 만들어 리턴하기 때문에 객체 마다 다른 값을 가지고 있다. 객체의 값을 동등성 비교시 hashCode()를 오버라이딩할 필요성이 있는데, 컬렉션 프레임워크에서 HashSet, HashMap, HashTable은 다음과 같은 방법으로 두 객체가 동등한지 비교한다.

     

    우선 hashCode() 메소드를 실행해서 리턴된 해시코드 값이 같은지를 본다. 해시 코드값이 다르면 다른 객체로 판단하고, 해시 코드값이 같으면 equals()메소드로 다시 비교한다. 두개가 모두 맞아야 동등 객체로 판단한다.

     

    출처 : https://minwan1.github.io/2018/07/03/2018-07-03-equals,hashcode/

     

    다음 예제를 보면 Key클래스 equals 메소드를 재정의해서 number필드값이 같으면 true를 리턴하도록했다. 그러나 hashCode메소드는 재정의 하지 않았기 때문에 Object의 hashCode() 메소드가 사용된다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    import java.util.HashMap;
     
    public class Key{
        public int number;
     
        public Key(int number){
            this.number = number;
        }
     
        @Override
        public boolean equals(Object obj){
            if(obj instanceof Key){
                Key compareKey = (Key) obj;
                if(this.number == compareKey.number){
                    return true;
                }
            }
            return false;
        }
     
        public static void main(String[] args) {
            HashMap<Key, String> hashMap = new HashMap<>();
     
            //식별키 "new key(1)"로 "홍길동"을 저장함
            hashMap.put(new Key(1), "홍길동");
     
            //식별키 "new key(1)"로 "홍길동"을 읽어옴
            String value = hashMap.get(new Key(1));
            System.out.println(value); // null
        }
    }
     
     

     

    위에 내용에서 hashCode()메소드를 오버라이딩하지 않았기 때문에 값은 null로 출력이 된다. 만약 의도한 대로 홍길동을 출력하려면 아래와 같이 hashCode를 오버라이딩 해야 한다.

     

    왜 null이 출력이 될까? 위의 예제에서는 hashCode 오버라이딩 안했어도 됐는데?

    왜냐하면 put 메소드를 이용할 때의 new key(1)과 value값을 가져오는 메소드인 get을 쓸 때의 new key(1)은 다른 객체이다. 둘다 new 연산자를 이용했기 때문에 메모리가 각자 할당이 된다. 따라서 get메소드에서 key값이 존재하는지 찾는 과정에서 equals 메소드를 사용하는데 이 때 Object의 hashCode()메소드를 반환 값을 받게 된다. 메모리 위치가 다르기 때문에 hashCode값이 달라서 key값으로 value를 찾을 수가 없다.

     

     

     

    HashCode 오버리이딩(number 반환)

    1
    2
    3
    4
    5
    6
    7
    8
    public class Key{
     
      @override
      public int hashCode(Object obj){
        return number;
      }
     
    }
     
     

     

    이렇게 오버라이딩을 하면 현재 객체의 number 값을 반환한다. 그러면 위에서 말한 것처럼 get 메소드를 이용할 때 현재 hashMap내부에 탐색 key값이 존재하는지 여부를 검색 후에 있다면 해당 value를 반환하게 되는데 이 때 키 값끼리 비교하는 과정에 equals 메소드도 사용된다. 이 때는 오버라이딩 했던 hashCode() 메소드가 실행되기 때문에 true를 반환한다.

     

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    import java.util.HashMap;
     
    public class Key{
        public int number;
     
        public Key(int number){
            this.number = number;
        }
     
        @Override
        public boolean equals(Object obj){
            if(obj instanceof Key){
                Key compareKey = (Key) obj;
                if(this.number == compareKey.number){
                    return true;
                }
            }
            return false;
        }
     
     
        @Override
        public int hashCode() {
            return number;
        }
     
        public static void main(String[] args) {
            HashMap<Key, String> hashMap = new HashMap<>();
     
            //식별키 "new key(1)"로 "홍길동"을 저장함
            hashMap.put(new Key(1), "홍길동");
     
            //식별키 "new key(1)"로 "홍길동"을 읽어옴
            String value = hashMap.get(new Key(1));
            System.out.println(value); // 홍길동
        }
    }
     
     

     

    실제 메모리 주소값을 출력하는 법

    System.identityHashCode(참조변수) 를 이용하면 알 수 있다. 

     

     

     

    참고 블로그

    https://minwan1.github.io/2018/07/03/2018-07-03-equals,hashcode/

     

    Wan Blog

    WanBlog | 개발블로그

    minwan1.github.io

     

    반응형

    댓글

Designed by Tistory.