본문 바로가기
  • 기업가 장준영
Java_1

Java_1_10 ( Object클래스 )

by 장준영 2020. 1. 10.

Java_1_9에서는 상속에 대해서 알아봤다

오늘은 Object클래스에 대해서 정리해 본다

Java_1_9에서 상속이라는 것에 대해서 알아보았다. 그런데, 중요한 것 하나를 가르쳐 주지 않았다

바로 모든 클래스의 부모 클래스가 있다는 사실이다

//Java_1_10_src1

public class Objec{

public static void main(String[]args){

Objec object = new Objec();

System.out.println(object.toString());

}

}

위 클래스를 보면 Java_1_9에서 배운 extends라는 것도 없으니 확장한 부모 클래스도 없다

하지만, 자바 에서는 기본적으로 이렇게 아무런 상속을 받지 않으면 java.lang.Object클래스를 확장한다

이 클래스가 어떻게 Object클래스를 확장한다는 것을 알 수 있을까?

가장 쉬운 방법은 Object 클래스에 있는 메소드를 사용하는 것이다

Java_1_10_src1를 보면 main()메소드 외에는 선언되어 있는 메소드가 전혀 없다. 그런데, toString()이라는 메소드가 호출된 것을 볼 수 있다

그렇다면 내가 extends를 사용하여 부모 클래스를 상속받을 때에는 어떻게 될까?

자바는 한번에 이중 상속을 받을 수는 없지만, 여러 단계로 상속을 받을 수는 있다

부모 클래스A와 자식 클래스a 가 있다고 해보자

A클래스는 아무런 상속을 받지 않았지만, 실제로는 Object클래스의 상속을 받았다.

그러한 A클래스를 a클래스가 상속받았으므로 a클래스는 자동으로 Object클래스의 메소드들을 상속받는다

다시 말해서 a클래스는 Object클래스의 자식의 자식이다 (손자)

즉 관계는 이러하다 Object > A > a

그러면 왜 모든 클래스는 Object 클래스의 상속을 받을까?

가장 큰 이유는 Object 클래스에 있는 메소드들을 통해서 클래스의 기본적인 행동을 정의할 수 있기 때문이다

예를 들면 "사람"은 두 발로 걷고, 말을 하고, 생각을 한다

이와 마찬가지로

"클래스"라면 이 정도의 메소드는 정의되어 있어야 하고, 처리해 주어야 한다.

이것을 정의하는 작업이 필요하기 때문에 Object클래스를 상속받았다고 생각하면 된다

Object 클래스에서 제공하는 메소드들의 종류

Object클래스에 선언되어 있는 메소드는 객체를 처리하기 위한 메소드쓰레드를 위한 메소드로 나뉜다

쓰레드를 지금 정리하면 처음 자바를 접하는 독자에겐 무리가 가니 쓰레드에 대한 것은 다음에 알아보자

객체 처리를 위한 메소드는 다음과 같다

                                  매소드                                      설명
protected Object clone() 객체의 복사본을 만들어 리턴한다
public boolean equals(Object obj)

현재 객체와 매개 뱐수로 넘겨받은 객체가 같은지 확인한다같

으면 true를, 다르면 false를 리턴한다

protected void finalize()

현재 객체가 더 이상 쓸모가 없어졌을 때 가비지 컬랙터에 의

해서 이 메소드가 호출된다

 

(평생 사용할 일 없으니 그냥 이런게 있다 라고만 알고 있자)

public Class<?> getClass() 현재 객체의 Class 클래스의 객체를 리턴한다
public int hashCode()

객체에 대한 해시 코드(Hash code)값을 리턴한다

해시 코드 라는 것은 

 

"16진수로 제공되는 객체의 메모리 주소"를 말한다

public String toString() 객체를 문자열로 표현하는 값을 리턴한다

이제 객체를 처리하기 위해 주로 사용하는 중요한 메소드 들을 알아보자

toString()

toString() 메소드는Object 클래스의 메소드 중에서 가장 많이 사용된다

해당 클래스가 어떤 객체인지를 쉽게 나타낼 수 있는 중요한 메소드다

이 메소드가 자동으로 호출되는 경우는 다음과 같다.

  • System.out.println() 메소드에 매개 변수로 들어가는 경우
  • 객체에 대하여 +(더하기) 연산을 하는 경우

