본문에서 소개할 메소드를 설명하기 전에 자바의 최상위 클래스인 Object 클래스에 대해 알아보자.
package c.inheritance;
public class InheritanceObject {
public static void main(String[] args) throws Exeption{
InheritanceObject object = new InheritanceObject();
System.out.println(object.toString());
}
}
위 코드에서 inheritanceObject 클래스가
toString()이라는 메소드를 호출할 수 있는 이유는 무엇일까?
자바에서는 기본적으로 아무런 상속을 받지 않으면 java.lang.Object 클래스를 확장한다.
따라서 inheritanceObject 클래스는 main() 메소드 외에 선언되어 있는 메소드가 없어도
java.lang.Object 클래스를 상속받고 있으므로 toString()이라는 메소드를 사용할 수 있다.
Object 클래스에서 제공하는 메소드의 종류는 객체를 처리하기 위한 메소드와 쓰레드를 처리하기 위한 메소드로 나뉜다.
■ 객체를 처리하기 위한 메소드
메소드 | 설명 |
protected Object clone() | 객체의 복사본을 만들어 리턴한다. |
public boolean equals(Object obj) | 현재 객체와 매개 변수로 넘겨 받은 객체가 같은지 확인, 같으면 true, 다르면 false 리턴한다. |
protected void finalize() | 현재 객체가 더이상 쓸모 없어졌을 때 가비지 컬렉터(garbage collector)에 의해 이 메소드가 호출된다. |
public Class<?> getClass() | 현재 객체의 Class 클래스의 객체를 리턴한다. |
public int hashCode() | 객체에 대한 해시코드(hash code) 값을 리턴한다. 해시코드: "16진수로 제공되는 객체의 메모리 주소" |
public String toString() | 객체를 문자열로 표현하는 값을 리턴 |
■ 쓰레드를 처리하기 위한 메소드
메소드 | 설명 |
public void notify() | 이 객체의 모니터에 대기하고 있는 단일 쓰레드를 깨운다. |
public void notifyAll() | 이 객체의 모니터에 대기하고 있는 모든 쓰레드를 깨운다. |
public void wait() | 다른 쓰레드가 현재 객체에 대한 notify() 메소드나 notifyAll() 메소드를 호출할 때까지 현재 쓰레드가 대기하고 있도록 한다. |
public void wait(long timeout) | wait() 메소드와 동일한 기능 제공하며, 매개 변수에 지정한 시간만큼만 대기한다. |
public void wait(long timeout, int nanos) | wait() 메소드와 동일한 기능을 제공하지만, wait(timeout)에서 밀리초 단위의 대기 시간을 기다린다면, 이 메소드는 보다 자세한 시간만큼 대기한다. |
== 연산자와 equals() 메소드의 차이
== 연산자
기본형 타입(primitive type)에 대해서는 값을 비교,
참조형 타입(reference type)에 대해서는 주소값을 비교한다.
아래는 == 연산자를 통해 참조형 타입의 세 데이터를 비교하는 코드이다.
이때 본문의 == 연산자는 객체의 주소값을 비교하는 것에 유의하자.
package javaStudyEquals;
// .. 생략
// 비교 연산자를 사용
public void Method() {
MemberDTO m1 = new MemberDTO("Jiyeon", "", "");
MemberDTO m2= new MemberDTO("Jiyeon", "", "");
MemberDTO m3 = m2;
if(m1 == m2) {
System.out.println("Result: m1 == m2");
System.out.println("m1의 hashCode : " + System.identityHashCode(m1));
System.out.println("m2의 hashCode : " + System.identityHashCode(m2));
}
else {
System.out.println("Result: m1 != m2");
System.out.println("m1의 hashCode : " + System.identityHashCode(m1));
System.out.println("m2의 hashCode : " + System.identityHashCode(m2));
}
System.out.println("---------------------------------------------------------");
if(m2 == m3) {
System.out.println("Result: m2 == m3");
System.out.println("m2의 hashCode : " + System.identityHashCode(m2));
System.out.println("m3의 hashCode : " + System.identityHashCode(m3));
}
else {
System.out.println("Result: m2 != m3");
System.out.println("m2의 hashCode : " + System.identityHashCode(m2));
System.out.println("m3의 hashCode : " + System.identityHashCode(m3));
}
}
Method에서 참조하는 MemberDTO 클래스는 name, phone, email이라는 세 개의 속성을 갖고 있다.
MemberDTO.class
package javaStudyEquals;
public class MemberDTO{
String name;
String phone;
String email;
// '같은 이름'을 가진 메소드 선언
public MemberDTO(String name, String phone, String email) {
this.name = name;
this.phone = phone;
this.email = email;
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((email == null) ? 0 : name.hashCode());
result = prime * result + ((email == null) ? 0 : phone.hashCode());
result = prime * result + ((email == null) ? 0 : email.hashCode());
return result;
}
public boolean equals(Object obj) {
if (this == obj) return true; // 주소가 같으면 true 리턴
if(this == null) return false; // obj가 null 이면 fasle 리턴
if(getClass() != obj.getClass()) return false; // 클래스 종류가 다르면 false 리턴
MemberDTO other = (MemberDTO) obj; // 같은 클래스이면 형 변환 실행
// 각 인스턴스 변수가 같은지 비교하는 작업 수행
// name
if(name == null) { // name이 null 일 때
if(other.name != null) return false; // name이 null이 아니면 false 리턴
}else if (!name.contentEquals(other.name)) return false; // 두 개의 name 값이 다르면 flase 리턴
// phone
if(phone == null ) { // phone이 null 일 때
if(other.phone!=null) return false; // phonel이 null이 아니면 false 리턴
}else if(!phone.contentEquals(other.phone)) return false; // 두 개의 phone 값이 다르면 false 리턴
// email
if(email == null) { // eamil이 null 일 때
if(other.email!=null) return false; // email이 null이 아니면 false 리턴
}else if(!email.contentEquals(other.email)) return false; // 두 개의 email 값이 다르면 false 리턴
// 모든 검사를 통과하면 두 객체는 같은 값으로 의미하여 true 반환
return true;
}
}
선언된 각각의 String 객체의 비교를 통해 조금 더 자세히 살펴보자.
MemberDTO m1 = new MemberDTO("Jiyeon", "", "");
MemberDTO m2= new MemberDTO("Jiyeon", "", "");
MemberDTO m3 = m2;
이를 논리적인 구조로 표현하면 다음과 같다.
m1, m2 두 객체는 MemberDTO에 생성된 내용은 같으나, 두 해시코드(주소) 값이 다르기 때문에 m1 != m2 임을 알 수 있다. 1
m2, m3 두 객체는 같은 해시코드(주소) 값을 가지고 있으므로 m2 = m3 임을 알 수 있다.
따라서 출력 결과는 다음과 같다.
결론
"== 연산자는 주소 안의 데이터를 비교하는 것이 아니라
참조형 타입(reference type)에 대한 주소값을 비교한다."
equals() 메소드
자바의 최상위 클래스인 Object 클래스에 포함된 메소드로서 모든 하위 클래스에 재정의하여 사용할 수 있으며,
객체가 가진 주소 안의 데이터를 비교한다.
아래는 equals() 메소드를 통해 참조형 타입의 세 데이터를 비교하는 코드이다.
package javaStudyEquals;
// .. 생략
// equals() 메소드 사용
public void equalMethod() {
MemberDTO obj1 = new MemberDTO("Jiyeon","","");
MemberDTO obj2 = new MemberDTO("Jiyeon", "","");
MemberDTO obj3 = obj2;
if(obj1.equals(obj2)) {
System.out.println("Result: obj1 and obj2 is same");
System.out.println("obj1의 hashCode : " + System.identityHashCode(obj1));
System.out.println("obj2의 hashCode : " + System.identityHashCode(obj2));
}
else {
System.out.println("Result: obj1 and obj2 is different");
System.out.println("obj1의 hashCode : " + System.identityHashCode(obj1));
System.out.println("obj2의 hashCode : " + System.identityHashCode(obj2));
}
System.out.println("---------------------------------------------------------");
if(obj2.equals(obj3)) {
System.out.println("Result: obj2 and obj3 is same");
System.out.println("obj2의 hashCode : " + System.identityHashCode(obj2));
System.out.println("obj3의 hashCode : " + System.identityHashCode(obj3));
}
else {
System.out.println("Result: obj2 and obj3 is different");
System.out.println("obj2의 hashCode : " + System.identityHashCode(obj2));
System.out.println("obj3의 hashCode : " + System.identityHashCode(obj3));
}
}
equalMethod에서 참조하는 MemberDTO 클래스는 name, phone, email이라는 세 개의 속성을 갖고 있다.
MemberDTO 클래스는 == 연산자 설명시 추가하였으므로 참조하면 된다.
선언된 각각의 String 객체의 비교를 통해 조금 더 자세히 살펴보자.
MemberDTO obj1 = new MemberDTO("Jiyeon","","");
MemberDTO obj2 = new MemberDTO("Jiyeon", "","");
MemberDTO obj3 = obj2;
이 또한 == 연산자에서 생성된 객체와 같은 방식으로 해시코드 값이 설정됨을 알 수 있다.
즉 obj1, obj2 두 객체는 MemberDTO에 생성된 내용은 같으나, 두 해시코드(주소)값이 다르다.
obj1, obj3 두 객체는 같은 해시코드(주소) 값을 가지고 있다.
equals() 메소드는 객체의 주소에 상관 없이 주소 안에 들어 있는 데이터를 비교한다.
따라서 obj1, obj2, obj3는 모두 같은 데이터를 가지고 있기 때문에 모두 같다고 볼 수 있다.
따라서 출력 결과는 다음과 같다.
결론
"equals() 메소드는 객체의 주소값을 비교하는 것이 아니라,
주소 안에 들어 있는 데이터를 비교한다."
equals() 메소드와 hashCode()의 관계
앞에서 설명한 바와 같이 equals() 메소드는 Object 클래스의 hashCode() 메소드의 리턴 값을 비교한다.
따라서 우리는 올바른 비교를 위해 equlas()메소드를 오버라이딩 시켜야 한다.
MemberDTO.class
package javaStudyEquals;
public class MemberDTO{
String name;
String phone;
String email;
// '같은 이름'을 가진 메소드 선언
public MemberDTO(String name, String phone, String email) {
this.name = name;
this.phone = phone;
this.email = email;
}
// equlas()메소드 오버라이딩 *******************
public boolean equals(Object obj) {
if (this == obj) return true; // 주소가 같으면 true 리턴
if(this == null) return false; // obj가 null 이면 fasle 리턴
if(getClass() != obj.getClass()) return false; // 클래스 종류가 다르면 false 리턴
MemberDTO other = (MemberDTO) obj; // 같은 클래스이면 형 변환 실행
// 각 인스턴스 변수가 같은지 비교하는 작업 수행
// name
if(name == null) { // name이 null 일 때
if(other.name != null) return false; // name이 null이 아니면 false 리턴
}else if (!name.contentEquals(other.name)) return false; // 두 개의 name 값이 다르면 flase 리턴
// phone
if(phone == null ) { // phone이 null 일 때
if(other.phone!=null) return false; // phonel이 null이 아니면 false 리턴
}else if(!phone.contentEquals(other.phone)) return false; // 두 개의 phone 값이 다르면 false 리턴
// email
if(email == null) { // eamil이 null 일 때
if(other.email!=null) return false; // email이 null이 아니면 false 리턴
}else if(!email.contentEquals(other.email)) return false; // 두 개의 email 값이 다르면 false 리턴
// 모든 검사를 통과하면 두 객체는 같은 값으로 의미하여 true 반환
return true;
}
}
equals() 메소드는 객체에 들어있는 데이터가 같은지 다른지를 비교할 수 있는 동등성을 비교할 수 있다.
동등성: 서로 다른 객체이지만 같은 값을 가지는 것
Equals.class
package javaStudyEquals;
public class Equals {
public static void main(String arg[]) {
Equals thisObject = new Equals();
thisObject.equalMethod();
}
// equals() 메소드 사용
public void equalMethod() {
MemberDTO obj1 = new MemberDTO("Jiyeon","1234","jn@naver.com");
MemberDTO obj2 = new MemberDTO("Jiyeon", "1234","jn@naver.com");
System.out.println("obj1.equals(obj2) = " + obj1.equals(obj2));
System.out.println("obj1.hashCode() = " + obj1.hashCode());
System.out.println("obj2.hashCode() = " + obj2.hashCode());
}
}
위의 출력 결과는 다음과 같다.
hashCode() 메소드는16진수로 제공되는 객체의 메모리 주소 값을 나타낸다.
때문에 객체에 들어있는 데이터는 같으나 객체의 고유값이 다르기 때문에 완전히 같은 객체라고 보기 어렵다.
그래서 데이터가 동일한 객체는 동일한 메모리 주소(해시코드)를 갖도록 해야 한다.
때문에 우리가 만약 equals() 메소드를 오버라이딩 한다면, hashCode() 메소드도 오버라이딩 해야 한다.
MemberDTO.class
package javaStudyEquals;
public class MemberDTO{
String name;
String phone;
String email;
// '같은 이름'을 가진 메소드 선언
public MemberDTO(String name, String phone, String email) {
this.name = name;
this.phone = phone;
this.email = email;
}
// + hashCode()메소드 오버라이딩 *******************
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((email == null) ? 0 : name.hashCode());
result = prime * result + ((email == null) ? 0 : phone.hashCode());
result = prime * result + ((email == null) ? 0 : email.hashCode());
return result;
}
// equlas()메소드 오버라이딩 *******************
public boolean equals(Object obj) {
if (this == obj) return true; // 주소가 같으면 true 리턴
if(this == null) return false; // obj가 null 이면 fasle 리턴
if(getClass() != obj.getClass()) return false; // 클래스 종류가 다르면 false 리턴
MemberDTO other = (MemberDTO) obj; // 같은 클래스이면 형 변환 실행
// 각 인스턴스 변수가 같은지 비교하는 작업 수행
// name
if(name == null) { // name이 null 일 때
if(other.name != null) return false; // name이 null이 아니면 false 리턴
}else if (!name.contentEquals(other.name)) return false; // 두 개의 name 값이 다르면 flase 리턴
// phone
if(phone == null ) { // phone이 null 일 때
if(other.phone!=null) return false; // phonel이 null이 아니면 false 리턴
}else if(!phone.contentEquals(other.phone)) return false; // 두 개의 phone 값이 다르면 false 리턴
// email
if(email == null) { // eamil이 null 일 때
if(other.email!=null) return false; // email이 null이 아니면 false 리턴
}else if(!email.contentEquals(other.email)) return false; // 두 개의 email 값이 다르면 false 리턴
// 모든 검사를 통과하면 두 객체는 같은 값으로 의미하여 true 반환
return true;
}
}
MemberDTO 클래스에 hashCode() 메소드를 오버라이딩한 후 출력 결과는 다음과 같다.
비로소 두 객체가 동일한 해시코드 값을 갖는 것을 볼 수 있다.
결론적으로 우리는 equals와 hasCode의 관계를 다음과 같이 정의할 수 있다.
- 어떤 두 개의 객체에 대하여 equals() 메소드를 사용하여 비교한 결과가 true일 경우, 두 객체의 hashCode() 메소드를 호출하면 동일한 int 값을 리턴해야 한다.
- 두 객체를 equals() 메소드를 사용하여 비교한 결과가 false를 리턴했다고 해서, hashCode() 메소드를 호출한 int 값이 무조건 달라야 할 필요는 없다. 하지만, 이 경우에 서로 다른 int 값을 제공하면 hashtable의 성능을 향상시키는 데 도움이 된다.
이러한 제약들로 인해 우리가 직접 equals()메소드나 hashCode() 메소드를 작성하는 것은 별로 권장하지 않는다.
요즘에 나오는 각종 개발 툴에서는 이 두 개의 메소드를 자동으로 생성해주는 기능을 제공하고 있으므로,
그 기능을 사용할 것을 권장한다.
- "16진수로 제공되는 객체의 메모리 주소" [본문으로]
'Study' 카테고리의 다른 글
[JAVA] 동기, 비동기와 멀티스레딩 (0) | 2022.06.17 |
---|---|
[JAVA] Process(프로세스)와 Thread(스레드) (0) | 2022.06.01 |
[JAVA] JVM의 Garbage Collector (0) | 2022.05.17 |
[JAVA] 변수의 종류 (0) | 2022.04.12 |
THREE Paradigms (0) | 2022.03.27 |