[Java] Object 클래스

Bam·2024년 3월 9일
0

Java

목록 보기
50/98
post-thumbnail

Object 클래스

상속을 다룰 때 잠깐 언급했던 모든 클래스의 최고 조상인 Object 클래스에 대해 알아봅시다.

Object 클래스는 모든 클래스의 최고 조상이며, 최고 조상이기 때문에 모든 클래스에서 바로 접근이 가능한 클래스입니다.

Object 클래스의 메소드들은 다음과 같은 것들이 있습니다.

공식 문서에서 직접 확인하실 수 있습니다.

Object 클래스의 멤버 중 자주 사용되는 clone(). equals(), hashCode(), toString() 네 가지만 조금 더 자세하게 살펴보려고 합니다.

clone()

공식 문서 clone()

clone()은 자신을 복제해 새로운 인스턴스를 생성하는 메소드입니다.

clone()은 멤버 변수의 값만을 복사하기 때문에 참조형 변수에 대해서는 완전한 복제가 이루어지지 않을 수 있습니다. 즉, clone()은 기본적으로 얕은 복사를 수행합니다. 만약 원본에 영향을 주고싶지 않다면 clone() 메소드를 오버라이딩해서 별개의 메모리 공간을 만든 후 복사하도록 작업을 해야합니다.

참조형은 주소값을 저장하기 때문에 원본 객체든, 복사된 객체든 같은 주소의 같은 참조형 객체를 가리키게 됩니다. 따라서 복사본에서 수정을 하면 원본 객체에 영향이 갈 수 있음을 주의해야합니다.

얕은 복사와 깊은 복사

얕은 복사(swallow-copy)는 원본과 복사본은 따로 생성되지만, 복사본의 변화가 원본에 영향을 줍니다. 반대로 원본의 변화가 복사본에 영향을 주게 됩니다.

비록 자바스크립트이긴하지만 얕은 복사/깊은 복사의 개념은 동일하기에 예전에 작성한 두 복사에 대한 내용을 한 번 읽어보시는 것도 추천드립니다.

깊은 복사(deep-copy)는 원본과 복사본을 따로 생성하고, 그 내부의 내용도 별개의 것으로 취급하는 것 입니다. 복사하는 변수가 서로 다른 것을 참조한다는 의미입니다. 그래서 복사본의 변화가 원본에 아무런 영향을 주지 않습니다.


다시 clone()으로 돌아와서 clone()을 사용하고자한다면 클래스에 Cloneable 인터페이스를 구현해주어야합니다.

public class Obj implements Cloneable {	//clone() 사용을 위한 Cloneable 인터페이스 implements
    public int n;

    public Obj(int n) {
        this.n = n;
    }

	@Override
    public Object clone() {	//Object 클래스의 clone() 오버라이딩
        Object obj = null;

        try {	//clone()에는 CloneNotSupportedException 예외가 명시되어있으므로 try 구문 사용
            obj = super.clone();
        } catch (CloneNotSupportedException e) {
            return obj;
        }
        return obj;
    }
}
public class Main {
    public static void main(String[] args) {
        Obj obj = new Obj(100);
        Obj copyObj = (Obj)obj.clone();	//복사된 객체는 Object 타입이므로 Obj타입으로 형변환

        System.out.println(copyObj.n);
    }
}

복사된 객체 copyObjn 값이 제대로 복사되었죠? 이때 객체.clone()으로 생성되는 객체는 Object 타입이기 때문에 타입이 맞지 않는다면 형변환을 이용해야합니다.

clone() 메소드는 CloneNotSupportedException예외를 throws하므로 try~catch 구문을 사용해서 예외 처리를 해주어야합니다.

참조형 깊은 복사의 경우 clone() 오버라이딩 부분에 새 참조형을 만든 후 복사합니다.
ex) 배열이라면, 새 배열을 만들고 요소들을 복사하는 코드 삽입해야 원본/복사 배열이 서로 영향을 주지 않습니다.


equals()

공식 문서 equals()

equals()는 두 객체가 동일한지 아닌지를 판단해서 ture/false의 결과를 반환하는 메소드입니다. 두 객체가 동일하다는 것은 같은 주소의 같은 객체를 가리킨다는 것을 의미합니다.

방금 전의 코드를 가지고 equals() 메소드를 직접 사용해보겠습니다.

