본문 바로가기

백엔드 기술/Java

얕은 복사 VS 깊은 복사

얕은 복사 (Swallow Copy)

원본 객체의 non-static 필드를 복사하여 새로운 객체로 생성하는 것.

새로운 객체를 생성할 때 원본 객체의 필드 타입으로 나뉜다.
1. 원본 객체가 non-primitive 타입의 필드를 가지고 있을 때.
2. 원본 객체가 primitive 타입의 필드를 가지고 있을 때.

*primitive 타입 : int, float, double, char, byte, short, long, boolean*

1. 원본 객체가
non-primitives 타입의 필드(변수)를 가질 경우,
원본 객체의 non-primitive 타입의 필드들을 복사하여 새로운 객체를 생성한다.
그 결과, 원본 객체얕은 복사의 결과인 새로운 객체동일한 non-primitive 타입 필드들을 가진다.

즉, 원본 객체 or 얕은 복사 결과 객체의 non-primitive 필드 중 변화가 생기면 그 값을 공유한다.

 

[예시]
#1. Person 클래스를 만들고, Person의 친구들을 나타내는 List<String> 필드를 생성하자.

class Person implements Cloneable {
    private String name;
    private final List<String> friends;

    public Person(String name, List<String> friends) {
        this.name = name;
        this.friends = friends;
    }

    public List<String> getFriends() {
        return friends;
    }

    @Override
    protected Person clone() throws CloneNotSupportedException {
        return (Person)super.clone();
    }
}

 

#2. Person 객체를 생성하고, clone 메서드를 사용해서 얕은 복사 객체를 생성해보자.

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        List<String> friendsList = new ArrayList<>();
        friendsList.add("Alice");
        friendsList.add("Bob");

        Person p1 = new Person("John", friendsList);
        Person p2 = p1.clone();
    }
}

p1 = 원본 Person 객체.
p2 = p1의 얕은 복사로 생성한 객체. (원본 객체의 non-static 필드를 복사하여 새로운 객체로 생성)

p1p2서로 다른 객체이다.
하지만 두 객체에 공통적으로 있는 List<String> friends 필드동일한 ArrayList 개체를 참조한다.
만약 두 객체 중 한 객체의 friends 필드에 변화가 생긴다면 다른 객체에도 변화가 적용된다.

[예시] 얕은 복사로 생성한 p1의 복사 객체인 p2의 friends 필드에 "David" 를 추가

p2.getFriends().add("David");

p1 객체의 friends 필드를 조회하면 p2와 동일하게 p1의 friends 필드에도 David이 포함된 걸 알 수 있다.

System.out.println("p1.getFriends() = " + p1.getFriends());

 

2. 원본 객체가 primitive 타입의 필드(변수)를 가질 경우
얕은 복사로 생성되는 새로운 객체원본 객체의 primitive 타입의 필드를 그대로 복사하여 생성된다.
하지만 원본 객체얕은 복사로 생성된 객체는 각 primitive 타입의 필드를 가지지만,
두 객체들은 primitive 타입의 필드들의 값을 공유하지 않는 독립적인 객체가 된다.

[예시]

class Person implements Cloneable {
    private String name;
    int age;
    private final List<String> friends;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        this.friends = new ArrayList<>();
    }
    
    public void addFriend(String friend) {
        friends.add(friend);
    }

    public List<String> getFriends() {
        return friends;
    }

    public int getAge() {
        return age;
    }

    @Override
    protected Person clone() {
        try {
            return (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException("Failed to clone Person Object", e);
        }
    }
}

위의 예제에서 Person 클래스에 int age는 primitive 타입의 필드이고, friends는 non-primitive 타입 필드.

[예시]

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("Alice",30);
        p1.addFriend("Bob");

        Person p2 = p1.clone();
        p2.addFriend("Charlie");

        System.out.println("P1 friends:  = " + p1.getFriends());
        System.out.println("P2 friends:  = " + p2.getFriends());

        p2.age = 35;
        System.out.println("P1 age:  = " + p1.getAge());
        System.out.println("P2 age:  = " + p2.getAge());

    }
}

1. Person 클래스의 객체 p1 을 생성.
2. p1의 non-primitive 필드인 friends에 "Bob"을 추가함.
3. p2의 non-primitive 필드인 friends에 "Charlie" 추가함.
3. p1을 대상으로 얕은 복사를 통해 p2 를 생성.
4. p2의 primitive 필드인 age를 별도로 설정.
4. p1, p2의 friends 필드age 필드를 출력.

[결과]

non-primitive 필드의 값은 p1, p2가 서로 공유하는 반면,
primitive 필드의 값은 p1, p2가 공유하지 않고 독립적인 값을 갖는다.

 

