[Java] 오브젝트(Object) 란? - 특징과 사용법

2021. 6. 17. 10:58Java

java.lang 패키지의 Object 클래스를 정리해보았다.

 

java.lang 패키지의 모든 클래스와 인터페이스는 import 없이 사용 가능하다. 주요 클래스는 아래와 같다.

 


Object 클래스

- 최상위 클래스

- 컴파일 시 extends Object 가 자동으로 붙는다.

- Object 클래스에는 11개의 메서드가 존재하며, 어떠한 Class 에서도 자유롭게 사용 가능하다.

- Object 클래스의 메서드는 아래와 같다.

 


Object 클래스의 중요한 메서드를 몇 가지 알아보자.

 

1. equals() 

- equals 메서드는 원론적으로 == 연산자와 동일하게 메모리 번지를 비교한다.

- Object 클래스의 equlas() 메서드를 확인해보면 메모리 주소를 비교 코드를 볼 수 있다.

- 의문점이 있다면, 그동안 equals() 는 값을 비교하기 위해 썼는데 메모리 번지 비교라니????

- 이유는 String 클래스에서 Object 클래스의 equlas() 를 오버라이딩 하여 값을 비교하게 하였기 때문이다.

- 이는 논리적 동등을 지키기 위함이었고, 일반적으로 사용하는 방법이다.

- 논리적 동등이란? 객체의 주소가 달라도 저장 값이 같다면, 같은 객체로 보자는 개념이다.

- 하여, 사용자 정의 Class 에서는 논리적 동등을 만들기 위해 Object 클래스의 equlas() 를 오버라이딩 하여 값을 비교하게끔 만든다.

- String 클래스에서 Object 클래스의 equlas() 메서드 오버라이딩 코드를 확인해보자.

- 주소 비교를 값 비교로 재정의 한 걸 볼 수 있다.

- 앞으로 사용자 정의 Class 에서 논리적 동등을 위해 오버라이딩 해주는 걸 잊지 말자.

 

 

2. hashCode()

- 메모리 번지를 기준으로 객체를 식별할 수 있는 유일한 값을 의미한다. (10진수)

- 따라서 new 연산자로 생성된 인스턴스들은 해시 코드가 모두 다르다.

- 예제로 확인해보자.

import java.util.HashSet;

public class Example {

    public static void main(String[] args) {

        // Person 객체 3개 생성
        Person person1 = new Person("사람", 20);
        Person person2 = new Person("사람", 20);
        Person person3 = new Person("사람", 20);

        // hashSet 객체 생성
        HashSet hashSet = new HashSet();
        hashSet.add(person1);
        hashSet.add(person2);
        hashSet.add(person3);

        // hashSet 은 중복이 허용 되지 않음
        System.out.println(hashSet.size()); // "3" 출력
    }
}

class Person {

    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

- 위 코드를 보면 hashSet 에 3개의 객체가 add 된 걸 확인할 수 있다. add 된 것부터 객체의 중복이 아니란 증거이다. (hashSet 은 중복이 허용되지 않음)

- 위에서 설명한 것처럼 Object 클래스의 hashCode 는 객체의 유일한 식별 값인 메모리 주소를 return 해주기 때문이다.

 

- 하지만 위의 정의는 우리가 바라는 개념이 아니다.

- hashCode() 는 일반적으로 두 객체가 동일한 객체인지 판단해주는 메서드로 사용된다.

- 그렇기에 사용자 정의 Class 에서는 hashCode() 를 오버라이딩하여 기능을 만들어주자.

- 주의점

  : 해싱(hasing) 기법(=저장 또는 검색할 때 유용한 알고리즘)을 사용하는 Collection Framework (HashSet / HashMap / HashTable / LinkedHash)는 두 객체가 동등한 지의 판단을 내릴 때, 먼저 hashCode() 의 return 값을 보고, equlas() 의 return 값으로 최종 결정을 짓는다. 그렇기에 해싱 기법을 이용하는 hash 시리즈들을 사용할 거라면 equals() 도 오버라이딩 해주자!

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

public class Example {

    public static void main(String[] args) {

        // Person 객체 3개 생성
        Person person1 = new Person("사람", 20);
        Person person2 = new Person("사람", 20);
        Person person3 = new Person("사람", 20);

        // hashSet 객체 생성
        HashSet hashSet = new HashSet();
        hashSet.add(person1);
        hashSet.add(person2);
        hashSet.add(person3);

        // hashSet 은 중복이 허용 되지 않음
        System.out.println(hashSet.size()); // "1" 출력
        

        // toString
        System.out.println(person1);
        System.out.println(person2);
        System.out.println(person3);
    }

}

class Person {

    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int hashCode() {
        System.out.println("hashCode() 호출");
        return Objects.hash(name, age);
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println("equals() 호출");

        if (obj instanceof Person) {
            Person person = (Person) obj;   // 다운캐스팅

            // 같은지 비교
            if (
                    this.name.equals(person.name) && 
                    this.age == person.age
            ){
                return true;
            }
        }

        return false;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

 

 

3. toString()

- 객체의 "패키지명@16진수코드" 를 반환하는 메서드

- 16진수는 주소를 리턴한 값이다.

- toString() 또한 우리는 문자 정보를 받도록 알고 있다.

- 그리고 위의 내용과 비슷하게 toString() 또한 오버라이딩 되어 쓰여왔다는 걸 알 수 있다.

- 위 hashCode 설명 코드를 보고, 예상해보자. 같은 객체이므로 toString 또한 주소 값이 똑같이 나올 것이다. 그리고 오버라이딩 해주었으니, 문자열로 출력될 것이다.

- 주소 같은 의미 없는 데이터를 반환하는 것보다는 의미 있는 문자 정보를 반환하도록 오버라이딩

  ex) Date, String 클래스

 

 

4. clone()

- 객체를 복제

- 복제의 종류

