-> 블로그 이전

[Java] 얕은 복사 / 깊은 복사

2022. 2. 17. 15:36Language`/Java

얕은 복사 (Shallow Copy)

- "주소값"을 복사한다

  • 따라서, 실제 참조하고 있는 값이 동일하다

- "="연산자는 얕은 복사를 수행

 

>> 따라서, 한 쪽에서 Update가 되면, 주소값을 참조하고 있는 다른쪽도 같이 Update된다

import java.util.*;
public class test3{
    public static void main(String[] args) {
        int [] a = {1, 2, 3, 4, 5};
        int [] b = a; // shallow copy

        a[0] = 5;
        b[4] = 3;
        /*
        예상
        a : 5 2 3 4 3
        b : 5 2 3 4 3
         */

        System.out.println("Array a : " + Arrays.toString(a));
        System.out.println("Array b : " + Arrays.toString(b));
    }
}
  • '='연산자를 통해서 Array b가 Array a를 Copy했기 때문에 a를 Update하면 b도 Update될 것이라고 예측했다

  • 왜냐면 '='연산자를 통해서 b는 a의 "주소값"을 참조하기 때문에 어느 쪽에서 Update되든간에 같이 Update된다

 

깊은 복사 (Deep Copy)

- "실제값"새로운 메모리 공간에 복사한다

  • 따라서, 실제값이 서로 다르다

- 새로운 메모리 공간을 할당하기 때문에, 메모리 소요가 크고 속도도 느리다

import java.util.*;
public class test3{
    public static void main(String[] args) {
        int [] a = {1, 2, 3, 4, 5};
        int [] b = a.clone(); // deep copy

        a[0] = 5;
        b[4] = 3;
        /*
        예상
        a : 5 2 3 4 5
        b : 1 2 3 4 3
         */

        System.out.println("Array a : " + Arrays.toString(a));
        System.out.println("Array b : " + Arrays.toString(b));
    }
}


1차원 배열

얕은 복사

  • 단순히 '='연산을 통해서 얕은 복사 가능
import java.util.*;
public class test3{
    public static void main(String[] args) {
        int [] a = {1, 2, 3, 4, 5};
        int [] b = a; // shallow copy

        a[0] = 5;
        b[4] = 3;
        /*
        예상
        a : 5 2 3 4 3
        b : 5 2 3 4 3
         */

        System.out.println("Array a : " + Arrays.toString(a));
        System.out.println("Array b : " + Arrays.toString(b));
    }
}

 

깊은 복사

  • clone()을 통해서 깊은 복사
import java.util.*;
public class test3{
    public static void main(String[] args) {
        int [] a = {1, 2, 3, 4, 5};
        int [] b = a.clone(); // deep copy

        a[0] = 5;
        b[4] = 3;
        /*
        예상
        a : 5 2 3 4 5
        b : 1 2 3 4 3
         */

        System.out.println("Array a : " + Arrays.toString(a));
        System.out.println("Array b : " + Arrays.toString(b));
    }
}

1차원 객체 배열

얕은 복사 

- '='

 

깊은 복사 

- clone() / System.arraycopy()

 

 

※ System.arraycopy(src, srcPos, dest, destPos, length)

  • src : 원본
  • srcPos : 원본의 어디서부터 읽을건가
  • dest : 복사본
  • destPos : 복사본에 어디부터 원본을 저장할까
  • length : 원본 > 복사본으로 Data 얼마나 읽을까
import java.util.*;
public class test3{
    public static void main(String[] args) {
        String [] a = {
                "abcde",
                "edcba"
        };
        String [] b = a; // shallow copy
        String [] c = a.clone(); // deep copy

        String [] d = new String[2];
        System.arraycopy(a, 0, d, 0, a.length); // deep copy

        a[0] = "ABCDE";
        b[1] = "EDCBA";
        /*
        예상
        a : "ABCDE", "EDCBA"
        b : "ABCDE", "EDCBA"
        c : "abcde", "edcba"
        d : "abcde", "edcba"
         */
        System.out.println("Array a : " + Arrays.toString(a));
        System.out.println("Array b : " + Arrays.toString(b));
        System.out.println("Array c : " + Arrays.toString(c));
        System.out.println("Array d : " + Arrays.toString(d));
    }
}


2차원 배열

- 2차원 배열의 각 row에 대한 주소값을 보유하고 있다

  • 따라서, clone()을 통해서도 얕은 복사가 된다
import java.util.*;
public class test3{
    public static void main(String[] args) {
        int [][] a = {
                {1, 2, 3},
                {4, 5, 6}
        };
        int [][] b = a; // shallow copy
        int [][] c = a.clone(); // shallow copy

        a[0][0] = 10;
        a[1][2] = 100;
        /*
        예상
        a : {10, 2, 3} / {4, 5, 100}
        b : {10, 2, 3} / {4, 5, 100}
        c : {10, 2, 3} / {4, 5, 100}
         */
        System.out.println("Array a : " + Arrays.deepToString(a));
        System.out.println("Array b : " + Arrays.deepToString(b));
        System.out.println("Array c : " + Arrays.deepToString(c));
    }
}

 

2차원 배열 깊은복사 방법

1. 2중 반복문을 통해서 하나하나 복사

2. 반복문 + System.arraycopy() 사용

import java.util.*;
public class test3{
    public static void main(String[] args) {
        int [][] a = {
                {1, 2, 3},
                {4, 5, 6}
        };
        int [][] b = new int[a.length][a[0].length];
        int [][] c = new int[a.length][a[0].length];

        // a->b :: deep copy by 'double loop'
        for(int i=0; i<a.length; i++){
            for(int j=0; j<a[i].length; j++)
                b[i][j] = a[i][j];
        }

        // a->c :: deep copy by (loop + System.arraycopy())
        for(int i=0; i<a.length; i++){
            System.arraycopy(a[i], 0, c[i], 0, a[i].length);
        }

        a[0][0] = 10;
        a[1][2] = 100;
        /*
        예상
        a : {10, 2, 3} / {4, 5, 100}
        b : {1, 2, 3} / {4, 5, 6}
        c : {1, 2, 3} / {4, 5, 6}
         */
        System.out.println("Array a : " + Arrays.deepToString(a));
        System.out.println("Array b : " + Arrays.deepToString(b));
        System.out.println("Array c : " + Arrays.deepToString(c));
    }
}


2차원 객체 배열

- 2차원 객체 배열의 경우

  • System.arraycopy()나 clone()을 통해서 깊은 복사를 할 수 없다
  • 2차원 객체 배열직접 2중 for문을 통해서 복사를 해야 한다

clone?

- 자바의 Object 클래스내에 존재하는 메소드

- 어떠한 객체를 복제(복사)하는 메소드

- "clone은 실제로는 Object 메소드가 아니기 때문에 모든 클래스를 복사할수는 없다" 

  • clone은 사실 "Cloneable" 인터페이스의 추상메소드이다
  • "Cloneable" 인터페이스가 구현된 클래스만 복사가 가능하다

- clone()을 통해서 깊은 복사가 되지 않는다면 직접 Cloneable 인터페이스의 clone() 추상메소드를 구현해야 한다

 

protected native Object clone() throws CloneNotSupportedException;
  • 접근 제한자 protected : 복사하려는 객체 외부에서 접근 X / 객체 안에서 clone()을 super 키워드로 접근해야 한다
  • native : 자바 언어가 아닌 JNI를 사용해서 구현되었다
  • CloneNotSupportedException : java.lang.Cloneable 인터페이스를 구현하지 않으면 예외 던지기
    • checked exception이기 때문에, 호출하는 쪽에서 반드시 예외처리를 해야한다

 

class Person implements Cloneable{
    private String name;
    private int age;
    
    Person(String name, int age){
        this.name = name;
        this.age = age;
    }

    @Override
    public Person clone(){
        try{
            return (Person) super.clone();
        }catch(CloneNotSupportedException e){
            return null;
        }
    }
    String getName(){
        return name;
    }
    int getAge(){
        return age;
    }
    
    void setName(String name){
        this.name = name;
    }
    void setAge(int age){
        this.age = age;
    }
}

 

Example :: Shallow Copy vs Deep Copy (by clone())

public class test3{
    public static void main(String[] args) {
        Person a = new Person("David", 30);
        Person b = a; // shallow copy
        Person c = a.clone(); // deep copy

        a.setAge(50);

        System.out.println("a : " + print(a));
        System.out.println("b : " + print(b));
        System.out.println("c : " + print(c));
    }

    static String print(Person p){
        return "Hashcode : " + p.hashCode() + " / Name, Age : " + p.getName() + ", " + p.getAge();
    }
}

  • a-b : shallow copy이므로, 객체의 주소값을 서로 공유 / 값을 변경하면 서로 같이 Update
  • a-b : deep copy이므로, 서로 다른 주소값을 가지고 있고, 값을 변경해도 서로 아무런 영향이 없다

 

>> 모든 멤버 변수가 primitive 타입 or immutable하다면 @Override한 clone()을 사용해도 깊은 복사가 된다

  • 하지만 멤버 변수가 mutable하다면, 깊은 복사가 이루어지지 않고, 얕은 복사가 이루어진다

 

Mutable variable

import java.util.*;

class Address{
    private int num;
    private String city;
    Address(int num, String city){
        this.num = num;
        this.city = city;
    }
    int getNum(){
        return num;
    }
    String getCity(){
        return city;
    }
    void setNum(int num){
        this.num = num;
    }
    void setCity(String city){
        this.city = city;
    }
}

class Person implements Cloneable{
    private String name;
    private int age;
    private Address adr;

    Person(String name, int age, Address adr){
        this.name = name;
        this.age = age;
        this.adr = adr;
    }

    @Override
    public Person clone(){
        try{
            return (Person) super.clone();
        }catch(CloneNotSupportedException e){
            return null;
        }
    }
    String getName(){
        return name;
    }
    int getAge(){
        return age;
    }
    Address getAdr(){
        return adr;
    }

    void setName(String name){
        this.name = name;
    }
    void setAge(int age){
        this.age = age;
    }
}
  • Person내에 Mutable Variable인 Address를 추가해봤다
public class test3{
    public static void main(String[] args) {
        Person a = new Person("David", 30, new Address(10101, "Seoul"));
        Person b = a; // shallow copy
        Person c = a.clone(); // deep copy

        a.getAdr().setCity("Busan");

        System.out.println("a >> Name, Age : " + a.getName() + ", " + a.getAge() + " : " + a.getAdr().getCity());
        System.out.println("b >> Name, Age : " + b.getName() + ", " + b.getAge() + " : " + b.getAdr().getCity());
        System.out.println("c >> Name, Age : " + c.getName() + ", " + c.getAge() + " : " + c.getAdr().getCity());
    }
}

  • a.getAdr().setCity("Busan")을 통해서 a의 city를 변경했는데 
    • b는 얕은복사니까 당연히 변경되었고
    • c는 clone()을 통해서 복사를 하였는데도 변경이 되었다

 

>> 이럴 경우 직접 Mutable Variable을 포함한 깊은 복사를 구현해야 한다

 

  • Mutable Variable 내에 clone()을 구현하고 해당 변수를 사용하는 객체에서도 clone()을 구현해야 한다
@Override
public Address clone(){
    try{
        return (Address) super.clone();
    }catch(CloneNotSupportedException e){
        return null;
    }
}
    
@Override
public Person clone(){
    try{
        Address c_adr = adr.clone(); // 일단 참조변수부터 clone()
        Person p = (Person) super.clone();
        p.setAddress(c_adr);
        return p;
    }catch(CloneNotSupportedException e){
        return null;
    }
}

  • 이제서야 clone()을 통한 깊은 복사가 완료되었다

반드시 clone()?

  • Cloneable 인터페이스를 구현하는 것보다, 복사 생성자 or 복사 메소드를 정의하는게 더 좋은 방법이라고 생각한다