자바의 Object Class

Object 클래스는 모든 클래스의 부모 클래스이다.

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

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() 메소드와 동일한 기능을 제공하며, 매개 변수에 지정한 시간만큼만 대기한다. 즉, 매개 변수 시간을 넘어 섰을 때에는 현재 쓰레드는 다시 깨어난다. 여기서의 시간은 밀리초로 1/1000초 단위다. 만약 1초간 기다리게 할 경우에는 1000을 매개 변수로 넘겨주면 된다.
public void wait(long timeout, int nanos) wait() 메소드와 동일한 기능을 제공한다. 하지만, wait(timeout)에서 밀리초 단위의 대기 시간을 기다린다면, 이 메소드는 보다 자세한 밀리초 + 나노초(1/1,000,000,000초) 만큼만 대기한다. 뒤에 있는 나노초의 값은 0~999,999 사이의 값만 지정할 수 있다.

2. toString() 메소드

toString() 메소드는 Object 클래스의 메소드 중에서 가장 많이 사용된다. 해당 클래스가 어떤 객체인지를 쉽게 나타낼 수 있는 중요한 메소드이다. 이 메소드가 자동으로 호출되는 경우는 다음과 같다.

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

예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package objectpractice;
 
public class ToString {
    public static void main(String[] args){
        ToString thisObject = new ToString();
        thisObject.toStringMethod(thisObject);
    }
    public void toStringMethod(Object obj){
        System.out.println(obj);
        System.out.println(obj.toString());
        System.out.println("plus " + obj);
    }
}
 
cs

실행 결과

객체를 그냥 출력하는 것과 객체의 toString() 메소드를 호출하는 것은 동일한 것을 볼 수 있다. 가장 마지막에 더하기 연산을 한 결과를 보자. "plus "뒤에 toString() 한 것과 동일한 결과가 출력되었다. 다시 말하면, String을 제외한 참조 자료형에 더하기 연산을 수행하면 자동으로 toString() 메소드가 호출되어 객체의 위치에는 String 값이 놓이게 된다.

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

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

Object 클래스에 있는 getClass()의 결과에 getName() 메소드를 부르면 현재 클래스의 패키지 이름과 클래스 이름이 나온다. 그 다음에는 at(@)가 붙는다. 이는 앞의 결과와 뒤의 결과를 구분하기 위한 구분자이다. 그리고 마지막 부분에는 객체의 해시코드 값을 출력한다. hashCode() 메소드에서는 int 타입의 값을 리턴해준다. 그 값을 Interger라는 클래스에서 제공하는 toHexString()이라는 메소드를 활용하여 16진수로 변환하는 작업이 수행된다.

그리고 클래스를 만들 때 toString() 메소드는 그냥 사용하기만 하면 되는 것이 아니라 Overriding을 통해 직접 구현해야 한다. 모든 클래스의 toString()을 Overriding을 할 필요는 없다. 하지만 DTO를 사용할 때에는 이 toString() 메소드를 Overriding해 놓는 것이 좋다.

예제 코드

public class MemberDTO{
    public String name;
    public String phone;
    public String email;
    
    public String toString(){
        return "Name="+name+" Phone="+phone+" email="+email;
    }
}

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

MemberDTO dto = new MemberDTO("Sangmin", "010XXXXYYYY", "[email protected]");
System.out.println(dto);

3. Eqauls() 메소드

두 개의 값이 같은지 다른지를 비교하는 연산자는 ==와 !=이다. ==는 같은지를 비교하고, !=는 다른지를 비교하는 연산자이다. 두 연산자의 결과는 모두 true나 false의 booelan 타입의 값이다. 그런데, 이 연산자들은 기본 자료형에서만 사용할 수 있다. 즉, 참조 자료형에서는 사용할 수 없다. 더 정확하게 말하면 사용해도 되지만, "값"을 비교하는 것이 아니라 "주소값"을 비교한다.

따라서 참조 자료형은 equals()라는 메소드를 사용하여 두 객체를 비교해야 한다. 그리고 Object 클래스에 선언되어 있는 equals() 메소드를 Overriding해 놓아야지만 제대로 된 비교가 가능하다. 만약 equals() 메소드를 Overriding하지 않으면 equals() 메소드에서는 hashCode() 값을 비교한다. hashCode() 값은 해당 객체의 주소 값을 리턴한다. 따라서, 클래스의 인스턴스 변수값들이 같다고 하더라도, 서로 다른 생성자로 객체를 생성했으면 해시 코드가 다르니 두 객체는 다르다는 결과가 나온다.

예제 코드 : MemberDTO 클래스에 equals() 메소드를 Overriding

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
32
33
34
35
36
37
package objectpractice;
 
public class MemberDTO {
    public String name;
    public String phone;
    public String email;
    public MemberDTO(String name){
        this.name = name;
    }
    
    public boolean equals(Object obj){
        
        if (this == obj) return true;
        if (obj == nullreturn false;
        
        if (getClass() != obj.getClass()) return false;
 
        MemberDTO other = (MemberDTO) obj;
        
        if(name == null){
            if(other.name != nullreturn false;
        }else if (!name.equals(other.name)){
            return false;
        }
        
        if(email == null){
            if(other.email != nullreturn false;
        }else if (!name.equals(other.name)) return false;
        
        if(phone == null){
            if(other.phone != nullreturn false;
        }else if (!phone.equals(other.phone)) return false;
        
        return true;
    }
}
 
cs

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

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

그런데, 한 가지 유념해야 하는 것이 있다. equals() Overriding할 때에는 hashCode() 메소드도 같이 Overriding해야만 한다는 것이다. 왜냐하면, equals() 메소드를 Overriding해서 객체가 서로 같다고 이야기할 수 있지만, 그 값이 같다고 해서 그 객체의 주소 값이 같지는 않기 때문이다. 다시 말하면, equals() 메소드의 결과가 true인데도 불구하고 hashCode()의 값은 다르게 된다. 따라서, 같은 hashCode() 메소드 결과를 갖도록 하려면 hashCode() 메소드도 Object 클래스에서 제공하는 그대로 사용하면 안 된다.

예제 코드 : 자동 생성된 MemberDTO의 hashCode()

package objectpractice;

public class MemberDTO {
    public String name;
    public String phone;
    public String email;
    public MemberDTO(String name){
        this.name = name;
    }

    public boolean equals(Object obj){

        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 (!name.equals(other.name)) 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 = 1;
        int result = 1;
        result = prime * result + ((email == null) ? 0 : email.hashCode());
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + ((phone == null) ? 0 : phone.hashCode());
        return result;
    }
}

4. hashCode() 메소드

hashCode() 메소드는 기본적으로 객체의 메모리 주소를 16진수로 리턴한다. 만약 어떤 두 개의 객체가 서로 동일하다면, hashCode() 값은 무조건 동일해야만 한다. 따라서, equals() 메소드를 override하면, hashCode() 메소드도 override해서 동일한 결과가 나오도록 만들어야 한다.

자바 API 문서에서는 이 메소드를 Overriding할 때에는 다음과 같은 조건을 따라야 한다고 명시하고 있다.

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

참고

  • 자바의 신

좋은 웹페이지 즐겨찾기