ksouth9

java.lang 패키지(2) 본문

Java

java.lang 패키지(2)

ksouth9 2022. 3. 27. 02:11

clone()


clone()메서드는 자신을 복제하여 새로운 인스턴스를 생성하는 일을 한다.

Object클래스에 정의된 clone()은 단순히 인스턴스변수의 값만 복사하기 때문에 참조타입의 인스턴스 변수가 있는 클래스는 완전한 인스턴스 복제가 이루어지지 않는다. 

class Point implements Cloneable {	// Cloneable인터페이스를 구현한 클래스에서만 clone()을 호출할 수 있다.
	int x,y;
	
	Point(int x,int y){
		this.x = x;
		this.y = y;
	}

	@Override
	public String toString() {
		return "x = "+x+" y = "+y;
	}

	@Override
	public Object clone() throws CloneNotSupportedException {
		Object obj = null;
		try {
			obj = super.clone();
		} catch (CloneNotSupportedException e) {}
			return obj;
	}
		
}

public class CloneEx {

	public static void main(String[] args) throws CloneNotSupportedException {
		Point original = new Point(3,5);
		Point copy = (Point) original.clone();
		System.out.println(original);
		System.out.println(copy);
	}

}//실행결과
x = 3 y = 5
x = 3 y = 5

clone()을 사용하려면, 먼저 복제할 클래스가 Cloneable인터페이스를 구현해야하고, clone()을 오버라이딩하면서 접근 제어자를 protected에서 publicd으로 변경해야만 상속관계가 없는 다른 클래스에서 clone()을 호출 할 수 있다.

 

Cloneable인터페이스를 구현한 클래스의 인스턴스만 clone()을 통한 복제가 가능하다. 그 이유는 인스턴스의 데이터를 보호하기 위해서이다. Cloneable인터페이스가 구현되어 있다는 것은 클래스 작성자가 복제를 허용한다는 의미이다.

공변 반환타입


공변 반환타입 기능은 오버라이딩할 때 조상 메서드의 반환타입을 자손 클래스의 타입으로 변경을 허용하는 것이다. (JDK1.5부터 '공변 반환타입'이 추가 되었다.)

public Point clone() {	// 반환타입을 Object에서 지정한 class이름으로 변경
	Object obj = null;
    try {
    	obj = super.clone();
        } catch (CloneNotSupportedException e) {}
        return (Point)obj;	// Point 타입으로 형변환한다.
}
Point copy = (Point)original.clone();
			↓↓↓
Point copy = original.clone();

위의 코드처럼 '공변 반환타입'을 사용하면, 조상의 타입이 아닌, 실제로 반환되는 자손 객체의 타입으로 반환할 수 있어서 번거로운 형변환이 줄어든다.

얕은 복사와 깊은 복사


clone()은 단순히 객체에 저장된 값을 그대로 복제할 뿐, 객체가 참조하고 있는 객체까지 복제하지 않는다.

예를 들어 기본형 배열인 경우에는 아무런 문제가 없지만, 객체배열을 clone()으로 복제하는 경우에는 원본과 복제본이 같은 객체를 공유하므로 완전한 복제라고 보기 어렵다. 

 

얕은 복사

  • 객체에 저장된 값을 복사하는 것.
  • 원본과 복사본이 같은 객체를 참조한다.
  • 원본을 변경하면 복사본도 영향을 받는다.

깊은 복사

  • 원본이 참조하고 있는 객체까지 복제하는 것.
  • 원본과 복사본이 서로 다른 객체를 참조한다.
  • 원본의 변경이 복사본에 영향을 미치지 않는다.
  • 원본이 참조하고 있는 객체를 선언해주면 깊은 복사를 사용가능하다.
public class Circle implements Cloneable {
	Point p;
	double r;
	
	Circle(Point p, double r){
		this.p = p;
		this.r = r;
	}
	public Circle clone() {
		Object obj = null;
		try {
			obj = super.clone();	// 조상인 Object의 clone()을 호출.
		} catch (CloneNotSupportedException e) {}
			return (Circle)obj;
	}