  : 얕은 복제 (Thin Clone)  :  멤버변수 값만 복사하고 참조변수들은 주소를 서로 공유

  : 깊은 복제 (Deep Clone)  :  멤버변수 뿐만 아니라 참조변수들의 주소도 다르게 객체를 복사한다.

- Object 클래스의 clone() 메소드 기능은 Thin Clone 이다.

- 또한 java.lang.Cloneable 인터페이스를 구현한 객체만 복제가 가능하다. (implements Cloneable)

- 신기하게도 Cloneable 인터페이스는 아무 내용이 없다....!

- 아무 내용이 없지만, 이를 implements 하지 않은 클래스를 복제하게 되면 CloneNotSupportedException 발생

- 하여, 참조변수들은 프로그래머들이 직접 복제하는 코드를 필히 작성해줘야 한다.

 

- 얕은 복제 (Thin Clone) 실습 코드

import java.util.Arrays;

public class Example {

    public static void main(String[] args) {

        Product originProduct = new Product("1", "TV", 4000, new int[]{ 1, 2}, new Objc());
        Product cloneProduct = originProduct.cloneProduct();

        // 가격 변경
        originProduct.setPrice(1000);

        // 배열 변경
        int arr[] = originProduct.getArr();
        arr[0] = 100;

        
        //== 얕은 복제의 출력 확인
        System.out.println("오리지날 : " + originProduct.toString());
        System.out.println("===>클론 : " + cloneProduct.toString());

        
        /**
         * 얕은 복제의 출력 결과
         *
         * 오리지날 : Product{id='1', name='TV', price=1000, arr=[100, 2], objc=Objc@1b6d3586}
         * ===>클론 : Product{id='1', name='TV', price=4000, arr=[100, 2], objc=Objc@1b6d3586}
         */
    }
}

class Product implements Cloneable {

    String id;
    String name;
    int price;
    int[] arr;
    Objc objc;

    public Product(String id, String name, int price, int[] arr, Objc objc) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.arr = arr;
        this.objc = objc;
    }

    public int[] getArr() {
        return arr;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public void setArr(int[] arr) {
        this.arr = arr;
    }

    @Override
    public String toString() {
        return "Product{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", price=" + price +
                ", arr=" + Arrays.toString(arr) +
                ", objc=" + objc +
                '}';
    }

    public Product cloneProduct() {
        Product cloneProduct = null;

        try {
            // Object 클래스의 clone
            cloneProduct = (Product) this.clone();	// Thine Clone

        } catch (CloneNotSupportedException e) {
            e.getMessage();
        }

        return cloneProduct;
    }
}

class Objc {
    String temp;
}

 

- 깊은 복제 (Deep Clone) 실습 코드

import java.util.Arrays;

public class Example {

    public static void main(String[] args) {

        Product originProduct = new Product("1", "TV", 4000, new int[]{ 1, 2}, new Objc());
        Product cloneProduct = originProduct.cloneProduct();

        // 가격 변경
        originProduct.setPrice(1000);

        // 배열 변경
        int arr[] = originProduct.getArr();
        arr[0] = 100;


        //== 깊은 복제의 출력 확인
        System.out.println("오리지날 : " + originProduct.toString());
        System.out.println("===>클론 : " + cloneProduct.toString());


        /**
         * 깊은 복제의 출력 결과
         *
         * 오리지날 : Product{id='1', name='TV', price=1000, arr=[100, 2], objc=Objc@1b6d3586}
         * ===>클론 : Product{id='1', name='TV', price=4000, arr=[1, 2], objc=Objc@1540e19d}
         */
    }
}

class Product implements Cloneable {

    String id;
    String name;
    int price;
    int[] arr;
    Objc objc;

    public Product(String id, String name, int price, int[] arr, Objc objc) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.arr = arr;
        this.objc = objc;
    }

    public int[] getArr() {
        return arr;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public void setArr(int[] arr) {
        this.arr = arr;
    }

    @Override
    public String toString() {
        return "Product{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", price=" + price +
                ", arr=" + Arrays.toString(arr) +
                ", objc=" + objc +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // Object 클래스의 clone() 호출
        Product cloneProduct = (Product) super.clone(); // Thin Clone

        // 참조변수 복제 - 배열 및 객체
        cloneProduct.arr = Arrays.copyOf(this.arr, this.arr.length);
        cloneProduct.objc = new Objc();

        return cloneProduct;
    }

    public Product cloneProduct() {
        Product cloneProduct = null;

        try {
            // 오버라이딩 clone
            cloneProduct = (Product) this.clone();

        } catch (CloneNotSupportedException e) {
            e.getMessage();
        }

        return cloneProduct;
    }
}

class Objc {
    String temp;
}

 

 

 

5. finalize()

- 객체를 소멸시킬 때 사용

- 기본적으로 GC 가 객체를 소멸하기 직전에 finalize() 를 실행시킨다.

- 객체가 소멸되기 직전 실행할 코드가 있따면 finalize() 를 오버라이딩 하여 사용하면 되지만, 그런일은 없을것이다.

- 사용할 일이 없으면 Object 클래스에 이런 메서드가 있다고만 알아두자.

 

* 참고: GC의 구동 시점은 ? 알 수 없다. 메모리가 부족하다. CPU 가 한가하다 등의 애매한 말이 있긴하지만 말도 안되는 말이다. 대신 GC 를 호출할 수 있는 방법은 있다. System.gc() 메서드를 사용하면 되지만, 바로바로 실행 되지는도 알 수 없고 순차적으로 실행 되는것도 알 수 없다.