ejyoo's 개발 노트

Java equals(), HashCode() 본문

BackEnd/Java

Java equals(), HashCode()

ejyoovV 2021. 3. 4. 21:41

equals 메서드와 hashcode 메서드는 Object 클래스의 메서드이다.

Object는 상속 관게 상 가장 최상위 게층이므로 (모든 클래스가 Object를 상속)

어떤 객체라도 Object 의 메서드인 equals와 hashcode를 사용할 수 있다.

HashCode는 Object에 기본적으로 있으며 메모리 기반 int 값기준으로 작성하여 던져준다.

 

이 두개의 개념이 필요할 때는 바로 사용자가 생성한 객체를 비교할 때이다.

해시 코드 개념 전에 해시 함수(hash function)에 대해 알아야 한다.

 

💡 해시함수(hash function) 

 임의의 데이터를 고정된 길이의 데이터로 매핑하는 함수

  해시 함수에 의해 얻어지는 값은 해시 값, 해시 코드, 해시 체크섬 이렇게 존재하는데, 이것을 간단하게 해시라고 한다.

(암호학적 해시함수 및 비암호학적 해시함수)

 

그래서 HashSet, HashMap, HashTable과 같은 객체들을 사용할  때,

객체가 서로 같은지를 비교하기 위해 equals()메서드와 hashCode() 메서드를 호출한다.

 

위에서 equals() 메서드와 hashcode() 메서드는 Object에도 존재한다고 했다.

하지만 Object 에 있는 equals() 메서드와 hashcode()를 사용하게 되면

그 객체가 동일한지 비교할 수 없다.

아래의 예를 살펴보자.

  👀 Person 이라는 클래스를 생성한 뒤 Person 객체를 생성하여 번호, 이름을 생성자를 사용하여 초기화한다.

  👀 Person 이라는 객체가 총 3개가 있고 셋중 2개는 같은 값을 넣어주었다.

Java 코드 일부

  👀 이럴때, 우리는 같다고 생각한다.

  👀 오버라이드 한 equals 없이 Object 클래스 내 존재하는 equals 를 사용하여 비교를 해보았다.

Java 코드 일부
출력 결과

  👀 값은 분명 같은 값인데 결과는 false 이다. 

이러한 이유로 Object 에서 제공하는 equals 만으로 내가 새로 만든 객체에 대한 값을 비교할 수없다.

 

💡 그렇다면 내가 생성한 객체를 비교하려면 어떻게 해야하는가?

  내가 생성한 객체가 서로 같은지를 비교하기 위해 equals() 메서드와 hashCode() 메서드 둘다 재정의 해야한다.

  참고로 HashSet, HashMap, HashTable에서는 객체가 같은지 여부를 데이터 추가하는 시점에 검사한다.

  

  그렇다면 오버라이딩할 equals() 메서드와 hashCode() 메서드의 개념을 살펴보자면,

 

  • equals() : 두 객체의 내용이 같은지 확인하는 메서드
  • hashCode() : 두 객체가 같은 객체인지 확인하는 메서드

  로 개념을 정의할 수 있다.

 

💡 두 메서드를 오버라이딩 시 규칙이 존재한다.

  1. 두 객체가 같으면 반드시 같은 hashCode를 가져야 한다.
  2. 두 객체가 같으면 equals() 메서드를 호출햇을 때 true를 반환해야 한다.
    즉 객체 a, b가 같다면 a.equals(b)와 b.equals(a) 둘다 true 이어야 한다.
  3. 두 객체의 hashcode가 같다고 해서 두 객체가 반드시 같은 객체는 아니다.
    하지만 두 객체가 같다면 반드시 hashcode 가 같아야 한다.
  4. equals() 메서드를 오버라이드 하면 반드시 hashcode() 메서드도 오버라이드 해야한다.
  5. hashCode()는 기본적으로 Heap에 있는 각 객체에 대한 메모리 주소 매핑 정보를 기반으로 정수값을 반환한다.
    그러므로, 클래스에서 hashCode() 메서드를 override 하지 않으면 절대로 두 객체가 같은 것으로 간주될 수있다.
    - hashCode() 메서드에서 사용하는 '해싱 알고리즘'에서서로 다른 객체에 대하여 같은 hashcode 값을 만들어 낼 수 있다. 그래서 객체가 같지않더라도 hashcode가 같을 수 있다.

💡 오버라이딩 시 규칙이 이해가 가지 않아요!

