글 작성자: beaniejoy

 String 클래스를 사용하다 보면 equals를 통해 문자열이 일치하는지 비교하는 것을 본 적이 많을 것이다.

 예를 들어 "BeanieJoy"와 "JoyBeanie" 두 개의 문자열이 일치하는지 아닌지를 비교하려고 한다면

     → "BeanieJoy".equals("JoyBeanie") 를 통해 true인지 false인지를 알아볼 것이다.

 

 하지만 Car라는 임의의 클래스를 만들어서 그 안에 정보들을 넣고 그 정보가 다른 객체와 같은지 아닌지를 알아보기 위해 equals를 사용한다면 완전히 다른 결과가 나올 것이다. 예를 들어보자.


≫ Overriding 하지 않은 상황

1
2
3
4
5
6
7
8
9
10
11
12
 
public class EqualsDemo {
    public static void main(String[] args) {
        Car c1 = new Car("00001""black");
        Car c2 = new Car("00001""black");
        
        System.out.println(c1 == c2); // false
        System.out.println(c1.equals(c2)); // false
        
        System.out.println(c1.toString());
    }
}
 

 

 위와 같이 Car라는 클래스를 통해 c1, c2라는 객체를 새로 생성했다. 그 안에 들어가는 모델넘버, 색상변수에 똑같은 값으로 초기화시켰다. 과연 내용이 똑같은 두 개의 객체를 비교한다면 어떻게 될까.

 

 "==" 연산자를 primitive변수가 아닌 참조변수에 사용하면 비교대상들의 참조변수값(어디를 가리키고 있는지 알려주는 값)을 비교하기 때문에 c1 == c2를 하면 그 안에 내용이 같아도 false가 나올 것이다. 그런데 equals를 이용해서 비교한다면 어떻게 나올까. 결과는 false가 나온다. 여기서 equals는 내용물 비교가 아닌 "==" 연산자와 똑같이 참조변수를 비교한다.

 

 String도 클래스로서 참조변수 비교가 아닌 equals를 사용해서 그 내용물인 문자열을 비교할 수 있는데 왜 Car 클래스는 내용물 비교를 못할까. 정답은 Override에 있다.


▶ equals( ) & hashCode( ) 의 Overriding

 

 모든 클래스는 Object 클래스를 상속받는다. 그렇기 때문에 Object에 있는 여러 method들을 가져와서 Override할 수 있다. 그 중에 가장 대표적인 메서드 2개가 있는데 그 중 하나가 equals 메서드다. Object 클래스의 equals 메서드는 "==" 연산자와 같다고 볼 수 있다.

 

 만약 equals를 String 클래스처럼 안에 담고 있는 내용을 비교하고 싶다면 Override를 해야 한다. Override에 대해서는 자세히 언급은 안하겠지만 상속을 받은 상위 클래스의 메서드들을 덮어쓰는 것이라고 생각하면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Car other = (Car) obj;
        if (color == null) {
            if (other.color != null)
                return false;
        } else if (!color.equals(other.color))
            return false;
        if (modelNumber == null) {
            if (other.modelNumber != null)
                return false;
        } else if (!modelNumber.equals(other.modelNumber))
            return false;
        return true;
    }
 

 

 위와 같이 여러 조건문을 통해서 color와 modelNumber가 서로 같은지를 비교해서 true, false값을 출력하게끔 하면 된다.

그런데 여기서 더 나아가서 equals 메서드를 Override할 때 hashCode 메서드도 같이 Override 해야 하는 주의사항이 있다.

 

 hashCode는 객체의 유일한 값을 의미함으로써 주소값과 조금 다른 의미를 쓰인다. Object 클래스에서는 equals 메서드 결과가 true면 비교대상의 hashCode값이 같게 나와야하는 일치성을 중요시 여긴다. equals와 hashCode 메서드는 쌍으로 움직여야 한다는 의미다. 만약 여기서 equals 메서드는 Overriding 했는데 hashCode 메서드는 하지 않는다면 Collection 사용시 큰 문제가 발생한다. 

 

1
2
3
4
5
6
7
8
@Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((color == null) ? 0 : color.hashCode());
        result = prime * result + ((modelNumber == null) ? 0 : modelNumber.hashCode());
        return result;
    }
 

 위의 코드처럼 equals()를 Overriding 할 때 같이 해주면 된다. 내용을 보면 color와 modelNumber 각각의 hashCode를 가지고 계산하는 것을 볼 수 있다. (Car의 속성값들인 color와 modelNumber는 String인데 String 클래스는 equals & hashCode를 Overriding 했기 때문에 만약 비교대상들의 속성값들이 똑같다면 hashCode 결과값도 같은 값을 보여줄 것이다.)

 

≫ Overriding 한 Class
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package java_20191127;
 
class Car {
    String modelNumber;
    String color;
 
    Car(String modelNumber, String color) {
        this.modelNumber = modelNumber;
        this.color = color;
    }
    
    // equals를 Overriding해서 안에 있는 내용물 비교로 바꾸면
    // hashCode()도 이에 맞게 변경해줘야 한다.
    // 맴버변수.hashCode()를 통해서 계산을 해준다.(어차피 내용물이 아예 같으니까)
    // String의 hashCode()도 Overriding 된 것임
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((color == null) ? 0 : color.hashCode());
        result = prime * result + ((modelNumber == null) ? 0 : modelNumber.hashCode());
        return result;
    }
 
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Car other = (Car) obj;
        if (color == null) {
            if (other.color != null)
                return false;
        } else if (!color.equals(other.color))
            return false;
        if (modelNumber == null) {
            if (other.modelNumber != null)
                return false;
        } else if (!modelNumber.equals(other.modelNumber))
            return false;
        return true;
    }
 
    // toString Overriding을 통해 해당 객체안의 내용을 출력해줌
    @Override
    public String toString() {
        return "Car [modelNumber=" + modelNumber + ", color=" + color + "]";
    }    
}
 
public class EqualsDemo {
    public static void main(String[] args) {
        Car c1 = new Car("00001""black");
        Car c2 = new Car("00001""black"); // 서로 다른 hashcode값
        //Car c2 = c1; //hashcode값도 같게 나온다.
        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode()); 
        System.out.println(c1 == c2); // false
        System.out.println(c1.equals(c2)); // true
        
        System.out.println(c1.toString());
    }
}
 

 Car 클래스도 String 클래스처럼 equals & hashCode 메서드 Overriding을 했다. 하고나서 equals를 이용해 메서드를 실행하면 true값이 출력되는 것을 확인할 수 있다. 

 

 

※정리

  1. 모든 클래스는 Object 클래스를 상속받는다.
    그리고 Object 클래스에 정의된 몇몇 메서드들을 필요에 따라 Override한다.
  2. Object에 정의된 equals 메서드는 본래 객체의 참조변수값을 비교하지만 Override를 통해
    객체의 속성값(instance variable)들을 비교하게끔 만들 수 있다. (대표적인 예로 String Class, Wrapper Class)
  3. equals를 Override하면 hashCode 메서드도 똑같이 Override 해주어야 한다.
    (equals & hashCode는 쌍으로 움직인다고 기억하자)