JAVA/HIGH JAVA

[JAVA] Equals HashCode

아잠만_ 2024. 4. 24. 12:03
  • quals() 메서드 => 두 객체의 내용이 같은지를 비교하는 메서드 (동등성)
  • hashCode() 메서드 => 두 객체가 같은 객체인지를 비교하는 메서드 (동일성)

  • HashSet, HashMap, Hashtable과 같이 Hash로 시작하는 컬렉션 객체들은
    객체의 의미상의 동일성 비교를 위해  hashCode()메서드를 호출하여 비교한다.
    그러므로 객체가 같은지 여부를 결정하려면 equals()메서드와 hashCode()메서드를 같이 재정의 해야한다.
    hashCode() > Objects.hash(변수1, 변수2...)

  • hashCode()메서드에서 사용하는 '해싱 알고리즘'은 서로 다른 객체들에 대해 같은
    hashCode값을 만들어 낼 수 있다.

객체 ==, equals 로 비교하기

public class EqualsHashcodeTest {
	public static void main(String[] args) {
		Person p1 = new Person();	// new 생성자도 생성
		
		p1.setNum(1);
		p1.setName("홍길동");
		
		Person p2 = new Person();
//		p2.setNum(2);
//		p2.setName("이순신");
		p2.setNum(1);
		p2.setName("홍길동");
		
		Person p3 = p1;
		
		System.out.println(p1 == p2);	// false (주소값이 다름)
		System.out.println(p1 == p3);	// true
		System.out.println("-------------------------");
		System.out.println(p1.equals(p2));	// false 
		System.out.println(p1.equals(p3));	// true
	}
}

class Person{	// extends Object
	private int num;
	private String name;
	
	// 클래스이름과 같은 메소드 생성자 메서드
//	public Person(int num, String name) {	// 만들지 않으면 자동으로 생성
//		super();
//		this.num = num;
//		this.name = name;
//	}
	
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		this.num = num;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

객체에 저장되어있는 값이 같지만 new로 새로운 객체로 생성할 경우 == 로 비교하였을 때
==는 주소값을 비교하기 때문에 주소값이 다르므로 false로 표시된다

 

( .equals는 Person클래스 내에 없음에도 실행할 수 있는 이유는 extends는 없지만
조상 객체인 Object객체를 상속받았기 때문에 사용할 수 있다)

다만 equals로 표현할 경우에도 값이 같음에도 true가 아닌 false의 결과가 나오는데

상속받고있는 Object에서 equals를 주소값을 비교하고 있다는 것을 할 수 있다.
실질적으로 p1과 p2는 다른 주소값을 가지고 있기 때문에 같은 값을 가지고 있음에도 다르다고 나오는 것이다.

public boolean equals(Object obj) {	
    return (this == obj); 	// 참조값 비교
}

String도 주소값만 비교했을 때 다르지만 equals를 사용하면 true값이 나온다
>> 그 이유는 Object 상속 부모의 내용을 변경하기 위해선 메소드를 재정의 (Override)가 되어있기 때문이다
그러므로 Person 내에서도 equals에 대한 재정의가 필요하다

< JVM의 실행 메모리구조 >

Method영역 Call Stack 영역 Heap영역
클래스정보
static 정보 (main)



Person 정보



















main 메소드 이름과 같은 stack frame생성
(main 메소드가 사용하는 영역)
지역변수나 매개변수 생성 args


main stack frame 안에 p1 변수 생성
p1의주소값을 저장 ( 100번지 )
p2의 주소값을 저장 ( 200번지 )
p3는 p1의 주소값 참조 (100번지)


SetNum 프레임 (끝나면 삭제)
매개 변수 num=1
this변수 생성 주소값(100번지)
> heap영역에 1저장됨
(p2생성시에도 생성되었다가
200번지에 매개변수 값을
저장후 삭제)

SetName 프레임
 (끝나면 삭제)
매개 변수 name="홍길동"
this변수 생성 주소값(100번지)
> heap영역에 "홍길동"저장됨
(p2생성시에도 생성되었다가
200번지에 매개변수 값을
저장후 삭제)

객체 생성












(100번지) Instance 변수
p 1의 각각의 변수들 값을 초기화 저장
주소값을 생성  +메소드
(이후 SetNum과 SetName에서
this의 주솟값을 참조하여 값이 저장)



(200번지) Instance 변수
p2의 각각의 변수들 값을 초기화 저장
주소값을 생성  +메소드
(이후 SetNum과 SetName에서
this의 주솟값을 참조하여 값이 저장)



this this()
자기 자신을 가져올 때 생성자에서 다른 생성자를 호출할 때 사용

equals 재정의

import java.util.HashSet;

public class EqualsHashcodeTest {
	public static void main(String[] args) {
		Person p1 = new Person();	// new 생성자도 생성
		
		p1.setNum(1);
		p1.setName("홍길동");
		
		Person p2 = new Person();
//		p2.setNum(2);
//		p2.setName("이순신");
		p2.setNum(1);
		p2.setName("홍길동");
		
		Person p3 = p1;
		
		System.out.println(p1 == p2);	// false (주소값이 다름)
		System.out.println(p1 == p3);	// true
		System.out.println("-------------------------");
		System.out.println(p1.equals(p2));	// true
		System.out.println(p1.equals(p3));	// true
		System.out.println("-------------------------");

		// Hashcode가 같으면 equals로 비교 2번
		// 값이 같으면 같은 것으로 취급하게 하고싶다면 Hashcode를 같게하면된다
		HashSet<Person> testSet = new HashSet<Person>();
		testSet.add(p1);
		testSet.add(p2);	// 주소가 다르면 중복취급 X
//		testSet.add(p3);	// 중복된 값 추가 X 
		
		System.out.println("set의 개수 : "+testSet.size()); // set의 개수 : 2
		System.out.println("p1 : "+p1.hashCode());	// p1 : 366712642
		System.out.println("p2 : "+p2.hashCode());	// p2 : 1829164700
		System.out.println("p3 : "+p3.hashCode());	// p3 : 366712642

	}
}

class Person{
	private int num;
	private String name;
	