public class Main {
    public static void main(String[] args) {
        Obj obj1 = new Obj(100);
        Obj obj2 = obj1;
        Obj copyObj = (Obj)obj1.clone();

        System.out.println(obj1.equals(obj2));
        System.out.println(obj1.equals(copyObj));
    }
}

먼저, obj2obj1대입 연산자 =를 통해 대입했습니다. 따라서 두 객체는 완전히 동일하고, 같은 주소를 가리키기 때문에 true입니다.

하지만 clone() 객체는 아까 내부 코드를 보시면 새로운 Object 타입 객체를 만들고 해당 객체에 내용을 복사했기 때문에 내용은 같지만 주소가 다른 객체입니다. 따라서 결과는 false가 됩니다.

@Override
public Object clone() {
	Object obj = null;

	try {
		obj = super.clone();
	} catch (CloneNotSupportedException e) {
		return obj;
	}
	return obj;
}

앞으로 나올 메소드도 그렇지만 equals()도 오버라이딩이 가능합니다. 대표적으로 String에도 equals() 메소드가 있는데, 이 메소드가 Object.equals()를 오버라이딩해서 String에 맞게 재정의된 메소드입니다.


hashCode()

공식 문서 hashCode()

hashCode()해시 함수를 구현한 메소드입니다. 해시 함수는 데이터 저장과 검색에 사용되는 해싱에 사용되는 기술입니다. 해시 함수는 검색을 위해 데이터를 입력하면 검색 결과의 위치를 알려주는 해시 코드를 반환합니다.

자바에서 hashcode()는 객체의 주소값을 통해 해시 코드를 생성해서 반환하게 됩니다. 그렇기 때문에 한 번의 실행에서는 둘 이상의 객체가 서로 같은 값을 가질 수 없게됩니다. 이를 이용해서 객체가 동일한 객체인지를 판별하는데 사용하게 됩니다.

hashcode()는 정수값으로 된 숫자를 반환합니다.

public class Main {
    public static void main(String[] args) {
        Obj obj1 = new Obj(100);
        Obj obj2 = obj1;
        Obj copyObj = (Obj)obj1.clone();
        
        System.out.println(obj1.hashCode());
        System.out.println(obj2.hashCode());
        System.out.println(copyObj.hashCode());

        System.out.println(obj1.hashCode() == obj2.hashCode());
        System.out.println(obj1.hashCode() == copyObj.hashCode());
    }
}

역시 이전 코드에서 비교하는 구문만 변경했습니다. obj1obj2는 같은 객체를 가리키기 때문에 해시코드가 동일하고, 복사된 객체는 다른 객체 생성 후 복사한 것이므로 해시코드가 다르게 반환됩니다.


toString()

공식 문서 toString()

toString()은 객체에 대한 정보를 문자열 형태로 반환하는 메소드입니다. 기본적으로 클래스이름@16진수해시코드의 형태로 값을 반환합니다. 16진수 해시코드가 객체의 주소에 대한 정보를 가지고 있습니다. 만약 클래스가 패키지에 속해있다면 패키지 이름도 같이 출력해줍니다.

public class Main {
    public static void main(String[] args) {
        Obj obj1 = new Obj(100);
        Obj obj2 = new Obj(100);

        System.out.println(obj1.toString());
        System.out.println(obj2.toString());
    }
}

또는 다음과 같이 오버라이딩을 통해 유효한 데이터를 반환하도록 만들수도 있습니다.

public class Obj{
    public int n;

    public Obj(int n) {
        this.n = n;
    }

    @Override
    public String toString() {	//toString 오버라이딩
        return "n: " + this.n;
    }
}
public class Main {
    public static void main(String[] args) {
        Obj obj1 = new Obj(100);
        Obj obj2 = new Obj(200);

        System.out.println(obj1.toString());
        System.out.println(obj2.toString());
    }
}


지금까지 Object 클래스의 주요한 메소드들을 알아보았습니다.

메소드들을 그대로 사용해도 좋지만 오버라이딩했을 때 더욱 활용도가 높아진다는 공통점도 보입니다. 따라서 각 메소드들의 기본 사항을 숙지하고, 사용하고자 하는 용도에 맞게 오버라이딩을 해서 사용할 줄 알면 더 좋은 프로그래밍이 가능해집니다.

0개의 댓글