동일성 (Identity)
두 객체가 완전히 같은 경우를 말한다.
완전히 같은 경우라는 것은 Stack에 저장된 변수가 Heap 영역의 객체들 중 같은 객체의 주소를 가리키고 있다는 것이다.
즉, 같은 객체의 주소값을 바라보고 있는지를 비교하는 것이다.
위 그림에서 refVar2와 refVar3는 같은 객체(객체2)를 바라보고 있으므로 두 변수가 가리키는 객체는 동일하다고 말할 수 있다.
두 객체가 동일한지 아닌지는 == 연산자를 통해 판별할 수 있다.
int, double, char 등의 Primitive 타입은 따로 객체를 가지지 않기 때문에 == 연산자를 사용하여 비교했을 시 같으면 true를 리턴한다.
동등성(Equality)
두 객체에 저장된 값이 같은 값인 경우를 의미한다. 동등성은 변수가 참조하고 있는 객체의 주소가 서로 다르더라도 내용만 같으면 두 변수는 동등하다고 얘기할 수 있다.
동일하면 동등한것이지만, 동등하다고 동일하지는 않다.
동일 > 동등
코드
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2); // false
System.out.println(str1.equals(str2)); // true
위 코드를 보면 str1과 str2는 new 생성자를 통해 각각 생겨난 객체들이므로 동일하지 않다. 그렇기 때문에 == 연산자를 통해 false가 출력되었다.
하지만 .equals()를 통해 안의 내용이 같은지에 대한 동등성 판단에서는 true가 출력되었다.
바로 String 클래스가 Object 클래스를 상속받으면서 .equals()를 글자가 같은지 판별하는 함수로 재정의 하였기 때문에 str1과 str2의 .equals() 비교가 true 값이 출력된 것이다.
.equals()
모든 클래스의 부모 클래스인 Object 클래스의 .equals() 함수는 이렇다.
public boolean equals(Object obj) {
return (this == obj);
}
단순히 == 연산자로 비교를 하고 있기 때문에, 따로 .equals() 함수를 재정의하지 않는 한 == 연산자를 사용하는 것과 다를 바가 없다.
String의 .equals()
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;
}
String 객체에서 .equals() 함수는 먼저 == 연산자로 동등한 지 판단한 뒤에 동등하지 않다면, 같은 String 클래스인지 판별 후 인스턴스 하나하나 비교하면서 같은 문자열인지 아닌지 판별한다.
직접 만든 객체에서 재정의하기
public class Car {
private final String name;
public Car(String name) {
this.name = name;
}
}
Car라는 클래스를 생성하고 같은 이름의 객체 두 개를 생성하고 .equals() 비교를 하게 되면 false가 출력된다.
Car car1 = new Car("아이오닉5");
Car car2 = new Car("아이오닉5");
System.out.println(car1.equals(car2)); // false
만약 위 상황에서 자동차의 이름이 같을 때는 true를 출력하고 싶다면 Car 클래스에서 .equlas() 함수를 재정의 하면 된다.
public class Car {
private final String name;
public Car(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Car car = (Car) obj;
return name.equals(car.name);
}
}
name은 String에서 .equals()를 재정의 해뒀기 때문에 차의 이름이 같은지 비교하는 .equals()함수는 제대로 동작하게 된다.
.hashCode()
만약에 위에서 생성한 car1과 car2를 Set 자료구조에 담게 된다면 어떻게 될까??
Set<Car> cars = new HashSet<>();
Car car1 = new Car("아이오닉5");
Car car2 = new Car("아이오닉5");
cars.add(car1);
cars.add(car2);
System.out.println(cars.size()); // 2
Set은 중복을 허용하지 않기 때문에 두 Car 객체들이 같은 "아이오닉5"이기 때문에 cars의 크기가 1로 기대했지만, 두 개가 담겨있다고 출력되었다.
이렇게 출력된 이유는 Set은 HashTable을 사용하는 자료형이기 때문이다.
Hash Table 자료구조는 어떤 객체가 중복되는지 판단하기 위해 해싱 알고리즘을 사용하는데, 이때 사용되는 것이 객체의 해시코드이다.
car1과 car2의 객체의 이름은 같지만 주소값은 다르기 때문에 중복된 객체가 아니라고 판단해 두 객체를 다 cars에 담을 수 있었던 것이다.
그렇기 때문에 .hashCode() 메서드도 재정의하여서 자료구조에서도 동등성을 보장해주어야 한다.
@Override
public boolean hashCode() {
return Object.hash(name);
}
위와 같이 재정의하게 된다면 두 객체의 주소값은 다르지만 name 필드의 값이 같다면 hashCode()의 값이 동일하게 되고, name 필드를 해싱 알고리즘에 사용할 수 있도록 전달하게 된다.
정리
- 동일성 : == 연산자를 통해 변수가 참조하고 있는 주소값이 같은지 비교하는 것이다.
- 동등성 : equals(), hashCode()를 통해 논리적 지위가 같은지 비교하는 것이다.
- 논리적 지위가 같다의 기준은 개발자가 요구사항에 맞게 Override 하여 재정의해야 한다.
- .equals() : "두 객체가 같다"의 기준이 될 필드들을 비교하도록 재정의하는 것이다.
- .hashCode() : "두 객체가 같다"의 기준이 될 필드들의 값으로 hashCode를 만들도록 재정의하는 것이다.
참고
'멋진 개발자 > Java & Spring' 카테고리의 다른 글
개발자 성장 기록 55 - MyBatis의 동적쿼리 (0) | 2024.04.24 |
---|---|
개발자 성장 기록 54 - JSP (0) | 2024.04.23 |
개발자 성장 기록 51 - MyBatis (0) | 2024.04.18 |
개발자 성장 기록 46 - Java의 접근제어자 (0) | 2024.04.12 |
개발자 성장 기록 45 - String, StringBuffer, StringBuilder (0) | 2024.04.09 |