본문 바로가기

Java

[Java] Object.equals()

equals의 스펙이나 이런 것 보다는 (여기에 잘 나와있습니다.)
클래스를 만드는데 있어서 이 equals라는 메서드가 왜 필요한지..
OOP라는 관점으로 한번 써보려고 합니다. (그래봐야 아주 기초적인 내용입니다.)

사실 이런 것은... 논란의 여지가 있기도 할 것 같아서... 좀 그렇지만..

List는 자바에서 가장 많이 사용 되는 Collections 프레임워크에 속해있는 클래스입니다.
잘 알고 계시다시피..
List는 입력 된 순서대로 저장이 되며, index를 통해 접근한다는 특징이 있습니다.
원소를 가지고 있는지를 파악하는 contains라는 메서드도 가지고 있습니다.

일단 아래와 같은 클래스를 만들어 봅니다.



이름과 나이를 갖는 사람을 클래스화 하였습니다.
아래의 코드를 보겠습니다.


실행 결과는 "false" 입니다.

아마.. "저건 당연한 것 아닌가? user1과 user2가 서로 다른 객체 이니까 저건 당연한 결과지.." 라고 생각 하실 수 있을 것입니다.
그렇다는 얘기는 list의 contains 메서드가 엘리먼트들의 equals 메서드를 사용하여 비교한다는 사실도 아시는 것일 거에요.

그런데.. 업무적으로 본다면..이름과 주민번호가 같은 두개의 사람 객체가 다른 객체라고 볼 수 있을까요?

  JVM 입장에서는 저 두개의 객체는 다른 객체입니다.

하지만 클래스를 설계한 입장에서는 주민번호와 이름.. 아니 주민번호만 같아도
두개의 객체는 같은 사람을 뜻하는 객체라고 생각되어져야 할 것 입니다.
그렇지 않다면 엉망이 되어버릴테니까요...

위와 같이 사람 객체 처럼 단 하나만 생성되어야 하는 클래스들은
싱글턴으로 구현을 하기도 하지만, 일단 거기까지는 생각하지 않고 보면
위 상황은 조금 애매하게 보입니다. 싱글턴으로 구현 된 클래스들은 그 변화가
JVM 전역적으로(엄격히 얘기하면 ClassLoader에..여기) 영향을 주기 때문에 다루기도 쉽지 않습니다.

예전에는 책에서 이야기 하는대로 객체지향 프로그래밍을 하기 위해 사람을 클래스화 시켜서
User라는 클래스를 만들었는데.. 이렇게 만들어서 얻어지는 장점이라는 것을
이해 하기가 어려웠습니다.
그저 이름과 주민번호를 담고 있는 Value Object 정도의 느낌이죠..

위와 같은 상황도 좀 작게 보면 비슷한 맥락이라고 생각합니다.

User를 만들어서 List에 넣고 contains 비교를 하는데
없다고 나옵니다. 주민번호가 같은 객체임에도 불구하고요..

저런 상황에서 저 객체가 List에 있냐 없냐를 판단하려고 아래와 같은 코드를 쓰기도 합니다.


User가 주민번호와 이름을 가지고 있으므로 저렇게 구현해도 로직은 틀리지 않을 것 입니다.
그래서 객체를 비교해야 하는 곳에서 모두 저렇게 로직을 구현했다고 했을 때...
그럴 일은 없겠지만 먼~~~ 훗날.. "더 정확한 체크를 위해 의료보험번호도 확인해야 한다!" 라는 로직을 추가해야 한다면..
개발자들은 저렇게 구현되어 있는 모든 부분을 찾아서 수정해줘야 합니다.

if문에 &&가 하나 더 들어가겠죠.

사람을 클래스화 시켜서 만들었지만
할 일은 이전과 큰 변화가 없어 보입니다.

이런 상황에서 무엇을 어떻게 해주면
좀 더 그럴듯한 모습으로 User 클래스를 만들 수 있을까요..

JAVA의 모든 Class들은 Object 클래스를 상속합니다.
그리고 이 Object 클래스에서 꼭 알고 넘어가야 하는 메서드가 있습니다.
바로, equals,hashCode,toString,clone 입니다.

다른 메서드는 당장 여기서 얘기 할 것은 아니고..
equals 메서드가 바로 위와 같은 상황에서 사용되어야 할 메서드이고
일반적으로 클래스를 만들 때 반드시 염두해 두어야 할 메서드 입니다.
equals 메서드에 대한 상세한 내용은 이곳을 참고하세요.

User 클래스라면 equals 메서드를 구현하여 사람 객체는 주민번호가 같으면 같은 객체다 라고 정의해야 하고
책이라면 isbn 넘버가 같으면 같은 객체다 라고 정의를 해주어야 합니다.
그래야 객체와 객체의 관계로 프로그램에서 객체를 핸들링 할 수 있기 때문입니다.
객체와 객체의 관계에 대한 정의를 객체가 가지고 있어야지, 다른 클래스에서 그 로직을 구현하고 있으면
그 관계가 변할 때 마다 수정에 들어가는 노력이 적지 않게 됩니다.


equals 메서드를 오버라이드 하였습니다.
(위 equals 메서드의 내용은 Java에서 제공하는 스펙에는 맞지 않습니다. 그냥 이 글의 내용에 맞게 단순화 시켜버린
메서드입니다.)

위와 같이 User 클래스를 수정하고 다시 예제 프로그램을 실행하면
true를 리턴합니다.

의료보험 번호로 체크를 더 해줘야 한다는 로직이 들어가면?

User 클래스만 변경하면 됩니다. User 클래스가 서로 같은지를 판단하는 것은
User 클래스가 가지고 있으면 됩니다. 다른쪽에 영향을 주지 않습니다.

equals나 clone등의 메서드를 구현하지 않아서
생기는 문제는 나중에 찾아내기가 쉽지 않습니다.
특히 clone같은 경우는 구현을 잘못하면 두개의 다른 객체라고 생각되는 넘들이
값을 공유하는 형태가 되어 버리기 때문에
더더욱 그렇습니다.

단순히 bean을 Value Object나 DTO라고 부르는 역할로 사용하려 한다면
모르겠지만, 비지니스를 분석하여 각 영역을 클래스로 뽑아낸 경우라면
위와 같이 equals 메서드를 override해주는 것을 고려하는 것이 좋다고 생각됩니다.

그런게 아니더라도..
java의 다른 클래스들이 다른 클래스들이 이 메서드들을 제대로 오버라이드 하고 있다고 가정하고 있기 때문에
이게 정상적으로 구현이 되어 있지 않으면
제대로 동작하지 않는 클래스들도 생길 것 입니다.

Map 인터페이스를 구현한 클래스들 (HashMap 등) 의 containsKey는 위와 같이 equals 메서드만 재정의 한 상태로는 제대로 작동을 하지 않을 수 있습니다. 원래대로라면 equals 메서드를 재정의 하면 hashCode 메서드도 재정의를 해줘야 합니다.