language/java

Object clone() - 얕은 복사, 깊은 복사

wooweee 2023. 3. 28. 11:55
728x90

clone() _ 얕은 복사, 깊은 복사

  • clone()
    • 인스턴스변수의 값만 복사하기 때문에 참조타입의 인스턴스 변수가 있는 클래스는 완전한 인스턴스 복제가 일어나지 않는다.
      • 참조타입의 인스턴스 변수: 객체에 iv들이 존재하는데 iv 값이 다른 객체 주소를 가리키는 상황.

      • 완전한 복제가 안된다는 의미: 객체까지는 복사가 됬는데 객체 내부의 iv가 가리기는 다른 객체는 서로 공유하는 상황

    • 이와 같은 clone을 얕은 복사라고 한다. -> 서로 공유하는 객체가 존재하기 때문


    • clone() 사용 방법
      clone의 얕은 복사로 인해 원본 객체에 영향을 끼치기 때문에 원본 보호 목적으로 특정 조건을 허용해야 clone()을 사용할 수 있도록 했다.
      1. implements Cloneable : interface 구현

      2. 접근제어자를 public으로 변경

      3. try-catch문을 통해 반드시 예외 처리 필요

    • clone 내부의 작동이 궁금하다면 아래 블로그를 참고
      https://vixxcode.tistory.com/204#--%--%--Target
class Point implements Cloneable {
    int x, y;
    
    Point(int x, int y){
        this.x = x;
        this.y = y;
    }
    
    public String toString() {
        return "x=" + x + ", y=" + y;
    }
    
    public Object clone(){
        Object obj = null;
        try {
            obj = super.clone(); 
            // clone의 내부 작동을 사용하기 위해서는 조상 Object의 clone으로 갈 수 밖에 없다.
        } catch (CloneNotSupportedException e) {}
        return obj;
    }
}

class CloneEx1 {
    public static void main(String[] args){
        Point original = new Point(3,5);
        Point copy = (Point)original.clone(); // clone 후 새 객체 생성
        System.out.println(original);
        System.out.println(copy);
    }
}

 

 

  • 얕은 복사 vs 깊은 복사 <면접 질문>
    • 얕은 복사
      • 정의 : 원본과 복제본이 같은 객체를 공유하는 복사
      • 장점 : 복사 속도가 빠르다.
      • 단점 : 원본을 회손하므로 변경에 유리하지 않다.
      • 사용 : 읽기만 할 때 

    • 깊은 복사
      • 정의 : 원본이 참조하고 있는 객체까지 복제하는 것
      • 장점 : 완벽한 복사본을 가지므로 원본을 회손하지 않는다. == 변경에 유리하다.
      • 단점 : 복사 속도가 느리다. 만들기 복잡하다.
      • 사용 : 읽고 쓰기를 할 때

    • 코드와 그림으로 보기
      • p, r 은 얕은복사와 깊은 복사 둘다 복사 완료
      • p가 가진 객체 주소는 깊은 복사만 복사 완료

자바의 정석 3판

 

public class Practice {
    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);

        c2.p.x =1234323;
        c2.p.y =3413542;
        c1.r = 34.23423;
        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); // c.p가 가리키는 Point 객체주소를 새로운 객체주소로 변경해줌
        return c;
    }

    public String toString() {
        return "[p=" + p + ", r=" + r + "]";
    }
}

class Point{
    int x, y;

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

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

 

  • 공변 반환타입 (Covariant return type) 
    • 오버라이딩할 때 조상 메서드의 반환타입을 자손 클래스의 타입으로 변경을 허용
    • 변성의 종류
      • 자바의 변성 - 공변/무공변/반공변, 사용지점 변성과 선언 지점 변성 - https://scshim.tistory.com/531
      • 현재는 공변이 핵심이 아니므로 이정도로 넘어간다.
    • 사용 목적
      • 형변환을 줄이는 것이 의미없는 코드길이를 단축화 할 수 있다.

 

  • 참고 - 배열
    • 배열class는 Cloneable과 serializable 인터페이스가 구현되어 있다. -> Object member 모두 상속

    • clone을 할 때도 공변을 통한 type변환이 이미 이루어졌기 때문에 형변환을 할 필요가 없다.

    • 결론 배열 복사 방법이 3가지가 존재
      1. for문으로 노가다 
      2. clone()으로 배열 복사
      3. Sytem.arraycopy()로 배열 복사