//Java_1_10_src2

public class Objec{

public static void main(String[]args){

Objec object = new Objec();

object.toStringMethod(object);

}

public void toStringMethod(Object obj){

System.out.println(obj);

System.out.println(obj.toSturbg);

System.out.println(+obj);

}

}

toStringMethod() 의 가장 첫 줄에는 객체를 그대로 출력했고

두 번째 줄에서는 toString()메소드를 불렀다

세 번째 줄에서는 객체에 더하기 연산을 하였다

이를 실행해보자

결과는 셋 모두 동일하다

객체를 그냥 출력하는 것과 객체의 toString() 메소드를 호출하는 것은 동일한 것을 볼 수 있다

그리고 가장 마지막에 더하기 연산을 한 결과를 보자 toStirng()메소드를 호출하는 것과 동일한 결과가 출력되었다

다시 말하면

Stirng을 제외한 참조 자료형에 더하기 연산을 수행하면, 자동으로 toString()메소드가 호출되어 객체의 위치에는 String값이 놓이게 된다

그런데 이렇게 toStirng()으로 출력된 결과는 도대체 뭐야? 라는 의문이 생기는 독자가 분명히 있을 것이다

실제 Object클래스에 구현되어 있는 toString()메소드는 다음과 같다

getClass().getName() + '@' + Integer.toHexString(hashCode())

그런데, 도대체 이렇게 클래스 이름과 해시 코드 값을 내가 봐서 무슨 소용이 있다는 말일까?

여러분들이 클래스를 만들 떄 toStirng()메소드는 그냥 사용하기만 하면 되는 것이 아니라,

직접 구현해야만 한다, 즉 Overriding을 적용해야만 한다.

그러면 이러한 toString()메소드는 언제 Overriding해야 할까?

모든 클래스의 toString()을 Overriding할 필요는 없다. 하지만 앞에서 배운 DTO를 사용할 때에는 아무리 개발 일정이 촉박하다고 하더라도 toString()메소드를 Overriding해 놓는 것이 좋다

그래야 내용 확인이 쉽기 때문이다

앞에서 예제로 사용한 MemberDTO라는 클래스의 인스턴스 변수 선언부를 보자

//Java_1_7_src3

public class MemberDTO{

public String name;

public String phone;

public String email;

//이하 생략

}

만약 toStirng() 메소드가Overriding 되어 있지 않다면. 이 MemberDTO에 선언된 값들을 확인할 떄 어떻게 해야 할까?

아마도 다음과 같이 확인해 봐야 할 것이다

MemberDTO dto = new MemberDTO("JunnYoung", "010-xxxx-yyyy", "helloolleh2153@naver.com");

System.out.println("Name="+dto.name+"Phone="+dto.phone+"eMail="+dto.email);

이 MemberDTO를 사용해야 하는 부분이 많은데,

사용할 때마다 이와 같이 출력을 해봐야 각각의 값이 어떻게 되어 있는지를 확인할 수 있을 것이다

이럴 때 toStirng()을 Overriding해 놓으면 된다

//Java_1_10_src3

public class MemberDTO{

public String name;

public String phone;

public String email;

//중간 생략

public String toString(){

return "Name="+dto.name+"Phone="+dto.phone+"eMail="+dto.email;

}

}

이렇게 toStirng()을 Overriding해 놓으면 다음과 같이 간단하게 사용할 수 있다

MemberDTO dto = new MemberDTO("JunnYoung", "010-xxxx-yyyy", "helloolleh2153@naver.com");

System.out.println(dto);

앞서 출력한 것과 지금 출력하는 것 중 어떤 것이 더 간단한지는 두말할 나위도 없을 것이다

equals()

==는 같은지를 비교하고, !=는 다른지를 비교하는 연산자이다

두 연산자의 결과는 모두 true나 false의 boolean타입의 값이다

그런데, 이 연산자들은 기본 자료형에서만 사용할 수 있다

거꾸로 이야기하면 참조 자료형에서는 사용하면 안 된다

더 장확하게 말하면 사용해도 되지만 "값"을 비교하는 것이 아니라 "주솟값"을 비교한다

MemberDTO에서 매개 변수로 받는 이름이 같은 객체 obj1과 obj2 가 있다