[정리]
#얕은 복사 
1. 원본 객체의 non-static 필드를 복사하여 새로운 객체에 할당.
2. 원본 객체가 non-primitive 필드를 가지고 있으면, non-primitive 필드를 복사하고 원본 객체와 공유 O.
3. 원본 객체가 primitive 필드를 가지고 있으면, 그 primitive 필드들을 복사하지만 원본 객체와 공유 X.

 

 

깊은 복사 (Deep Copy)

원본 객체의 모든 필드 (static or non-static / primitive or non-primitive 상관없이)를 복사하여
원본 객체와 완전히 독립된 새로운 객체를 생성하고 할당한다.

 

[예제]

public class Person{
    private String name;
    private List<String> friends;
    
    public Person(String name, List<String> friends) {
        this.name = name;
        this.friends = friends;
    }
    public Person deepCopy() {
        String newName = this.name;
        
        List<String> newFriends = new ArrayList<>();
        for(String friend : this.friends) {
            newFriends.add(friend);
        }
        return new Person(newName, newFriends);
    }
    public void addFriend(String friend){
        friends.add(friend);
    }
    public String getName(){
        return name;
    }
    public List<String> getFriends() {
        return friends;
    }
}

Person 클래스 타입의 deepCopy() 메서드 생성.
이 메서드는 원본 객체의 primitive, non-primitive 필드들을 복사하여 새로운 Person 객체를 생성한다.

[deepCopy() 메서드 설명]

1. Person 클래스의 non-primitive 필드 String name 복사.

String newName = this.name
> 우항의 this.name은 Person 클래스의 non-primitive 필드 String name 을 나타낸다.
String 은 불변 객체 타입이므로 새로운 String 객체를 생성해 기존의 String 객체 값을 할당할 수 있다.

2. Person 클래스의 non-primitive 필드 List<String> friends 복사.

A. 새로운 List<String> newFriends = new ArrayList<>(); 생성.
B. for(String friend : this.friends) {
            newFriends.add(friend);
        } 
for()문 우항의 this.friends 는 Person 클래스의 non-primitive 필드 List<String> friends 를 나타낸다.
즉 우항의 friends 로 부터 하나씩 꺼내서 String friend 객체에 담는다.
C. 새로 생성한 newFriends List 객체에 friend 객체를 추가한다.

3. Person 클래스의 non-primitive 필드 2개 String name, List<String> friends를 복사한
String newName, List<String> newFriends 를 사용한 새로운 Person 객체를 반환. => 깊은 복사

[예제]

public class Main {
    public static void main(String[] args) {
        // 깊은 복사 예제
        List<String> friendsList = new ArrayList<>();
        friendsList.add("Bob");
        Person p1 = new Person("Alice", friendsList);
        Person p2 = p1.deepCopy();

        p2.addFriend("Charlie");

        System.out.println("p1 friends:  = " + p1.getFriends());
        System.out.println("p2 friends:  = " + p2.getFriends());
    }
}

1. Person 클래스의 non-primitive 필드 List<String> 객체 생성하고 "Bob" 추가.
2. Person 객체 p1 생성.
3. Person p1 객체의 깊은 복사로 p2 객체 생성.
4. p2 객체의 List<String>객체에 "Charlie" 추가
5. p1, p2의 List<String> 객체 출력.

[결과]

즉, 원본 객체 p1p1의 깊은 복사 객체 p2필드의 값을 공유하지 않는다.

[정리]
#얕은 복사 
1. 원본 객체의 non-static 필드를 복사하여 새로운 객체에 할당.
2. 원본 객체가 non-primitive 필드를 가지고 있으면, non-primitive 필드를 복사하고 원본 객체와 공유 O.
3. 원본 객체가 primitive 필드를 가지고 있으면, 그 primitive 필드들을 복사하지만 원본 객체와 공유 X.


#깊은 복사

1. 원본 객체의 모든 필드 (non-static/static, primitive/non-primitive 상관없이)를 복사하여
새로운 객체를 생성하고 할당함.
2. 새롭게 생성된 깊은 복사의 객체원본 객체같은 필드들을 가지고 있지만 필드 값을 공유하지 않음

'백엔드 기술 > Java' 카테고리의 다른 글

자료구조 - Stack, Queue 차이점  (0) 2023.05.15
Synchronized 키워드의 이해  (0) 2023.04.29
Optional<T> 클래스란 ?  (0) 2023.04.21
Multi-Thread & Multi Process  (0) 2023.04.20
SingleTon 디자인 패턴  (0) 2023.04.20