티스토리 뷰

Java & Kotlin

Java Equals, HashCode

필자A 2022. 7. 17. 17:18

 

 

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
링크
«   2025/01   »
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
글 보관함