Object Class

Object class와 그 method들에 대한 이해

java.lang 패키지에는 Object 클래스가 포함되어 있다. 그리고 이 Object 클래스는 모든 Java 클래스의 최고 상위 클래스로 모든 클래스는 Object 클래스를 상속받는다.

따라서 Java의 모든 클래스들은 Object 클래스의 모든 메소드를 바로 사용할 수 있고, 이것을 잘 익혀놓으면 다양하게 활용할 수 있다.

총 11개의 메소드로 구성되어 있고, 주로 toString(), equals(), hashCode(), clone()을 override 해서 사용한다.
메소드들을 좀더 자세히 알아보겠다.


Object Class의 11가지 method

Method설명
protected Object clone()해당 객체의 복제본을 생성하여 반환함.
boolean equals(Object obj)해당 객체와 전달받은 객체가 같은지 여부를 반환함.
protected void finalize()해당 객체를 더는 아무도 참조하지 않아 가비지 컬렉터가 객체의 리소스를 정리하기 위해 호출함.
Class<T> getClass()해당 객체의 클래스 타입을 반환함.
int hashCode()해당 객체의 해시 코드값을 반환함.
void notify()해당 객체의 대기(wait)하고 있는 하나의 스레드를 다시 실행할 때 호출함.
void notifyAll()해당 객체의 대기(wait)하고 있는 모든 스레드를 다시 실행할 때 호출함.
String toString()해당 객체의 정보를 문자열로 반환함.
void wait()해당 객체의 다른 스레드가 notify()나 notifyAll() 메소드를 실행할 때까지 현재 스레드를 일시적으로 대기(wait)시킬 때 호출함.
void wait(long timeout)해당 객체의 다른 스레드가 notify()나 notifyAll() 메소드를 실행하거나 전달받은 시간이 지날 때까지 현재 스레드를 일시적으로 대기(wait)시킬 때 호출함.
void wait(long timeout, int nanos)해당 객체의 다른 스레드가 notify()나 notifyAll() 메소드를 실행하거나 전달받은 시간이 지나거나 다른 스레드가 현재 스레드를 인터럽트 할 때까지 현재 스레드를 일시적으로 대기(wait)시킬 때 호출함.

toString()

toString() method는 해당 인스턴스의 정보를 문자열로 반환한다.
클래스 이름과 구분자 ‘@’, 그 뒤에 16진수 해시 코드가 반환된다.

toString()의 원형

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class TestMethod {...}

class Main {
    public static void main(String args[]) {
        TestMethod test01 = new TestMethod();
        TestMethod test02 = new TestMethod();
        
        System.out.println(test01.toString());
        System.out.println(test02.toString());
    }
}

실행 결과

1
2
TestMethod@2c7b84de
TestMethod@3fee733d

String

String 객체를 출력하면 String 객체가 저장하고 있는 문자열이 출력된다.
jdk의 String 클래스는 toString()을 override하고 있기 때문이다.

1
2
String test = new String("Test");
System.out.println(test);

실행 결과

1
Test

그렇다면 이 toString()을 어떤 경우에 override해서 사용할까?
예를 들어 보겠다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class Song {
    
    private String title;
    private String singer;
    
    public Song(String title, String singer) {
        this.title = title;
        this.singer = singer;
    }
    
    @Override
    public String toString() {
        return "Song{title='" + title + "', singer='" + singer + "'}";
    }
    
    public static void main(String[] args) {
        Song song = new Song("노래 제목", "가수");
        System.out.println(song);
    }
}

실행 결과

1
Song{title='노래 제목', singer='가수'}

이렇게 객체의 정보를 문자열 형태로 표현하고자 할 때,
공통적으로 출력되어야 하는 형태가 있는 경우,
toString()을 override해서 return 값을 바꿔줄 수 있다.


equals()

equals() method는 두 객체가 같은 객체인지 비교할 때 사용한다.
여기서 동등성동일성에 대해 생각해볼 필요가 있다.

주로 primitive type의 자료형이 같은 지를 비교할 때 ’==’ 연산자를 사용하는데, 이는 두 object가 같은 정보를 담고 있는지를 비교한다. 즉, 동등한지를 비교한다.

그리고 두 객체가 같은 지를 비교할 때는 equals() method를 사용하는데, 이는 두 object가 완전히 같은, 동일한 object인지 비교하는 것이다.

두 객체가 같은 주소값을 가지는 같은 객체라는 것이 동일하다고 표현된다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class User {
    
    int id;
    String name;
    
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
    
    public boolean equals(Object obj) {
        return (this == obj);
    }
    
    public static void main(String[] args) {
        User user1 = new User(1000, "최승은");
        User user2 = new User(1000, "최승은");
        
        // 두 개의 다른 객체가 같은 값을 가지고 있다.
        System.out.println(user1.equals(user2)); // 서로 다른 객체이기 때문에 false
        user1 = user2; // 두 변수가 같은 주소를 가리키게 된다.
        System.out.println(user1.equals(user2)); // 같은 주소값의 같은 객체이기 때문에 true
    }
}

