티스토리 뷰
오늘은 동등성 비교와 동일성 비교가 무엇인지, 자바에서는 이러한 기능을 어떻게 제공하는 지에 대해서 작성해보도록 하겠습니다
객체
먼저 객체에 대한 이야기를 해야하 것 같습니다. OOP에서 객체는 프로그램의 기능을 담당하는 독립적인 존재이며, 메시지 교환을 통해 프로그램이 동작합니다.
객체를 구성하는 요소는 상태, 행동, 식별자입니다.
- 상태: 객체의 정보 집합
- 행동: 객체의 상태를 변화시키며, 상태에 의존적이다
- 식별자: 객체를 구분하는 상태
상태는 객체의 정보 집합입니다. 예를 들어, 사람의 경우 키와 몸무게 이름 등이 상태가 될 수 있습니다. 단순한 값만 상태가 될 수 있는 것은 아닙니다. 한 사람이 음료수를 가진다면, 음료수에 대한 정보도 객체의 상태 중 하나가 될 수 있습니다.
행동은 상태 값을 변화시키며, 상태에 의존적입니다. 예를 들어, 백신의 접종 여부를 사람 객체의 상태 중 하나로 가정해 봅시다. 어떤 가게에 백신 접종이 완료된 사람만 출입이 가능하다면, 접종 여부에 따라 사람의 행동이 달라질 수 있습니다. 그리고 병원에 방문하여 백신 접종을 하게 된다면, 사람의 백신 접종 여부라는 상태의 값이 True로 변화하는 것입니다.
식별자는 객체를 구분하는 것입니다. 학생 객체에서는 학번이 객체의 식별자가 될 수 있을 것입니다.
학교에서 어떠한 정보를 처리해야할 때, 학생 객체를 비교해야할 것입니다. 이 학번을 가진 학생이 누구인지, 현재 몇 학년인지, 3 학년이라면 수능을 치르기 위해 백신 접종은 했는지를 확인 하는 것 처럼 말입니다.
동등성과 동일성
학생을 비교하는데는 학번, 이름, 학년과 같은 다양한 상태를 사용할 수 있습니다. 식별자를 사용하여 객체를 비교하는 것을 동일성 비교, 객체가 가진 어떠한 상태의 값을 비교하는 것을 동등성 비교라고 합니다.
- 동일성 비교: 객체의 식별자를 비교하는 것
- 동등성 비교: 객체 내부의 상태 값을 비교하는 것
예를 들어, '951212-1234123' 이라는 주민 등록번호를 가지고 있는 사람은 태어난 이후 10년 20년이 지나도 같은 사람입니다. 주민 등록 번호를 비교하여 전산 상 저장되어 있는 이 사람이 지금 내 앞에 있는 사람과 같은 지 비교하는 것은 동일성 비교입니다.
백신 접종 수요 조사를 위해 전교생을 집합 시키고, 이 학생이 3학년 학생인지 확인하기 위해 학년을 비교하는 것은 동등성 비교입니다.
자바에서 '=='과 'equals()'
자바에서는 비교를 위해 '=='과 'equals()' 를 제공합니다. 다양한 블로그 글을 읽어 보았고, 공통적으로 '=='는 동일성 비교이며,'equals()'는 동등성 비교라고 작성되어 있어습니다.
저는 두 비교 방법이 이분법적으로 나뉠 수 있을 지에 대해 고민하였고, 현재 참여하고 있는 교육의 멘토 분과 토론하여 이분법적 나눌 수 없다 로 결론 지었습니다. 왜냐하면, 개발자가 식별자를 어떻게 정의하냐에 따라 달라질 수 있기 때문입니다.
아래와 같은 학생 클래스가 존재합니다. sNum은 유한 범위를 가지지만, 학생마다 서로 다른 값을 가지고 있다고 가정합니다.
public class Student {
private int sNum;
private int grade;
private String name;
Student(int sNum, int grade, String name) {
this.sNum = sNum;
this.grade = grade;
this.name = name;
}
public int getSNum() {
return sNum;
}
public int getGrade() {
return grade;
}
public String getName() {
return name;
}
}
이제 Main 클래스에서 학생 객체를 생성해봅시다.
public class Main {
public static void main(String[] args) {
Student s1 = new Student(1, 3, "HAM");
Student s2 = s1;
Student s3 = new Student(1, 3, "KIM");
System.out.println("s1 == s2 : " + (s1 == s2)); // true
System.out.println("s1 == s3 : " + (s1 == s3)); // false
System.out.println("s1.equals(s2) : " + (s1.equals(s2))); // true
System.out.println("s1.equals(s3) : " + (s1.equals(s3))); // false
}
}
s1과 s3는 당연히 이름도 다르고 new로 새로 생성한 객체이기 때문에 false가 출력이 되었고, s2에는 s1에 저장된 참조 값이 그대로 저장되었기 때문에 '=='과 'equals()' 모두 true가 나왔습니다.
위에서 개발자가 식별자를 어떻게 정의하느냐에 따라서 달라지기 때문에 두 방법을 이분법적으로 나눌 수 없다고 말씀드렸습니다. 만일 개발자가 학생 객체가 저장된 메모리 주소가 아닌 학생의 학번으로 객체를 식별한다면, 'equals()'는 동일성, 동등성 비교 중 어느 것일까요?
아래와 같이 equals()를 재정의 하고 비교해 봅시다.
public class Student {
private int sNum;
...
@Override
public boolean equals(Object obj) {
if (obj instanceof Student) {
Student other = (Student) obj;
return this.hashCode() == obj.hashCode();
}
return false;
}
@Override
public int hashCode() {
return sNum;
}
}
결과 코드도 조금 변경해보겠습니다.
public class Main {
public static void main(String[] args) {
// 예시 1
Student s1 = new Student(1, 3, "HAM");
Student s2 = new Student(2, 3, "JUNG");
Student s3 = new Student(1, 3, "KIM");
System.out.println("s1 == s2 : " + (s1 == s2)); // false
System.out.println("s1 == s3 : " + (s1 == s3)); // false
System.out.println("s1.equals(s2) : " + (s1.equals(s2))); // false
System.out.println("s1.equals(s3) : " + (s1.equals(s3))); // true
}
}
s1과 s2는 객체가 저장된 메모리 주소, 학번 모두 다르기 때문에 두 비교 결과 모두 false가 나왔습니다. s1과 s3는 객체가 저장된 메모리 주소는 다르지만, 학번이 같기 때문에 true가 나왔습니다.
여기서 'equals()'는 동일성 비교를 담당하고 있습니다.
이제 개발자가 equals() 메소드를 이름을 비교하도록 재정의 하고, 객체 식별자 비교는 '=='으로 하도록 변경했습니다.
public class Student {
...
private String name;
...
@Override
public boolean equals(Object obj) {
if (obj instanceof Student) {
Student other = (Student) obj;
return this.hashCode() == obj.hashCode();
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
public class Main {
public static void main(String[] args) {
// 예시 2
Student s1 = new Student(1, 3, "HAM");
Student s2 = new Student(2, 3, "HAM");
System.out.println("s1 == s2 : " + (s1 == s2)); // false
System.out.println("s1.equals(s2) : " + (s1.equals(s2))); // true
}
}
'=='의 결과 당연히 두 객체의 주소가 다르기 때문에 false가 나왔고, 'equals()'는 true가 반환되었습니다. 여기서 'equals()'는 동일성 비교인가요, 동등성 비교인가요?
제 생각에는 동등성 비교입니다.
결론
'=='는 객체가 저장된 메모리의 주소를 비교하기 때문에, 두 객체가 같은 곳에 저장되어 있는 지 비교합니다. 따라서 객체의 식별자를 메모리 주소로 정의한다면 '=='는 동일성 비교가 될 수 있습니다.
'equals()'는 상황에 따라 다릅니다. 객체의 인스턴스 멤버를 식별자로 설정하고 equals() 메서드를 재정의 한다면, equals()는 동일성 비교일 것입니다. 하지만 객체의 인스턴스 멤버 중 중복이 허용되는, 즉 객체를 식별할 수 없는 값을 비교하도록 equals()를 재정의 한다면 이는 동등성 비교입니다.
하지만 객체를 비교할 때 '=='을 사용하는 것은, 잘 모르지만 드물지 않을까 생각합니다. 자바에서 재공하는 HashMap을 사용할 때도 충돌이 발생한 경우 객체의 동일성 여부를 equals()의 결과로 판단합니다.
동일성, 동등성은 개념적인 부분이고 equals()와 ==는 자바가 제공하는 비교 기술입니다. 단순히 자바의 두 기술을 이분법적으로 나누는 것은 옳지 않습니다.
상황에 따라 객체를 유일하게 식별하는 상태 값을 비교할 경우는 동일성 비교, 객체 내부의 상태 값을 비교하여 어떠한 범주로 객체를 나눌 경우를 동등성 비교로 보는 것이 맞다고 생각합니다.
'Java' 카테고리의 다른 글
[Java] Enum 기본 (0) | 2021.08.20 |
---|---|
리스트 순회 중 발생하는 ConcurrentModificationException에 대해 (0) | 2021.07.25 |
[모던 자바 인 액션] 스트림 (0) | 2021.06.24 |
[모던 자바 인 액션] 3. 람다 표현식 (0) | 2021.06.21 |
[모던 자바 인 액션] 2. 동작 파라미터화 코드 전달하기 (0) | 2021.06.17 |