Java에서 값을 비교하는 방법은 == 연산자와 equals() 메서드를 사용하는 것이다.
== 연산자는 순수하게 값을 비교해주는 연산자이며,
equals()는 논리적으로 같은 객체인지 비교하는 메서드이다.
예를 설명하기 위해 Person 클래스를 만들어보았다.
name이 다르더라도 id 값이 같다면 동일 인물이다.
class Person {
Long id; // 주민등록번호
String name; // 이름
public Person(Long id, String name) {
this.id = id;
this.name = name;
}
}
일단 == 비교 연산을 해보았다.
public static void main(String[] args) {
Person p1 = new Person(1L, "홍길동");
Person p2 = new Person(1L, "홍당무");
Person p3 = p1;
System.out.println(p1==p1); // true
System.out.println(p1==p2); // false
System.out.println(p1==p3); // true
}
대략적으로 메모리를 표현해보았다.
== 비교는 값 자체를 비교하기 때문에 p1 == p2는 0x0001 == 0x0100(참조 주소)를 뜻한다.
그래서 p1 == p2는 false가 나오게 되고, p1 == p3는 true가 된다.
이번엔 equals()를 써보겠다.
public static void main(String[] args) {
Person p1 = new Person(1L, "홍길동");
Person p2 = new Person(1L, "홍당무");
Person p3 = p1;
System.out.println(p1.equals(p1)); // true
System.out.println(p1.equals(p2)); // false
System.out.println(p1.equals(p3)); // true
}
여전히 p1과 p2는 다르다고 나온다. equals()는 Object에 있는 메서드로 오버라이드를 해야 한다.
class Person {
Long id; // 주민등록번호
String name; // 이름
public Person(Long id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
Person person = (Person) o;
return this.id == person.id;
}
}
Person의 id값이 같다면 true가 반환하도록 오버라이드 하였다. 이후 p1.equals(p2)는 true가 나오게 된다.
사실 equals 오버라이드를 하는데 규칙이 있다.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return this.id == person.id;
}
첫째로 자기 자신과 비교하는 구문을 굳이 넣을 필요는 없지만 만약 비교하는 부분이 리소스를 많이 먹는다면 자기 자신 비교는 바로 true가 리턴이 되도록 만들어 준다.
두 번째는 비교하는 대상이 null이나 다른 클래스라면 false를 반환하도록 한다.
이외에 대칭성, 추이성, 일관성을 지켜지도록 equals를 오버라이드 한다.
대칭성이란 객체 x, y에 대해서 x.equals(y) 값과 y.equals(x) 값은 같아야 한다.
추이성은 객체 x, y, z에 대해서 x.equals(y) == x.equals(z) 라면 y.equals(z)가 성립해야 한다.
일관성은 내부 값이 변경되지 않는 한 equals를 호출할 때마다 값이 같아야 한다.
추가로 equals를 오버라이드 했다면 hashcode도 오버라이드를 해줘야 한다. 그렇지 않다면 HashMap과 같은 Collection을 사용할 때 문제가 된다.
public static void main(String[] args) {
Person p1 = new Person(1L, "홍길동");
Person p2 = new Person(1L, "홍당무");
Map<Person,Integer> map = new HashMap<>();
map.put(p1,1);
System.out.println(map.get(p1)); // 1
System.out.println(map.get(p2)); // null
System.out.println(map.get(new Person(1L, "홍길동"))); // null
}
HaspMap에 key를 p1에 value를 1로 저장했다.
논리적으로 p1와 같은 p2로 조회를 했을 때 null이 반환이 되고
p1과 내부 값이 같은 새로운 인스턴스로 조회를 해보아도 null이 반환된다.
모두 id가 동일하기 때문에 map의 값은 모두 1이 나와야 될 것 같지만 그렇지 않다.
이유는 HashMap 같은 경우 hashCode()와 equals()를 사용하는 내부 구조 때문이다.
간단히 설명하면 HashMap의 Key의 값을 hashCode()를 통해 정수 값을 가져온다. 이 정수의 값은 버킷이라는 배열의 인덱스로 사용되는데, 배열의 요소는 링크드 리스트로 이루어져 있다. 해당 위치의 링크드 리스트를 순회하여 equals()를 통해 Value를 찾아낸다. 자세한 건 아래 링크 참조.
Java HashMap은 어떻게 동작하는가? (naver.com)
Person 클래스에 hashCode() 메서드를 오버라이드 하면 위의 코드는 모두 1일 반환한다.
@Override
public int hashCode() {
return Objects.hash(id);
}
참고로 hashCode()의 반환 값을 0으로 고정하면 어떻게 될까?
버킷의 0번째 링크드 리스트만 사용하기 때문에 해시맵을 사용하는 의미가 없어진다.
'Java > Basic' 카테고리의 다른 글
자바의 Optional 알아보기 (0) | 2021.11.09 |
---|---|
ThreadLocal 이란 (0) | 2021.11.01 |
댓글