실행 결과

1
2
false
true

equals() 기본 형태

1
2
3
public boolean equals(Object obj) {
    return (this == obj);
}

equals의 기본 형태는 이렇다. object가 동일한지를 비교하는 것이다.
그렇다면 equals()는 어떤 경우에 override해서 사용할까?
예를 들어 보겠다.

 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
public class User {
    
    int id;
    String name;
    
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
    
    public int getId() {
        return id;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof User) {
            return this.getId() == ((User)obj).getId();
        } else {
            return false;
        }
    }
    
    public static void main(String[] args) {
        User user1 = new User(1000, "최승은");
        User user2 = new User(1000, "최승은");
        
        System.out.println(user1.equals(user2));
    }
}

실행 결과

1
true

위의 예시처럼 두 객체가 같은 해시코드 값을 갖는지 비교하는 내용으로 override를 하여 사용한다.


hashCode()

해시코드란 JVM이 인스턴스를 생성할 때 메모리 주소를 변환해서 부여하는 코드이다.
실제 메모리 주소값과는 별개이며, 실제 메모리 주소는 System 클래스의 identityHashCode()로 확인할 수 있다.

자바에서의 동일성은 equals()의 반환값이 true, hashCode() 반환값이 동일함을 의미한다.
그래서 일반적으로 equals()와 hashCode()는 함께 override 한다.

 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
38
39
public class User {
    
    int id;
    String name;
    
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
    
    public int getId() {
        return id;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof User) {
            return this.getId() == ((User)obj).getId();
        } else {
            return false;
        }
    }
    
    @Override
    public int hashCode() {
        return getId();
    }
    
    public static void main(String[] args) {
        User user1 = new User(1000, "최승은");
        User user2 = new User(1000, "최승은");
        
        System.out.println("user1.equals(user2): " + user1.equals(user2));
        System.out.println("user1.hashCode(): " + user1.hashCode());
        System.out.println("user2.hashCode(): " + user2.hashCode());
        System.out.println("System.identityHashCode(user1): " + System.identityHashCode(user1));
        System.out.println("System.identityHashCode(user2): " + System.identityHashCode(user2));
    }
}

실행 결과

1
2
3
4
5
user1.equals(user2): true
user1.hashCode(): 1000
user2.hashCode(): 1000
System.identityHashCode(user1): 1804094807
System.identityHashCode(user2): 951007336

위의 예시는 id를 해시코드 값으로 반환하여 출력하게 된다.
이렇게 두 개의 서로 다른 메모리에 위치한 객체가 동일성을 갖기 위해 override를 하게 된다.

Wrapper class

Integer 클래스도 Object 클래스의 hashCode()를 override하고 있다.

1
2
3
4
5
6
7
Integer a = new Integer(1);
Integer b = new Integer(1);

System.out.println(a == b);
System.out.println(a.equals(b));
System.out.println("a.hashCode(): " + a.hashCode());
System.out.println("b.hashCode(): " + b.hashCode());

실행 결과

1
2
3
4
false
true
a.hashCode(): 1
b.hashCode(): 1

위 코드를 보면 a 객체와 b 객체는 같은 값을 갖지만 따로 생성된 다른 객체이다. 따라서 ‘==’ 연산자를 사용하여 비교하면 false가 나온다.

하지만 equals() 메소드를 사용하여 비교하면 true가 나온다. 그 이유는 같은 hashcode 주소값을 갖기 때문이다.

java.lang에 있는 integer 클래스를 보면 hashCode() 메소드가 override 되어있는것을 확인할 수 있다.

1
2
3
4
@Override
public int hashCode() {
    return Integer.hashCode(value);
}

clone()

clone() method는 객체를 복제할 때 사용하며, private 필드도 복제할 수 있기 때문에 정보은닉에 위배될 수 있다.

따라서 인터페이스가 명시돼있는 클래스만 clone()을 통해 객체를 복제할 수 있다.


finalize()

finalize() method는 직접 호출하는 메소드가 아니라 객체가 힙 메모리에서 해제될 때 가비지콜렉터가 호출하는 메소드이다.

이 메소드가 override 되어있으면 가비지콜렉터가 이 메소드를 호출하여 실행한다.
즉, finalize()에는 객체가 해제될 때 리소스 해제, 소켓 close 등의 필요한 것들을 구현해주면 된다.



References

오라클 api 공식 문서
Object 클래스
자바 Object 클래스 정리 - toString(), equals(), hashCode(), clone()

Choi SeungEun Tech Blog
Built with Hugo
Theme Stack designed by Jimmy