	// 클래스이름과 같은 메소드 생성자 메서드
//	public Person(int num, String name) {	// 만들지 않으면 자동으로 생성
//		super();
//		this.num = num;
//		this.name = name;
//	}
	
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		this.num = num;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	// equals 재정의
	@Override
	public boolean equals(Object obj) {
		if(this==obj) return true;	//나의 참조값과 obj의 참조값이 같을 때 ( 내용비교 필요X )
		
		if(obj==null) return false;
		
		// 같은 종류의 클래스인지 검사
		if(this.getClass()!=obj.getClass()) return false;
		
		// 매개변수의 객체를 현재 객체 유형으로 형변환한다.
		Person that = (Person) obj; // 같은 종류 클래스 이므로 형변환 후 저장
		
		// 각 값들이 같은지 검사 종류를 정해서 무엇이 같으면 같을지를 정하기
		// 이름과 num이 같은 지 검사
		return this.getName().equals(that.getName()) && this.getNum()==that.getNum();
	}
}

equals값이 같지만 set에 저장되는 이유는

> 먼저 HashSet에 저장되기전 equals보다 Hashcode가 같은지 파악하기 때문이다.

그래서 중복된 값이 나오지 않게 하려면 hashcode값이 같도록 해야한다

그래서 Person 클래스 내에서 hashCode를 재정의 해줘야한다

hashCode재정의

import java.util.HashSet;
import java.util.Objects;

public class EqualsHashcodeTest {
	public static void main(String[] args) {
		Person p1 = new Person();	// new 생성자도 생성
		
		p1.setNum(1);
		p1.setName("홍길동");
		
		Person p2 = new Person();
//		p2.setNum(2);
//		p2.setName("이순신");
		p2.setNum(1);
		p2.setName("홍길동");
		
		Person p3 = p1;
		
		System.out.println(p1 == p2);	// false (주소값이 다름)
		System.out.println(p1 == p3);	// true
		System.out.println("-------------------------");
		System.out.println(p1.equals(p2));	// true
		System.out.println(p1.equals(p3));	// true
		System.out.println("-------------------------");

		// Hashcode가 같으면 equals로 비교 2번
		// 값이 같으면 같은 것으로 취급하게 하고싶다면 Hashcode를 같게하면된다
		HashSet<Person> testSet = new HashSet<Person>();
		testSet.add(p1);
		testSet.add(p2);	// 주소가 다르면 중복취급 X
//		testSet.add(p3);	// 중복된 값 추가 X 
		
		System.out.println("set의 개수 : "+testSet.size()); // set의 개수 : 1
		System.out.println("p1 : "+p1.hashCode());	// p1 : 54151054
		System.out.println("p2 : "+p2.hashCode());	// p2 : 54151054
		System.out.println("p3 : "+p3.hashCode());	// p3 : 54151054

	}
}

class Person{
	private int num;
	private String name;
	
	// 클래스이름과 같은 메소드 생성자 메서드
//	public Person(int num, String name) {	// 만들지 않으면 자동으로 생성
//		super();
//		this.num = num;
//		this.name = name;
//	}
	
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		this.num = num;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	// equals 재정의
	@Override
	public boolean equals(Object obj) {
		if(this==obj) return true;	//나의 참조값과 obj의 참조값이 같을 때 ( 내용비교 필요X )
		
		if(obj==null) return false;
		
		// 같은 종류의 클래스인지 검사
		if(this.getClass()!=obj.getClass()) return false;
		
		// 매개변수의 객체를 현재 객체 유형으로 형변환한다.
		Person that = (Person) obj; // 같은 종류 클래스 이므로 형변환 후 저장
		
		// 각 값들이 같은지 검사 종류를 정해서 무엇이 같으면 같을지를 정하기
		// 이름과 num이 같은 지 검사
		return this.getName().equals(that.getName()) && this.getNum()==that.getNum();
	}
	
	// 같은 값일 경우 hashCode()를 같게 함
	@Override
	public int hashCode() {
		return Objects.hash(num, name);	// 2개의 값을 이용해 hashcode를 생성하기 때문에 같은 값이면 같은 값이 됨 
	}
}