a 객체를 만든 후 a.hashcode = 3; 이 된 뒤 b객체를 만든 후 b.hashcode를 했을 때, b.hashcode=3이 나올 수 있다.(매우적은확률로)

따라서 해쉬 알고리즘을 사용하는 컬렉션 객체들은 중복 체크 시 해쉬코드를 사용하여 비교를 하는데,

이때 해시코드 값이 같을 경우가 적은 확률로 존재한다는 말이다.

우리가 개발할 때 이러한 적은 확률도 무시하면 안된다.

따라서 두 객체의 hashcode가 같다고 해서 두 객체가 엄연히 다른 객체지만

hashcode가 동일하게 나올 수 있는것이다.

이러한 경우를 대비하여 equals()메서드로 한번 더 검사를 하는것이다.

그래서 오버라이드할 때, hashCode와 equals 메서드를 둘다 오버라이드 하여 두 객체 값을 비교해야 하는 것이다.

 

첫번째로 객체가 같은지 비교를 할 때,

hashcode로 먼저 비교를 하는데, 

hashcode가 다르다고 판명이 난 객체는 무조건 다르다고 할 수 있다.

다만, hashcode가 같다고 판명이 났을 때,

매우 작은 확률로 다른 두 객체의 hashcode가 같을 수 있다고 설명했다.

이럴 때 equals를 사용하여 비교를 하는 것이다.

 

처음에 hashCode를 호출하여 비교를 하고, 해시코드가 다른경우 해당 작업에서 비교를 마친다.

그러나 적은 확률로 다른 객체가 hashCode 값이 같은경우, 

equals를 한번 더 호출하여 확실하게 파악하고 종료한다.

 

결국 해시 알고리즘에서는 hashCode 메서드를 먼저 호출하고 

우연치 않게 해시코드가 같으면 equals를 호출해서 명확화 한다.

결과적으로 오버라이드 할 때는 둘다 오버라이드 한다.

 

 

💡 hashCode, equals 메서드 오버라이드 예시

위의 개념을 통해서직접 예시를 적어본다.

Person 객체를 생성한다.

