1. Object 클래스의 Equals 와 Hashcode 메소드
자바의 모든 클래스는 Object를 상속받고 있고, Object 클래스에는 equals 와 hashcode 메소드가 존재한다.
즉, 우리가 클래스를 만들게 되면 Object 클래스의 메소드를 Overriding 이 가능하다는 것이다.
1. equals() 란
equals 메소드는 2개의 객체가 동일한지 검사하기위해 사용된다.
public boolean equals(Object obj) {
return (this == obj);
}
메소드를 직접 살펴보면 두객체의 참조하는곳이 같은지를 리턴한다.
즉, 2개의 객체가 가리키는곳이 동일하면 true 아니면 false 를 리턴하는 것이다.
다음의 코드를 살펴보자
public class Main {
public static void main(String[] args) {
String str1 = new String("a");
String str2 = new String("a");
System.out.println("str1 == str2 : "+ (str1==str2)); // false
System.out.println("str1 equals str2 : "+ str1.equals(str2)); //true
}
}
String 형 데이터 str1과 str2를 String Constant Pool에 생성하지 않고 new 예약어를 사용하여 Heap 메모리에 저장했다.
== 비교 연산자는 기본 자료형 변수를 비교했을때는 값이 같은지를 판단하지만,
참조 자료형에 사용할시엔 값이 같아도 객체의 주소값은 다르기 때문에 false 가 출력되는것을 확인할 수 있다.
하지만, str1과 str2 를 리터럴("")로 선언했을 시에는 String Constant Pool에 생성하여 값이 같다면 동일한 객체의 주소를 가리키기 때문에 true가 출력될 것이다.
이번엔 equals 연산자의 결과를 보면 true로 출력되는것을 확인할 수 있다,
분명 다른 주소를 가지고있는 str1, str2 인데 어째서 true로 출력되는것인가?
그답은 String 클래스에는 equals 메소드가 Overriding 해서 문자를 비교하는 코드를 추가했기 때문이다.
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;
}
1. hashCode() 란
hashCode() 는 런타임시 객체의 유일한 Integer 값을 반환한다.
public native int hashCode();
여기서 native 예약어는 메소드가 JNI(Java Native Interface)라는 native code를 이용해 구현되었음을 의미한다. 이는 메소드에만 적용가능한 예약어이며 java 가 아닌 C/C++ 언어로 구현된 부분을 JNI 를 통해서 java에서 이용하고자 할대 사용한다.
또한 hashCode()는 HashTable 같은 자료구조를 사용할 때 데이터가 저장되는 위치를 결정하기 위해 사용한다.
컬렉션 프레임워크(Collection Framework) (4) - 해시테이블(Hashtable), 해시맵(HashMap)
2. equals() 와 hashCode() 의 Overrride
그렇다면 equals() 와 hashCode()를 직접 Overriding해서 사용해보도록 하자
간단한 Dto 클래스를 만들어보면
public class MemberDto {
private String name;
private String phone;
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
public void setName(String name) {
this.name = name;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
public class EqualTest {
public static void main(String[] args) {
MemberDto memberDto1 = new MemberDto();
MemberDto memberDto2 = new MemberDto();
memberDto1.setName("cool");
memberDto2.setName("cool");
System.out.println(memberDto1.equals(memberDto2)); //false
}
}
MemberDto 클래스에는 멤버 변수로 name 과 phone이 있다.
memberDto1, memberDto2 객체를 생성하고, name 변수에 같은 값을 입력한 후 equals 메소드를 사용해서 비교해보면 false가 출력되는 것을 확인할 수 있다.
이는 MemberDto 클래스에 equals 메소드가 Override 되지 않았으므로 Object 의 equals가 호출되어
다른 주소를 가지고 있는 객체 2개를 비교해서 false를 출력하는 것이다. equals 메소드를 Override 해보자
public class MemberDto {
private String name;
private String phone;
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
public void setName(String name) {
this.name = name;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MemberDto memberDto = (MemberDto) o;
return Objects.equals(name, memberDto.name) &&
Objects.equals(phone, memberDto.phone);
}
}
여기서 작성한 equals 메소드는 인텔리제이의 기능으로 완성한 것이며 다시한번 main 을 실행하면 true를 반환하는 것을 볼 수 있다.
그렇다면 equals 메소드만 Override 하면 두 객체는 같다고 볼 수 있는가?
답을 알기 위해서 Java의 자료구조중 하나인 HashSet에 MemberDto 객체를 생성해서 확인해보자.
public class HashcodeTest {
public static void main(String[] args) {
Set<MemberDto> memberDtoSet = new HashSet<>();
MemberDto memberDto1 = new MemberDto();
MemberDto memberDto2 = new MemberDto();
memberDto1.setName("cool");
memberDto2.setName("cool");
memberDtoSet.add(memberDto1);
memberDtoSet.add(memberDto2);
System.out.println(memberDto1.equals(memberDto2)); //true
System.out.println(memberDtoSet.size());
}
}
HashSet 의 요소들은 중복이 불가능한것을 알고있다면, equals() 를 Override 했기때문에 true가 나왔으니 당연히 HashSet 의 요소들의 갯수는 1개가 될거라고 예상할 수 있다.
하지만 결과는 2로 출력된다.
이전의 포스팅에도 올려놨지만 Hash 관련 클래스들은 데이터를 저장할때 hashcode() 값으로 인덱싱하여 자료를 저장하게 된다.
컬렉션 프레임워크(Collection Framework) (4) - 해시테이블(Hashtable), 해시맵(HashMap)
따라서 우리는 아직 hashCode() 를 Override 하지 않았기 때문에 memberDto1 과 memberDto2 는 다른 Hash값을 가지게 되서 다른 객체라고 판단하고 따로 저장하게 되는것이다.
그렇다면 MemberDto 클래스에 hashCode()를 Override 해보자
public class MemberDto {
private String name;
private String phone;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MemberDto memberDto = (MemberDto) o;
return Objects.equals(name, memberDto.name) &&
Objects.equals(phone, memberDto.phone);
}
@Override
public int hashCode() {
return Objects.hash(name, phone);
}
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
public void setName(String name) {
this.name = name;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
equals() 와 마찬가지로 인텔리제이에서 제공하는 기능을 이용하여 hashCode()를 Override 하였다.
이제 다시 출력을해보면 HashSet의 크기는 1로 출력되는 것을 알 수 있다.
3. equals()와 hashCode()를 사용하는 전략
이제 우리는 equals()와 hashCode()를 Override 해서 두 객체가 같은지 비교하는 기능을 수행하는것을 알게되었다.
이제 equals()와 hashCode()를 Override 하는 전략을 알아보자
1. hashCode() 와 equals()를 생성하기 위해서 같은 필드를 이용하여야 한다 ( 예시 : memberDto의 name )
2. equals()는 반드시 일관되어야한다 ( 객체가 수정된것이 아니라면 값이 항상 동일해야한다. )
3. 만약 a.equals(b) 가 true라면 a.hashCode()==b.hashCode() 역시 true여야 한다.
4. equals를 Override 했다면 hashCode()도 Override 해야한다.
참고 : https://howtodoinjava.com/java/basics/java-hashcode-equals-methods/
'JAVA' 카테고리의 다른 글
쓰레드의 지역변수 ThreadLocal (0) | 2022.03.15 |
---|---|
일급 컬렉션 (First Class Collection) 을 사용하자 (0) | 2022.03.01 |
String , StringBuilder, StringBuffer 속도차이 비교 (0) | 2022.02.09 |
방어적 복사 vs Unmodifiable Collection (0) | 2022.02.08 |
자바의 예외구분 Checked Exception, Unchecked Exception (0) | 2022.02.08 |
불변객체(Immutable Object)를 사용해야하는 이유 (0) | 2022.02.08 |