얕은 복사, 방어 복사, 깊은 복사

Yeseong31·2023년 9월 17일
0

객체를 복사하는 방법에는 여러 가지가 있다.
여기서는 얕은 복사, 방어 복사, 깊은 복사를 순차적으로 살펴보고,
이러한 방법들이 각각 어떻게 동작하는지, 어떻게 사용되는지를 알아보도록 한다.


얕은 복사

  • 얕은 복사(shallow copying)객체의 주소 값만을 복사하는 방법이다.
  • 객체 내부에 다른 객체를 참조하는 또 다른 참조 값이 있더라도, 참조된 객체를 새로 복사하지는 않는다.
  • 얕은 복사를 사용하면 복사된 객체와 원본 객체는 같은 객체를 참조하게 된다.
  • 하나의 객체를 수정하면 다른 객체에도 영향을 미친다.

자바에서는 = 연산자로 얕은 복사를 할 수 있다.


얕은 복사 예시

public class Name {

    private final String value;

    public Name(String value) {
        this.value = value;
    }

    public static Name of(String value) {
        return new Name(value);
    }

    public String getValue() {
        return value;
    }
}
public class Friend {

    public Name name;

    public Friend(Name name) {
        this.name = name;
    }

    public Name getName() {
        return name;
    }

    public void setName(Name name) {
        this.name = name;
    }
}
public class Person {

    private Name name;
    private final List<Friend> friends;

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

    public Name getName() {
        return name;
    }

    public void setName(Name name) {
        this.name = name;
    }

    public List<Friend> getFriends() {
        return friends;
    }
}
public class Main {

    public static void main(String[] args) {

        Person personA = new Person(
				Name.of("nameA"), Collections.emptyList());

        Person personB = personA;                    // 얕은 복사 수행

        System.out.println(personB == personA);      // true

        System.out.println("=====[BEFORE]=====");
        System.out.println(personA.name().value());  // nameA
        System.out.println(personB.name().value());  // nameA
        
        personB.changeName(Name.of("nameB"));

        System.out.println("=====[AFTER]=====");
        System.out.println(personA.name().value());  // nameB
        System.out.println(personB.name().value());  // nameB
    }
}



방어 복사

  • 방어 복사(defensive copying)객체의 주소를 복사하지 않고 객체의 내부 값을 참조하여 복사하는 방법이다.
  • 이렇게 복사한 복사본은 원본과 다른 객체를 참조하게 되지만, 해당 객체 내부에 있는 객체들은 원본과 동일한 주소를 참조한다.
  • 이 방법은 주로 다른 객체의 참조를 포함하는 객체를 복사할 때 사용한다.

방어 복사 예시

public class Person {

    private Name name;
    private final List<Friend> friends;

    public Person(Name name, List<Friend> friends) {
        this.name = name;
        this.friends = new ArrayList<>(friends);  // 방어적 복사
    }

    public Name getName() {
        return name;
    }

    public void setName(Name name) {
        this.name = name;
    }

    public List<Friend> friends() {
        return new ArrayList<>(friends);  // 방어적 복사
    }

    public void addFriend(Person person) {
        this.friends.add(person);
    }
}
public class Main {

    public static void main(String[] args) {

        List<Friend> friends = new ArrayList<>();
        Person person = new Person(Name.of("nameA"), friends);
        Friend friend1 = new Friend(Name.of("friend1"));
        Friend friend2 = new Friend(Name.of("friend2"));
        Friend friend3 = new Friend(Name.of("friend3"));

        System.out.println("=====[BEFORE]=====");
        System.out.println(
				String.format("친구의 수: %d", person.friends().size()));
        person.friends().forEach(t -> System.out.print(t.name().value()  + " "));
		// 친구의 수: 0

        friends.addAll(List.of(friend1, friend2, friend3));

        System.out.println("=====[AFTER]=====");
        System.out.println(
				String.format("친구의 수: %d", person.friends().size()));
        person.friends().forEach(t -> System.out.print(t.name().value()  + " "));
		// 친구의 수: 0
    }
}

방어 복사에서 복사본 자체는 원본과 다른 주소를 사용한다.
하지만 복사본 내부의 객체들은 원본 주소를 그대로 사용하므로 변경에 직접적인 영향이 생길 수 있다.
이 문제는 깊은 복사를 사용하여 해결할 수 있다.




깊은 복사

  • 깊은 복사(deep copying)객체의 모든 내부 상태를 완전히 복사하여 새로운 객체를 만드는 방법이다.
  • 이 방법은 원본 객체 내부의 모든 객체들도 재귀적으로 복사한다.
  • 복사된 객체와 원본 객체는 서로 다른 객체를 참조하기 때문에 하나의 객체를 수정하더라도 다른 객체에는 영향을 미치지 않는다.

깊은 복사 예시

public class Person {

    private Name name;
    private final List<Friend> friends;

    public Person(Name name, List<Friend> friends) {
        this.name = name;
        this.friends = friends.stream()
                .map(it -> new Friend(it.name()))
                .collect(Collectors.toList());  // 깊은 복사
    }

    public Name getName() {
        return name;
    }

    public List<Friend> friends() {
        return friends.stream()
                .map(it -> new Friend(it.name()))
                .collect(Collectors.toList());  // 깊은 복사
    }
}

깊은 복사를 수행하는 메서드

public class Copy {

    public static void main(String[] args) {
        
        String[] folderA = {"my_computer", "my_document", "recycleBin"};
    
        // 1. clone() 메서드 호출
        System.out.println("=== 1. clone() 메서드 호출 ===");
        String[] folderB = folderA.clone();
        
        folderB[0] = "new_computer";
    
        System.out.println("folderA = " + Arrays.toString(folderA));
        System.out.println("folderB = " + Arrays.toString(folderB));
        System.out.println();
    
        // 2. System.arrayCopy() 메서드 호출
        System.out.println("=== 2. System.arrayCopy() 메서드 호출 ===");
        String[] folderC = new String[folderA.length];
        System.arraycopy(folderA, 0, folderC, 0, folderA.length);
    
        folderC[0] = "new_computer";
    
        System.out.println("folderA = " + Arrays.toString(folderA));
        System.out.println("folderC = " + Arrays.toString(folderC));
        System.out.println();
    
        // 3. Arrays.copyOf() 메서드 호출
        System.out.println("=== 3. Arrays.copyOf() 메서드 호출 ===");
        String[] folderD = Arrays.copyOf(folderA, folderA.length);
        folderD[0] = "new_computer";
    
        System.out.println("folderA = " + Arrays.toString(folderA));
        System.out.println("folderD = " + Arrays.toString(folderD));
        System.out.println();
        
        // 4. Arrays.copyOfRange() 메서드 호출
        System.out.println("=== 4. Arrays.copyOfRange() 메서드 호출 ===");
        String[] folderE = Arrays.copyOfRange(folderA, 0, folderA.length);
        folderE[0] = "new_computer";
    
        System.out.println("folderA = " + Arrays.toString(folderA));
        System.out.println("folderE = " + Arrays.toString(folderE));
        System.out.println();
    }
}
  • clone() 메서드

  • System.arraycopy() 메서드

  • Arrays.copyOf() 메서드

  • Arrays.copyOfRange() 메서드

profile
역시 개발자는 알아야 할 게 많다.

0개의 댓글

관련 채용 정보