//우리만의 객체를 만들기위해 클래스화 함.
class Person{
	private int id;
	private String name;
	public Person(int id, String name) {
		super();
		this.id = id;
		this.name = name;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

이 Persion 내에 있는 멤버(id, name)을 비교하기 위해서는 

hashCode()와 equals를 오버라이드 해야 한다고 했다.(공식으로 그냥 생각하면 마음 편함)

 

자바에서는 자주 사용하는 코드들을 자동으로 오버라이드 할 수 있게 구현해놓았다.

우클릭 - Source

Source 메뉴에서 'Generate hashCode() and equals()' 를 클릭하여 오버라이드 한다.

오버라이드 하면 내가 생성한 객체에 대한 비교를 했을 때, 

맨 위에 설명했던 오버라이드 된 hashCode() 와 equals()가 없을 때 실행했던 결과가 false 때와 다르게

오버라이드 된 hashCode(), equals() 가 있으므로

코드의 결과는 true 가 나온다.

Java 코드
오버라이드 한 후 Java코드 Run 결과

오버라이드 된 hashCode() 와 equals 는 다음과 같다.

//	이클립스에서 자동 완성 기능을 활용하여 만듬(hashCode, equals)
//	오버라이드 된 이유는 Object에 기본적으로 제공하는 기능이라서 오버라이드 할 수 있는것임.
//	오버라이드 한 이유는 Object에서 제공하는 기능을 사용하지 않고 새로운 형태로 사용을 하겠다. 라는 의미임.
//	Object는 두 객체가 다르면 false
	@Override
	public int hashCode() {
//		이 알고리즘을 해도 잘 동작할거야.
		final int prime = 31;//소수 : 1과 자기자신으로만 나눌 수 있는 것.
		int result = 1;
		result = prime * result + id;//객체의 id 값
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	
//	Object에 정의된 equals를 사용하는 경우 생성한 객체에 대해 equals를 사용하면 값이 같은지 비교할 수 없다.
//	따라서 오버라이드 해서 재정의 해야 한다.
	@Override
	public boolean equals(Object obj) {
		if (this == obj)//this : p1 = p1넣은 경우 (this.p1.equals(object))
			return true;
		if (obj == null)//p1.equals(null)
			return false;
//		getClass() : Object가 속한 클래스정보를 가져옴
//		Object를 만들라면 Class가 존재해야된다. 클래스 없이 Object를 만들 수 없다.
		if (getClass() != obj.getClass())//getClass() : Person , obj.getClass() : Person
			return false;
		Person other = (Person) obj;
		if (id != other.id)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

로직에 대한 이해를 못햇으므로, 그냥 결과만 짓자면.

📝 내가 만든 객체를 equals로 비교할 때가 있으면, 반드시 hashCode()와 equals() 메서드를 오버라이드 하여 사용한다.

📝 Set에서는 hashCode() equals()를 사용하여 중복을 제거한다.

시간이 흐르다 보면 왜 hashCode()와 equals() 가 저렇게 알고리즘이 짜여질수밖에 없는지 이해가 가는 날이 올것이다.

 

💡 전체코드

import java.util.HashSet;
import java.util.Set;

public class T08_equals_hashCodeTest {
	public static void main(String[] args) {
		
//		해시코드 테스트
//		Person 객체 생성
//		메모리는 다르고 필드값만 같음(p1,p2)
		Person p1 = new Person(1, "홍길동");
		Person p2 = new Person(1, "홍길동");
		Person p3 = new Person(1, "이순신");
		
//		비교
//		오버라이드 한 equals가 있을 때 true가 나옴
//		오버라이드 한 객체가 없을 때, Object 의 equals를 사용한 경우 false가 나옴 
		System.out.println("p1.equals(p2) : " + p1.equals(p2));
//		identity 비교 
		System.out.println("p1 == p1 : " + (p1==p1));
		System.out.println("p1 == p2 : " + (p1==p2));
		
//		중복제거가 잘 되는지 set에 넣어봄
		Set<Person> set = new HashSet<Person>();
//		set은 add하는 시점에 중복체크를 하기때문에 p1과 p2가 같다면 들어가지 않을것이다.
		set.add(p1);
		set.add(p2);
		
		System.out.println("p1, p2 등록 후 데이터");
		for(Person p : set) {
			System.out.println(p.getId() + " : " + p.getName());
		}
		
		System.out.println("add(p3) 성공여부 : " + set.add(p3));
		
		System.out.println("add(p3) 후 데이터");
		for(Person p : set) {
			System.out.println(p.getId() + " : " + p.getName());
		}
		
		System.out.println("remove(p2) 성공여부 : " + set.remove(p2));
		
		System.out.println("remove(p2) 후 데이터");
		for(Person p : set) {
			System.out.println(p.getId() + " : " + p.getName());
		}
		
		
		
	}
}




//우리만의 객체를 만들기위해 클래스화 함.
class Person{
	private int id;
	private String name;
	public Person(int id, String name) {
		super();
		this.id = id;
		this.name = name;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	
	
//	이클립스에서 자동 완성 기능을 활용하여 만듬(hashCode, equals)
//	오버라이드 된 이유는 Object에 기본적으로 제공하는 기능이라서 오버라이드 할 수 있는것임.
//	오버라이드 한 이유는 Object에서 제공하는 기능을 사용하지 않고 새로운 형태로 사용을 하겠다. 라는 의미임.
//	Object는 두 객체가 다르면 false
	@Override
	public int hashCode() {
//		이 알고리즘을 해도 잘 동작할거야.
		final int prime = 31;//소수 : 1과 자기자신으로만 나눌 수 있는 것.
		int result = 1;
		result = prime * result + id;//객체의 id 값
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	
//	Object에 정의된 equals를 사용하는 경우 생성한 객체에 대해 equals를 사용하면 값이 같은지 비교할 수 없다.
//	따라서 오버라이드 해서 재정의 해야 한다.
	@Override
	public boolean equals(Object obj) {
		if (this == obj)//this : p1 = p1넣은 경우 (this.p1.equals(object))
			return true;
		if (obj == null)//p1.equals(null)
			return false;
//		getClass() : Object가 속한 클래스정보를 가져옴
//		Object를 만들라면 Class가 존재해야된다. 클래스 없이 Object를 만들 수 없다.
		if (getClass() != obj.getClass())//getClass() : Person , obj.getClass() : Person
			return false;
		Person other = (Person) obj;
		if (id != other.id)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	

	
}

 

 

 

 

참고 블로그

 

'BackEnd > Java' 카테고리의 다른 글

JDK 버전 별 컴파일러 지원기능 정리  (0) 2021.03.05
Set & TreeSet  (0) 2021.03.05
MVC 패턴  (0) 2021.03.04
Maven - Oracle 설정 (pom.xml)  (0) 2021.03.04
Comparable, Comparator 차이  (0) 2021.03.04