MemberDTO obj1= new MemberDTO("Junn");

MemberDTO obj2= new MemberDTO("Junn");

두 객체는 자바 상에서는 다르지만, 현실 세계에서는 동일한 값을 갖고 있으면(이름, 전화번호, 이메일이 같으면)같다고 볼 수 있다

뭐 세상엔 여러 Junn이 있겠지만 그냥 동명이인이 없다고 생각하자

저 두게의 서로 이름이 같은 객체를 등호를 통해 같다고 논리 연산을 해보자 결과는 false일 것이다

왜냐하면 두 객체는 각각의 생성자를 사용하여 만들었기 때문에 주소값이 다르다

그런데, 그 안에 있는 속성값들은 name은 Junn이고 나머지 값들은 모두 null이므로 동일하다

그래서, 이와 같이 참조 자료형은 equals()라는 메소드를 사용하여 두 객체를 비교해야 한다

그리고, Object클래스에 선언되어 있는 equals()메소드를 Overriding해 놓아야지만 제대로 된 비교가 가능하다

equals()메소드를 사용해 보자

//Java_1_10_src4

public class UseEquals{

MemberDTO obj1= new MemberDTO("Junn");

MemberDTO obj2= new MemberDTO("Junn");

if(obj1.equals(obj2)){

System.out.println("obj1 and obj2 is same");

}else{

System.out.println("obj1 and obj2 is different");

}

}

이제 결과가 우리가 원하는 대로 모든 속성이 동일하니 같다고 나올까??

아쉽게도 결과는 다음과 같다

obj1 and obj2 is different

왜냐하면 equals()메소드를 사용하여 두 객체를 비교를 하긴 했지만, 비교 대상 객체인 MemberDTO 클래스에서는 아직

equals()메소드를 Overriding하지 않았기 때문에 이와 같은 결과가 나왔다

만약 여러분들이 이 메소드를 Overriding하지 않으면 equals()메소드 에서는 hashCode()값을 비교한다

정석 대로 equals()메소드를 Overriding하면 다음과 같이 작성할 수 있다

//Java_1_10_src5

public class MemberDTO{

public String name;

public String phone;

public String email;

//중간 생략

public String toString(){

return "Name="+dto.name+"Phone="+dto.phone+"eMail="+dto.email;

}

public boolean equals(Object obj){ //여기서 부터 equals()메소드를 Overriding

if(this == obj) return true;

if(obj == null) return false;

if(getClass() != obj.getClass()) return false;

MemberDTO other = (MemberDTO) obj;

if(name == null){

if(other.name != null) return false;

}else if(!name.equals(other.name)) return false;

if(email == null){

if(other.email != null) return false;

}else if(!email .equals(other.email )) return false;

if(phone== null){

if(other.phone!= null) return false;

}else if(!phone.equals(other.phone)) return false;

return true;

}

}

재대로 된 equals()메소드를 처음 본 분들은 이걸 내가 객체 비교할 때마다 만들라는 거야? 라고 생각할 수도 있다

하지만 이 메소드는 이클립스에서 자동으로 만들어주니 걱정하진 말자

그리고 혹시나 해서 이야기하지만, 여러분들이 equals()메소드를 반드시 Overriding해야 하는 것은 아니다

꼭 필요할 떄에만 Overriding하면 된다. DTO를 만들 경우에는 객체 비교를 위해서 반드시 필요하지만

그렇지 않은 메소드만 있는 기능 위주의 클래스를 만들 때에는 힘들게 equals()메소드를 Overriding할 필요는 별로 없다

본론으로 들어가

equals() 메소드를 Overriding할 때에는 "반드시" 다음 다섯 가지의 조건을 만족시켜야만 한다

  • 재귀 - null이 아닌 x라는 객체의 x.equals(x) 결과는 항상 true여야만 한다
  • 대칭 - null이 아닌 x와y라는 객체가 있을 떄 객체의 y.equals(x)가 true를 리턴했다면 x.equalse(y)도 반드시 true를 리턴 여야만 한다
  • 타동적 - null이 아닌 x,y,z라는 객체가 있을 때 객체의 x.equals(y)가 true를 리턴하고 y.equalse(z)도 true를 리턴하 면 x.equalse(z)도 반드시 true를 리턴하여야 한다
  • 일관 - null이 아닌x와 y가 있을 때 객체가 변경되지 않은 상황에서는 몇 번을 호출하더라도, x.equals(y)의 결과는 항상 true이거나 항상false여야만 한다
  • null과의 비교 - null이 아닌 x라는 객체의 x.equals(null) 결과는 항상 false여야만 한다

