티스토리 뷰
internal consistency:
1. Java 11을 사용하며 예시에 나온 코드가 다를 수 있습니다.
2. 참조 : baeldung https://www.baeldung.com/java-equals-hashcode-contracts
목차
0. 언제 사용하지?
1. 사전 지식 + 규칙
2. 간단한 예시
3. 나올지도? 실패 케이스
0. 언제 사용하지?
저의 경우 웹서비스 기반의 개발만 해보아서 equals, hashCode의 필요성을 많이 못 느꼈습니다.관계형 DB의 데이터를 가져와 해당 row의 식별자 값으로 비교를 하였으나
식별자가 없는 object를 다루는 경우 많은 도움이 될 것 같습니다.
1. 사전 지식 + 규칙
사전 지식
1. 동일성
완전히 동일한 것 을 의미 ( 두 객체의 주소 값이 같다 )
2. 동등성
내부 멤버 변수가 같다
3. Equals ( 동등성 )
class의 객체 내부 멤버 변수 비교에 사용할 수 있습니다.
4. HashCode
hash 값을 사용 시 필요
5. == ( 동일성 )
객체 주소 값 사용 시 필요
equals의 규칙✨
reflexive(반사성)
- 자기 자신과의 검증에는 같다는 결과가 나와야 한다.
symmetric(대칭성)
- x.equals(y)와 y.equlas(x) 의 결과는 같아야 합니다.
transitive(추이성, 삼단논법)
- x.equals(y) 와 y.equals(z)의 결과가 동일하다면 x.equals(z)의 결과도 동일해야 합니다.
consistent(일관성)
- 반복된 equals()의 호출에도 일관적인 결과를 반환해야 한다.
hashCode의 규칙✨
internal consistency
- hashCode 의 값은 equals에서 사용되는 속성이 변경되어야 만 변경될 수 있다.
equals consistency
- 같은 객체는 같은 hashCode를 가진다.
collisions
- 다른 객체라도 같은 hashCode 를 가질 수 있다.
2. 간단한 예시
Man class
class Man {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
main function
Man p1 = new Man();
p1.setAge(12);
p1.setName("JungMin");
Man p2 = new Man();
p2.setAge(12);
p2.setName("JungMin");
System.out.println("필드 비교 " + p1.equals(p2));
System.out.println("주소 비교 " + (p1 == p2));
result -->
필드 비교 false
주소 비교 false
Equals는 class의 객체 내부 필드 값 비교에 사용할 수 있습니다.
== 는객체 주소 값 사용 시 필요
"equals(), ==" 2개의 결과 모두 false로 결과가 나왔습니다.
== 는 서로 다른 객체이니 예상한 결과이지만
equals()는 이해할 수가 없습니다.
왜냐 Man.equals()는 overriding 하지 않아 부모 클래스의 overriding을 사용합니다.
Object class의 기능을 사용하고 있습니다.
public boolean equals(Object obj) {
return (this == obj);
}
내부적으로 보니 Object의 equals 도 "=="를 사용하고 있습니다.
그러면 main function에서 사용한 equals 도 == 를 사용하니 둘 다 주소 값 비교를 하고 있었군요.
p1, p2 둘 다 다른 주소 값을 가지니 옳은 결과를 보여줬습니다.
Equals() 추가
@Override
public boolean equals(final Object o)
{
// this(비교 주체) 와 o(비교 대상) 가 같은 주소가 같으면 성공
if(this == o ) return true;
// o 가 null 이거나 this 의 class 와 o의 class 가 다르면 실패
if((o == null) || (this.getClass() != o.getClass())) return false;
// 위에서 동일한 class 로 확인되어 Object 를 Man 으로 casting 합니다.
final Man that = (Man) o;
// 원하는 결과를 위한 로직을 작성합니다.
if(this.age == that.age && this.name == that.name)
return true;
else
return false;
}
--> result
equals check, 필드 비교 true
== check, 주소 비교 false
Man class에 equals()를 overriding 했습니다.
대략적으로 주소, null, class check를 하고 실제 멤버 변수들을 비교합니다.
overriding 한 equals 기능이 동작하여 p1, p2 가 다른 주소 값을 가져도
멤버 변수가 같을 시 같은 객체라고 판단합니다.
추가☠️
1.
만약 멤버 변수가 참조 타입 일시 null check가 필요합니다.
Objects.equals() 메서드를 사용하면 null check 도 함께 가능합니다.
Objects.equals() ->
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
2.
getClass() 사용 시 동일 클래스만 true를 반환합니다.
상속관계에서는 false를 던집니다.
이번엔 p1, p2를 hashSet에 넣어보겠습니다.
main function
final Set<Man> manSet = new HashSet();
manSet.add(p1);
manSet.add(p2);
System.out.println(manSet.size());
System.out.println(p1.hashCode() + " / " + p2.hashCode());
--> result
2
1802598046 / 659748578
아.. 2개의 객체가 모두 들어가 size()가 2를 반환했습니다.
hash 값을 사용하는 기능들은 판단기준을 hashCode()에 근거하며
Man class 가 hashCode()를 overriding 안 하여
Object의 hashCode를 사용합니다.
Object class ...
@HotSpotIntrinsicCandidate
public native int hashCode();
native로 작성되어있군요..^^
기본적으로 메모리 주소 값으로 값을 만든다라고 생각하면 됩니다.
@Override
public int hashCode() {
final int prime = 31;
int hashcode = 1;
// field 가 null 이면 0 반환, null 이 아닐시 field 의 hashCode hashcode 에 할당
hashcode = prime * hashcode + ((name == null) ? 0 : name.hashCode());
// 원시값이라 null check 불필요
hashcode = prime * hashcode + age;
return hashcode;
}
--> result
hashSet.size() 1
man instance 2개 hash 값 -170577345 / -170577345
Man class에 hashCode를 가져왔습니다.
이번엔 set의 사이즈가 1이 나왔군요.
가져온 hashCode 로직을 사용하여 처리되었습니다.
p1, p2의 값이
Man p1 = new Man();
p1.setAge(12);
p1.setName("JungMin");
Man p2 = new Man();
p2.setAge(12);
p2.setName("JungMin");
동등하여 hash 값도 동등하게 나오게 되었습니다.
그냥 equals 만 사용할게 하여 hashCode를 무시하면
HashSet, HashMap 등에서 위험성이 생기게 됩니다.
안전을 위하여 eqauls, hashCode를 overriding 해주면 좋을 것 같습니다.
#️⃣ 번외 : hashSet.add() 가 언제 hashCode(), Equals() 를 사용할까?
add 로 들어온 파라미터를 그대로 map.put 으로 pass 합니다. ( 내부적으로 hashMap 으로 hash 자료구조를 구현 )
hash(key) 로 쭉쭉 넘겨 key.hashCode() 를 사용합니다.
key.hashCode() 값으로 putVal() 로 진입하여 equals 를 사용합니다.
3. 나올지도? 실패 케이스
1. 상속관계
- 상속관계에 있어 자식 클래스 기준에서 동등성 검사가 실패할 수 있습니다.
동일한 상속도를 가지고 있어도 getClass(), instanceOf 를 잘못 사용 시 실패 할 수 있으며
자식 클래스에서 작성한 멤버 변수로 인하여 도 실패할 수 있습니다.
이에 대하여는 composition을 활용할 수 있습니다.
2. HashCode
- HashMap, HashSet에서(hashCode를 다루는 것 아무거나)
equals, hashCode를 둘 다 재정의 해야 합니다.
Object.hashCode 주석
Returns true if this set contains the specified element. More formally, returns true if and only if this set contains an element e such that Objects.equals(o, e).
이 집합에 지정된 요소가 포함되어 있으면 true를 반환합니다. 더 공식적으로, 이 집합에 Objects.equals(o, e)와 같은 요소가 포함된 경우에만 true를 반환합니다.
'Java & Kotlin' 카테고리의 다른 글
Java Type AutoCasting (0) | 2022.07.25 |
---|---|
java primitive type (0) | 2022.07.23 |
kotlin 기본 사용법 (0) | 2022.07.16 |
builder pattern 연습해보기 (0) | 2022.06.29 |
Kotlin @JvmStatic (0) | 2022.06.27 |
- Total
- Today
- Yesterday
- JDK
- java8
- 자사서비스
- springboot
- 알고리즘
- jre8
- 관계설정
- boot
- JDK8
- boot 일대다
- 스타트업
- mappedby
- 백준 제로
- jre
- 스택
- 개발자채용
- 프로그래머
- 문제
- 다대일
- ㅃ
- jdk11
- 백준
- 코딩테스트
- JPA
- jre11
- 백엔드
- Spring
- jvm
- 자바
- 백준 제로 자바
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |