Java, equals - 동일성 그리고 동등성

Object 클래스는 동등성 비교를 하는 equals() 메소드를 제공.
자바 언어에서의 두 객체가 같다의 표현은 2가지로 분리되어있다.

두 객체가 같다

  • 동일성(identity)
  • 동등성(Equality)

동일성은 "==" 연산자를 사용하여 두 객체의 참조가 동일한 객체를 가리키는지 확인한다.
동등성은 "equals()" 메소드를 사용한 두 객체가 논리적으로 동등한지 확인한다.

단어 정리

동일의 의미는 두 개 모두 완전히 같다로 의미한다.
동등의 의미는 가치와 수준이 같지만, 그 형태와 외관이 다를 수있다.


사용 예시

동일성은 메모리에 있는 객체 인스턴스의 참조 값을 확인한다.
동등성은 논리적으로 같은지 확인한다.
메모리의 참조는 물리적이며, 동등성은 사람이 생각하는 논리적인 기준을 제시하고 비교한다.

Class a = Class("A Class") // x0000001
Class b = Class("A Class") // x0000002

a 와 b의 객체는 메모리에서는 다르지만, 클래스 기준으로 봤을 때는 같은 반이라는 것을 인식한다.

이는 동일성은 다르지만, 동등성은 같다.
문자로 사용하였을 때도 같은 의미로 사용한다.

String str1 = "world";
String str2 = "world";

서로 다른 메모리로 가리키지만 같은 "world" 문자열을 담고 있다.

비교하기

이제 "==", "equals()" 사용한 비교를 해보도록 한다.

system.out.println("identity=" + (a == b));
system.out.println("equality=" + (a.equals(b)));

결과는 동일한 false 값을 출력한다. 자바 Object 에서의 equals 기능은 다음과 같이 정의되어 있다.

Object.equals()

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

기본적으로 자바는 동일성만 비교할 수 있지만, 동등성을 비교하려면 사용자가 메소드 재정의하여 사용해야 한다.


equals() 구현하기

새로운 클래스를 정의한다.

ClassV2.java

public class ClassV2 {
    private String name;

    public ClassV2(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        ClassV2 class = (ClassV2) obj;
        return name.equals(class.name);
    }
}
  • 타입의 매개변수로 Object 타입으로 사용하였다. equals() 비교에는 명시적인 다운캐스팅이 필요하다.
  • this.name 자신의 변수는 Object 넘어온 name 문자열과 비교하게 된다.
  • 문자열 비교는 "==" 사용하지 않고 "equals()" 메소드를 이용해야한다.

ClassV2Main.java

public Class ClassV2Main {
    public static void main(Args[] args) {
        ClassV2 a = ClassV2("A Class");
        ClassV2 b = ClassV2("A Class");

        system.out.println("identity=" + (a == b));
        system.out.println("identity=" + (a.equals(b)));
    }
}

출력 결과

identity=false
equality=true

equals 메소드가 동등성을 비교할 수 있도록 재정의되어 true 문자열을 반환하는 것을 볼 수 있다.


정확한 equals() 구현 규칙

실제로 equals 동등성을 정확하게 동작하려면 몇 가지 규칙이 지켜야 한다.

IntelliJ 와 같은 IDE 에서는 동등성을 쉽게 만들 수 있도록 equals() 코드를 자동으로 생성한다.

IntelliJ 윈도우 유저인 경우 재정의한 equals() 메소드를 삭제한 다음 equals() Alt + Insert 눌려 equals() 메소드를 자동 생성한다.

IDE 자동 생성 equals() 메소드

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null | getClass() != p.getClass()) return false;
    ClassV2 classV2 = (ClassV2) o;
    return Objects.equals(name, classV2.name);
}

equals() 메소드 구현 시 지켜야 규칙

equals 구현한 경우 안전하게 사용하기 위해서는 규칙을 지켜야 한다.

  • 반사성(Reflexivity): 객체는 동등해야 한다. (x.equals(x) == true)
  • 대칭성(Symmetry): 두 객체가 양방향으로 동일해야한다. (x.equals(x) == y.equals(y))
  • 추이성(Transitivity): x 객체가 y 와 동일하고 y와 z는 서로 동일하다면 x 와 z 는 동일하다.
  • 일관성(Consistency): 객체가 변경되지 않는 한, 값은 변경되지 않아야한다.
  • null 비교: 모든 객체는 null 값과 비교한 경우 false 값이어야 한다.

정리하기

  • equals() 메소드로 동일성을 비교하고 싶은 경우 메소드 재정의한다.
  • equals() 와 함께 hashcode() 메소드도 자동 생성된다.