이 기준은 자바 API문서에 정해져 있는 것이다

hashCode()

한 가지 유념해야 하는 것이 있다

equals()메소드를 Overriding할 때에는 hashCode()메소드도 같이 Overriding해야만 한다는 것이다

왜냐하면 equals()메소드를 Overriding해서 객체가 서로 같다고 이야기할 수는 있겠지만

그 값이 같다고 해서 그 객체의 주소 값이 같지는 않기 때문이다

다시 말하면 equals()메소드의 결과가 true인데도 불구하고 hashCode() 메소드의 값은 다르게 된다

따라서, 같은 hashCode()메소드 결과를 갖도록 하려면 hashCode()메소드도 Object클래스에서 제공하는 그대로 사용하면 안 된다

Overriding한 hashCode()메소드는 다음과 같다

//Java_1_10_src6

public class MemberDTO{

public String name;

public String phone;

public String email;

//중간 생략

public String toString(){

return "Name="+dto.name+"Phone="+dto.phone+"eMail="+dto.email;

}

public boolean equals(Object obj){ //여기서 부터 equals()메소드를 Overriding

if(this == obj) return true;

if(obj == null) return false;

if(getClass() != obj.getClass()) return false;

MemberDTO other = (MemberDTO) obj;

if(name == null){

if(other.name != null) return false;

}else if(!name.equals(other.name)) return false;

if(email == null){

if(other.email != null) return false;

}else if(!email .equals(other.email )) return false;

if(phone== null){

if(other.phone!= null) return false;

}else if(!phone.equals(other.phone)) return false;

return true;

}

public int hashCode(){

final int prime = 31;

int result = 1;

result = prime * result + ((email == null) ? 0 : email.hashCode());

result = prime * result + ((email == null) ? 0 : email.hashCode());

result = prime * result + ((email == null) ? 0 : email.hashCode());

result result;

}

}

haseCode()메소드는 기본적으로 객체의 메모리 주소를 16진수로 리턴한다

만약 어떤 두 개의 객체가 서로 동일하다면 hashCode()값은 무조건 동일해야만 한다

따라서 equals()메소드를 Overriding하면 hashCode()메소드도 Overriding해서 동일한 결과가 나오도록 만들어야만 한다

hashCode() 메소드를 Overriding할 때에는 "반드시" 다음 다섯 가지의 조건을 만족시켜야만 한다

  • 자바 애플리케이션이 수행되는 동안에 어떤 객체에 대해서 이 메소드가 호출될 떄에는 항상 동일한 int값을 리턴해 주어야 한다. 하지만, 자바를 실행할 때마다 같은 값이어야 할 필요는 전혀 없다
  • 어떤 두개의 객체에 대하여equals()메소드를 사용하여 비교한 결과가 true일 경우에, 두 객체의 hashCode()메소드를 호출하면 동일한 int값을 리턴해야만 한다
  • 두 객체를 equals()메소드를 사용하여 비교한 결과 false를 리턴했다고 해서, hashCode()메소드를 호출한 int값이 무조건 달라야 할 필요는 없다. 하지만, 이 경우에 서로 다른 int값을 제공하면 hashtable의 성능을 향상시키는데 도움이 된다

요즘 나오는 개발 툴에서는 이 두개의 메소드를 자동으로 생성해주는 기능을 제공하고 있으므로, 그 기능을 사용하길 권장한다

오늘은 모든 클래스의 최상위 부모인 Object클래스에 대해 알아 보았다

이정도만 알았어도 오늘 하루는 유익한 하루이다

'Java_1' 카테고리의 다른 글

Java_1_12 ( 예외 )  (0) 2020.01.16
Java_1_11 ( 인터페이스와 추상클래스, final, enum )  (0) 2020.01.11
Java_1_9 ( 상속 )  (0) 2020.01.10
Java_1_8 ( 패키지와 접근 제어자 )  (0) 2020.01.09
Java_1_7 ( 참조 자료형 )  (0) 2020.01.08

댓글