2022. 10. 19. 17:36ㆍJAVA
오브젝트의 동일성과 동등성
Java에서 "두 객체가 같다"라는 말은 두 가지로 해석될 수 있다.
두 개의 오브젝트가 완전히 같은(identical) 오브젝트라고 말하는 것과, 동일한 정보를 담고 있는(equal) 오브젝트라고 말하는 것은 분명히 다르다.
전자를 동일성(identity) 비교라 하고 후자를 동등성(equality)비교라고 한다.
동일성은 ==, 동등성은 equals() 메소드를 이용해 비교한다.
동일성은 하나의 오브젝트를 두 개의 레퍼런스 변수가 가리키고 있는 상태
동등성은 두 개의 다른 오브젝트가 메모리 상에 존재하나, 로직상의 정의에 따라 오브젝트 정보가 같다고 판단
equals()와 hashCode()
java.lang.Object 클래스는 eqauls()와 hashCode() 메소드를 제공한다.
hashCode()
Object 클래스의 hashCode() 메소드는 기본적으로 객체의 메모리 주소를 이용해 해시 값을 리턴하므로 객체마다 서로 다른 값을 가지고 있다.
해시 값은 HashTable과 같은 자료구조를 사용할 때 데이터가 저장되는 위치를 결정한다.
현재 Java 11을 사용중인데, Object 메소드 대부분에 @HotSpotIntrinsicCandidate 어노테이션이 붙어있다. 일단은 컴파일러 성능을 위한 어노테이션인 것만 알고 넘어가도록 하겠다.(해당 어노테이션이 있다면 HotSpot(JVM의 구현체)에 내재화(intrinsify) 될 수도, 안 될 수도 있다.VM에 내재화된다는 것은 성능 향상을 위해 컴파일러가 메소드를 인라인 코드로 대체한다는 것을 의미하는데, 이 과정에서 low-level 검사를 생략하나보다..)
native 키워드는 이 메서드가 OS의 메서드(C, C++, 어셈블리어)를 호출해서 사용한다는 뜻이다.
JNI는 자바 가상 머신 위에서 실행되고 있는 자바코드가 C, C++ 그리고 어셈블리 같은 다른 언어들로 작성된 라이브러리들을 호출하거나 반대로 호출되는 것을 가능하게 하는 프로그래밍 프레임워크이다.
(더 자세히 알고싶다면 JNI를 검색해보자.)
equals()
Object 클래스의 equals 메소드는 기본적으로 동일성 비교(==)를 수행한다.
이는 int, long 등의 primitive type인 경우에는 사용 가능하지만 객체를 비교할 때는 상황이 다르다.
객체의 경우에는 동일성 비교에서 true를 반환하기 위해서는 두 변수가 가리키는 메모리 주소가 같아야 한다.
객체는 new 키워드로 생성되고 메모리 영역에서 서로 다른 주소값을 갖게 되므로, 객체의 필드가 완전히 동일해도 Object의 equals 메소드는 false만을 리턴한다.
따라서 동일성 비교를(==) 하는 Object의 equals를 오버라이딩 해서 객체 간 동등성 비교가 가능하도록 해야 한다.
public class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
}
1. 위의 코드처럼 equals()만 오버라이딩하는 경우
public class Main {
public static void main(String[] args) {
Person p1 = new Person("kiseo", 26);
Person p2 = new Person("kiseo", 26);
System.out.println(p1.equals(p2));
Map<Person, Integer> map = new HashMap<>();
map.put(p1, 1);
map.put(p2, 2); // 키 값 중복으로 map의 size는 1?
System.out.println(map.size());
}
}
hashCode()는 오버라이딩 하지 않았으므로 현재 Person 클래스에는 동등한 객체여도 해시 값이 다르다는 문제점이 있다.
따라서 HashMap에 동등한 객체를 저장해도, 해시 값이 다르기 때문에 map에는 두 개의 객체가 서로 다른 위치에 저장된다.
Person 클래스에서 hashCode() 메소드도 같이 오버라이딩 해주어야 기대했던 값이 나온다.
IntelliJ의 generate을 사용하면 Objects 유틸 클래스의 hash() 메소드로 재정의한다.
@Override
public int hashCode() {
return Objects.hash(name, age);
}
2. hashCode()만 오버라이딩하는 경우
HashMap, HashSet, HashTable 등의 컬렉션에서 get()으로 검색할 경우
Map<Person, Integer> map = new HashMap<>();
1) hashCode() 메소드를 실행해 리턴된 해시 값이 같은지 확인한다.
2) 만약 해시 값이 다르다면 다른 객체로 판단하며, 해시 값이 같다면 equals() 메소드로 다시 비교한다.
이 두 값이 모두 동일해야 동등 객체로 판단한다.
객체가 완전히 다르더라도 같은 해시 값을 갖는 해시 충돌이 발생할 경우, 2번 로직의 equals에서 식별이 불가능하기 때문에,
hashCode()만 오버라이딩 한 경우도 해시 관련 컬렉션 프레임워크를 사용할 수 없다.
'JAVA' 카테고리의 다른 글
Java - Calculator 과제 회고 (0) | 2022.10.30 |
---|---|
Java 숫자야구 (0) | 2022.10.21 |
Collection과 Iterator (0) | 2022.10.20 |
JAVA 인터페이스 기능과 디폴트 메소드(Default Method) (0) | 2022.10.20 |
Optional<T> (0) | 2022.02.16 |