	public static void main(String[] args) {
		Circle c1 = new Circle(new Point(1,1),2.0);
		Circle c2 = c1.clone();	// 얕은 복사.
	}

}

c1과 c2는 같은 Point 인스턴스를 가리키게 되므로 완전한 복제라고 볼 수 없다.

class Point{
	int x, y;
	
	Point(int x, int y){
		this.x = x;
		this.y = y;
	}

	@Override
	public String toString() {
		return "("+x+", "+y+")";
	}
	
}

public 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();	// 조상인 Object의 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;
	}
	
	@Override
	public String toString() {
		return "[p= "+p+", r = "+r+"]";
	}
	
	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);
	}

}

shallowCopy()의 내용을 보면, 단순히 Object클래스의 clone()을 호출할 뿐이다. Object클래스의 clone()은 원본 객체가 가지고 있는 값만 그대로 복사한다.(얕은 복사)

deepCopy()는 shallowCopy()에 복제된 객체가 새로운 Point인스턴스를 참조하도록 했다. 원본이 참조하고 있는 객체까지 복사한 것

getClass()


getClass()메서드는 자신이 속한 클래스의 Calss객체를 반환하는 메서드이다. Class객체는 이름이 'Class'인 클래스의 객체이다.

  • Class객체는 클래스의 모든 정보를 담고있다.
  • 클래스 당 1개만 존재한다.
  • 클래스 파일이 '클래스 로더'에 의해서 메모리에 올라갈 때, 자동으로 생성된다.
  • Class객체를 이용하면 클래스에 정의된 멤버의 이름이나 개수 등, 클래스의 대한 모든 정보를 얻을 수 있다.

클래스 로더는 실행 시에 필요한 클래스를 동적으로 메모리에 로드하는 역할을 한다.

먼저 기존에 생성된 클래스 객체가 메모리에 존재하는지 확인하고, 있으면 객체의 참조를 반환하고 없으면 클래스 패스에 지정된 경로를 따라서 클래스 파일을 찾는다. 못찾으면 ClassNotFoundException이 발생하고, 찾으면 해당 클래스 파일을 읽어서 Class객체로 변환한다.

파일 형태로 저장되어 있는 클래스를 읽어서 Class클래스에 정의된 형식으로 변환하는 것이다. 즉, 클래스 파일을 읽어서 사용하기 편한 형태로 저장해 놓은 것이 클래스객체이다.

 

Class객체 얻는 방법

Class cObj = new Card().getClass();	// 생성된 객체로 부터 얻는 방법
Class cObj = Card.class;	// 클래스 리터럴(*.class)로 부터 얻는 방법
Class cObj = Class.forName("Card");	// 클래스 이름으로 부터 얻는 방법

forName()은 특정 클래스 파일을 메모리에 올릴때 주로 사용한다.

Card c = new Card();	// new 연산자를 이용해서 객체 생성
Card c = Card.class.newInstance();	// Class 객체를 이용해서 객체 생성
final class Card{
	String kind;
	int num;
	
	Card(){
		this("SPADE",1);
	}
	Card(String kind,int num){
		this.kind = kind;
		this.num = num;
	}
	@Override
	public String toString() {
		return kind + " : "+num;
	}
}

public class ClassEx {

	public static void main(String[] args) throws InstantiationException, IllegalAccessException {
		Card c = new Card("HEART",3);	// new 연산자로 객체 생성
		Card c2 = Card.class.newInstance();	// Class 객체를 통해서 객체 생성
		
		Class cObj = c.getClass();
		
		System.out.println(c);
		System.out.println(c2);
		System.out.println(cObj.getName());
		System.out.println(cObj.toGenericString());
		System.out.println(cObj.toString());

	}

}

Java API문서에서 Class클래스를 찾아보면 클래스의 정보를 얻을 수 있는 많은 수의 메서드가 정의되어 있다.

'Java' 카테고리의 다른 글

String클래스의 생성자와 메서드  (0) 2022.03.28
java.lang 패키지(3) - String클래스  (0) 2022.03.28
java.lang 패키지(1)  (0) 2022.03.20
예외처리(exception handling)(3)  (0) 2022.03.18
예외처리(exception handling)(2)  (0) 2022.03.18