[기본 개념] 4 | (1.2) Object 클래스의 메서드
1> Object클래스
2 String클래스
3 StringBuffer클래스와 StringBuilder클래스
4 Math클래스
5 래퍼(wrapper)클래스
1. Object클래스(2)
clone( )
이 메서드는 자신을 복제하여 새로운 인스턴스를 생성하는 일을 한다. 인스턴스에 대해 작업할 때, 원래의 인스턴스는 보존하고 clone메서드를 이용해서 새로운 인스턴스를 생성하여 작업을 하면 작업 이전의 값이 보존되므로 작업에 실패해서 원래로 되돌리거나 변경하기 전의 값을 참고하는데 도움이 될 것이다.
Object클래스에 저의 clone( )은 단순히 인스턴스변수의 값만 복사하기 때문에 참조타입의 인스턴스 변수가 있는 클래스는 완전한 인스턴스 복제가 이루어지지 않는다.
clone( )을 사용하려면, 먼저 복제할 클래스가 Cloneable인터페이스를 구현해야 한다. 왜냐하면 Cloneable인터페이스가 구현되어 있다는 것은 클래스 작성자가 복제를 허용한다는 의미이므로 인스턴스의 데이터를 보호하기 위해서이다. Cloneable을 구현하지 않은 클래스내에서 호출하면 예외를 발생시킨다.
그리고 clone( )을 오버라이딩하면서 접근제어자를 protected에서 public으로 변경해야 상속관계가 없는 다른 클래스에서 clone( )을 호출할 수 있다.
public class Object {
. . .
protected native Object clone( ) throws CloneNotSupportedException ;
. . .
}
마지막으로 조상클래스의 clone( )을 호출하는 코드가 포함된 try-catch문을 작성한다.
class Point implements Cloneable { // 1 Cloneable인터페이스 구현
. . .
public Object clone( ) { // 2 접근제어자를 protected에서 public으로 변경
Object obj = null ;
try {
obj = super.clone( ) ; // 3 try-catch내에서 조상클래스 clone( )호출
} catch (CloneNotSupportedException e) { }
return obj ;
}
}
공변 반환타입
오버라이딩할 때 조상메서드의 반환타입을 자손클래스의 타입으로 변경을 허용하는 것이다. 조상타입이 아닌 실제로 반환되는 자손 객체의 타입으로 변환할 수 있어서 번거로운 형변환이 줄어든다.
class Point implements Cloneable {
. . .
public Point clone( ) { // 1 반환타입을 Object에서 Point로 변경
Object obj = null ;
try {
obj = super.clone( ) ;
} catch (CloneNotSupportedException e) { }
return (Point)obj ; // 2 Point타입으로 형변환한다.
}
}
공변 반환타입을 사용하면 코드가 줄어든다.
Point copy = (Point)original.clone( ) ; |
----> | Point copy = original.clone( ) ; |
얕은 복사와 깊은 복사
clone( )은 단순히 객체에 저장된 값을 그대로 복제할 뿐, 객체가 참조하고 있는 개체까지 복제하지는 않는다. 기본형 배열인 경우에는 아무런 문제가 없지만 객체 배열을 clone( )으로 복제하는 경우에는 원본과 복제본이 같은 객체를 공유하므로 완전한 복제라 보기엔 어렵다. 이러한 복제를 '얕은 복사(shallow copy)'라고 한다. 이는 원본을 변경하면 복사본도 영향을 받는다.
원본이 참조하고 있는 객체까지 복제하는 것을 '깊은 복사(deep copy)'라고 하며, 원본과 복사본이 서로 다른 객체를 참조하기 때문에 원본의 변경이 복사본에 영향을 미치지 않는다.
Circle클래스가 Point타입의 참조변수를 포함하고 있고, clone( )은 단순히 Object클래스의 clone( )을 호출하도록 정의되어 있다. Circle인스턴스의 c1을 clone( )으로 복제해서 c2를 생성하면, c1과 c2는 같은 Point인스턴스를 가리키게 되므로 완전한 복제라고 볼 수 없다.
예제/ShallowDeepCopy.java
import java.util.*;
public class ShallowDeepCopy {
public static void main(String[] args) {
Circle c1 = new Circle(new Point(1, 1), 2.0);
Circle c2 = c1.shallowCopy();
Circle c3 = c1.deepCopy();
System.out.println("c1 = " + c1);
System.out.println("c2 = " + c2);
System.out.println("c3 = " + c3);
c1.p.x = 9;
c1.p.y = 9;
System.out.println("= c1의 변경 후 =");
System.out.println("c1 = " + c1);
System.out.println("c2 = " + c2);
System.out.println("c3 = " + c3);
}
}
class Circle implements Cloneable {
Point p; // 원점
double r; // 반지름
Circle(Point p, double r) {
this.p = p;
this.r = r;
}
public Circle shallowCopy() {
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) { }
return (Circle) obj;
}
public Circle deepCopy() {
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) { }
Circle c = (Circle) obj;
c.p = new Point(this.p.x, this.p.y);
return c;
}
public String toString() {
return "[p = " + p + ", r = " + r + "]";
}
}
class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
public String toString() {
return "(" + x + ", " + y + ")";
}
}
실행결과
c1 = [p = (1, 1), r = 2.0]
c2 = [p = (1, 1), r = 2.0]
c3 = [p = (1, 1), r = 2.0]
= c1의 변경 후 =
c1 = [p = (9, 9), r = 2.0]
c2 = [p = (9, 9), r = 2.0]
c3 = [p = (1, 1), r = 2.0]
인스턴스 c1을 생성한 후 얕은 복사로 c2를 생성하고, 깊은 복사로 c3를 생성했다.
얕은 복사는 Object클래스의 clone( )이 원본 객체가 가지고 있는 값만 그대로 복사하였다. 반면에 깊은 복사는 복제된 객체가 새로운 Point인스턴스를 참조하도록 하였다. 원본이 참조하고 있는 객체까지 복사한 것이다.
getClass( )
이 메서드는 자신이 속한 클래스의 Class객체를 반환하는 메서드이다.
public final class Class implements . . . { // Class클래스
. . .
}
Class객체는 클래스의 모든 정보를 담고 있으며 클래스 당 1개만 존재한다. 그리고 클래스 파일이 '클래스 로더(ClassLoader)'에 의해 메모리에 올라갈 때, 자동으로 생성된다.
먼저 기존에 생성된 클래스 객체가 메모리에 존재하는지 확인하고 있으면 객체의 참조를 반환하고, 없으면 클래스 패스(classpath)에 지정된 경로를 따라 클래스 파일을 찾는다. 못 찾으면 CloassNotFoundException이 발생하고, 찾으면 해당 클래스 파일을 읽어서 Class객체로 변환한다.
Class객체를 얻는 법
먼저 Class객체에 대한 참조를 얻어와야 하는데, 해당 Class객체에 대한 참조를 얻는 방법은 여러 가지가 있다.
Class cObj = new Card( ).getClass( ) ; // 생성된 객체로부터 얻는 방법
Class cObj = Card.class ; // 클래스 리터럴(*.class)로부터 얻는 방법
Class cObj = Class.forMame("Card") ; // 클래스 이름으로부터 얻는 방법
Class객체를 이용하면 객체를 생성하고 메서드를 호출하는 등 보다 동적인 코드를 작성할 수 있다.
Card c = new Card( ) ; // new연산자를 이용해서 객체생성
Card c = Card.class.newInstance( ) ; // Class객체를 이용해서 객체생성
(newInstance( )는 예외처리 필요)
출처 | Java의 정석 (남궁 성)
'💠프로그래밍 언어 > Java' 카테고리의 다른 글
[기본 개념] 4 | (1.4) StringBuffer클래스, StringBuilder 클래스의 메서드 (0) | 2021.11.26 |
---|---|
[기본 개념] 4 | (1.3) String 클래스의 메서드 (0) | 2021.11.26 |
[기본 개념] 4 | (1.1) Object 클래스의 메서드 (0) | 2021.11.26 |
[기본 개념] 3 | (1.4) 사용자 정의 예외, 예외 되던지기, 연결된 예외 (0) | 2021.11.25 |
[기본 개념] 3 | (1.3) Finally 블럭, Try-with-resources 문 (0) | 2021.11.24 |