자바의 모든 객체의 최상위 클래스는 Object 클래스라는 것을 다들 알고 있을 것입니다.
이 말이 의미하는 것은 모든 클래스의 부모를 따라 올라가다보면 결국은 Object 클래스를 상속해 내려왔었다는 사실을 알 수 있습니다. 이러한 Object클래스가 어떤 역할을 하고 영향을 미치는지 한번 정리해 보려고 합니다.
Object 클래스에 대해 공부하면서 finalize라는 소멸자가 있다는 사실을 처음 알았습니다.
Protected void finalize() throws Throwable
이 메소드는 GC을 통해 힙 영역의 인스턴스가 소멸(Sweep) 될 때 자동으로 호출 되는 메소드입니다.
Object클래스의 메소드라 오버라이드 해서 사용할 수 있지만, 프로그램이 실행되는 전체 과정중에 소멸자가 반드시 호출 할 것이라는 보장이 없기에 (프로그램이 종료되면 따로 소멸자를 호출하지 않고 그냥 전부 종료) 사용하기에 좋은 메소드는 아닙니다.
Object 에 참조값을 비교하는 메소드인 equals()도 존재합니다. 필자는 Equals() 메소드를 언제나 String 의 값을 비교할 경우에만 사용해 왔기에 == 연산자와는 다른 역할을 하는 메소드로 인지하고 있었는데 본질적으로 == 연산자와 똑같다는 사실에 놀라웠습니다.
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2); // false
System.out.println(str1.equals(str2)); // true
==연산자: 피연산자의 참조값이 일치하는가 비교
이미 알고 있거나 예상 했겠지만 String 클래스 내부에서 Object 클래스의 equals() 메소드를 오버라이딩 해서 사용하고 있기 때문입니다.
위의 코드를 하나씩 살펴보면
첫 if문을 통해 this 와 비교대상 객체의 참조값을 비교합니다. -> 자기자신인지 확인, 동일하면 true 반환
이후 객체가 String의 인스턴스인지 판단하고, 문자열의 인코딩 방식을 판단하고, 이후 마지막 String의 내부 클래스인 StringLatin1의 equals를 통해 값을 비교 한 후 그 결과를 반환한다고 합니다.
이렇게 Object의 equals() 메소드를 오버라이딩 함으로써 프로그래머가 원하는 방식으로 동등 비교를 할 수 있습니다.
클론 메소드를 호출하면 똑같이 복사된 인스턴스의 Object 형 참조값이 반환됩니다.
clone() 메소드는 cloneable 인터페이스가 구현되어 있어야 호출할 수 있어서 인터페이스의 메소드라고 생각할 수 있는데, Clone 메소드는 Object 클래스의 메소드입니다.
복사하면 빠질 수 없는 이야기 중 하나가 얕은 복사와 깊은 복사 입니다. 간단히 설명을 해보자면
얕은복사: 새로운 인스턴스로 복제해서 참조하는것이 아니고 현존하는 참조값을 그대로 다른 참조변수로 복제해 사용하는 것으로 참조하는 데이터를 수정하면 원본 데이터도 값이 변하게 됩니다. 이런 복사를 얕은 복사라고 합니다.
깊은복사: 깊은복사는 새로운 참조변수에 원본과 똑같은 값을 가진 새로운 인스턴스를 참조할 수 있도록 복제하는 것을 말합니다. 이 경우 전혀 다른 두 인스턴스이기 때문에 복제된 객체의 값을 수정해도 원본에 영향이 가지 않습니다.
// 점의 좌표를 나타내는 포인트 클래스
class Point implements Cloneable {
private int xPos;
private int yPos;
public Point(int x, int y) {
xPos = x;
yPos = y;
}
}
// 사각형 클래스
class Rectangle implements Cloneable {
private Point upperLeft;
private Point lowerRight;
public Rectangle(int x1, int y1, int x2, int y2) {
upperLeft = new Point(x1, y1);
lowerRight = new Point(x2, y2);
}
@Override
public Object clone() throws CloneNotSupportedException {
Rectangle copy = (Rectangle)super.clone();
return copy;
}
}
위와 같은 상황에서 아래 코드를 실행시키면 어떻게 될까?
Rectangle rec = new Rectangle(1, 1, 9, 9);
Rectangle cpy = (Rectangle) rec.clone();
보기에는 새로운 인스턴스를 할당받아 cpy에 참조값을 전달하고 있기에 완전히 복제된 것처럼 보일 수 있다.
하지만 내부적으론 이렇게 Rectangle 내부의 Point 변수는 원본 데이터와 같은 인스턴스를 참조하게 된다. 이러한 오류를 범하지 않도록 clone() 메소드를 호출 할 경우 주의해야한다. 이러한 문제를 해결한 깊은 복사의 예시도 살펴보자
// 점의 좌표를 나타내는 포인트 클래스
class Point implements Cloneable {
...
// 생략
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// 사각형 클래스
class Rectangle implements Cloneable {
...
// 생략
@Override
public Object clone() throws CloneNotSupportedException {
Rectangle copy = (Rectangle)super.clone();
copy.upperLeft = (Point)upperLeft.clone();
copy.lowerRight = (Point)lowerRight.clone();
return copy;
}
}
방법은 간단하다. 위와 같이 복제한 인스턴스의 Point 참조값도 clone을 통해 참조하면 완전히 같은 형태를 지닌 복제된 인스턴스를 반환받을 수 있다.