티스토리 뷰
방어적 복사에 대해서 공부하다가 얕은 복사, 깊은 복사, 방어적 복사 함께 비교하면 좋을거 같아서 비교글을 써보기로 했다.
얕은 복사 vs 깊은 복사 vs 방어적 복사
- 얕은 복사: 객체의 참조만 복사한다. 즉, 원본 객체와 복사본 객체가 같은 메모리 주소를 참조하게 된다.
- 깊은 복사: 객체와 내부에 포함된 참조 타입 필드(예: 배열, 필드)까지 모두 복사하여 원본과 독립적인 객체를 생성한다.
- 방어적 복사: 외부에서 전달된 객체를 변경할 수 없도록 원본 객체의 상태를 보호하기 위해 내부에서 새로운 객체를 생성한다.
💡 얕은 복사
객체의 참조만 복사한다.
객체의 내부 데이터를 복사하지 않고, 그 데이터가 저장된 메모리 주소값(참조)을 복사한다는 걸 의미한다.
얕은 복사로 생성된 객체는 겉보기엔 원본 객체와 별도의 객체로 보이지만,
실제 내부 필드가 참조하는 데이터는 같은 객체를 가리키게 된다.
따라서 원본이나 복사본 중 하나의 내부 데이터를 변경하면, 다른 쪽도 함께 영향을 받는 문제가 발생한다.
언제 사용할까?
- 메모리 절약이 중요한 경우
- 원본 데이터가 변경되지 않는다는 보장이 있을 때
class Person {
String name;
int[] scores;
Person(String name, int[] scores) {
this.name = name;
this.scores = scores;
}
}
// 얕은 복사 사용
Person original = new Person("coding", new int[]{90, 85});
Person shallowCopy = original; // 얕은 복사
// 동일한 배열을 참조 테스트
System.out.println(original.name); // coding
System.out.println(shallowCopy.name); // coding
System.out.println(original.scores == shallowCopy.scores); // true
// 복사본의 값을 바꾸면 원본의 값이 변한다.
shallowCopy.scores[0] = 100; // 복사본 배열 수정
System.out.println(Arrays.toString(original.scores)); // [100, 85] 원본 배열이 변했다!
original
과 shallowCopy
의 필드 scores
는 동일한 배열을 참조한다.
따라서 둘 중에 하나를 변경하면 모두 영향을 받는다.
💡 깊은 복사
깊은 복사는 객체와 내부 필드(특히 참조 타입 필드)까지 복사한다.
언제 사용할까?
- 원본 데이터와 복사본을 완전히 분리해야 하는 경우
- 원본 변경이 복사본에 영향을 미치면 안 되는 상황
class Person {
private String name;
private int[] scores; // 참조 타입인 배열 필드
// 생성자
Person(String name, int[] scores) {
this.name = name;
this.scores = scores;
}
// 깊은 복사를 위한 생성자
Person(Person other) { // 복사 생성자
this.name = other.name; // String은 불변 객체라 값 복사도 괜찮다
this.scores = other.scores.clone(); // clone()을 호출해서 참조 타입은 깊은 복사
}
public int[] getScores() {
return scores;
}
public String getName() {
return name;
}
}
깊은 복사를 해보자.
Person original = new Person("coding", new int[]{90, 85, 80});
// 깊은 복사 생성자 호출해서 새로운 객체를 생성한다.
Person deepCopy = new Person(original);
// 복사본의 배열 수정
deepCopy.getScores()[0] = 100;
// 원본의 배열과 복사본의 배열이 독립적이다.
System.out.println(Arrays.toString(original.getScores())); // [90, 85, 80] 원본 유지
System.out.println(Arrays.toString(deepCopy.getScores())); // [100, 85, 80] 복사본만 변경됨
original
객체의 배열 scores
는 변경되지 않았고, deepCopy
의 배열만 변경되었다.
Person
클래스의 복사 생성자에서 배열 복사를 scores.clone()
을 통해 새로운 배열을 생성하고,
해당 배열을 deepCopy
가 참조했기 때문이다.
💡 방어적 복사
방어적 복사는 원본 객체를 보호하기 위해 내부 필드를 복사한 값을 반환하거나,
외부에서 전달받은 값을 복사하여 사용하는 방식이다.
따라서 외부에서 객체를 변경할 수 없으므로 불변성이 보장되고, 값이 변하지 않음을 보장하므로 코드에 대한 신뢰성이 높아진다.
하지만 매번 복사해야 하므로 메모리와 성능 비용 발생한다.
언제 사용할까?
- 외부에서 전달된 데이터가 내부에서 변경되지 않도록 방지해야 할 때
- 불변 객체를 생성하거나 외부로 안전하게 반환할 때
public class Team {
private final List<Person> persons;
public Team(List<Person> persons) {
this.persons = new ArrayList<>(persons); // 방어적 복사
}
public List<Person> getPersons() {
return new ArrayList<>(persons); // 방어적 복사로 반환
}
}
원본 List<Person>
을 복사할 때, 원본 컬렉션 List<Person>
와의 참조를 끊고,
new ArrayList<>(persons)
으로 새로운 객체를 생성하여 복사하면 방어적 복사로 본다.
💡 Collection 객체의 복사 방법
데이터 구조 | 얕은 복사 | 깊은 복사 | 방어적 복사 |
List | List.copyOf() | 스트림으로 개별 요소 복사 | 반환 시 new ArrayList<>(list) 사용 |
Map | new HashMap<>(originalMap) |
내부 key-value 쌍의 깊은 복사 필요 | 반환 시 Collections.unmodifiableMap(map) 사용 |
'language > java' 카테고리의 다른 글
[JVM 시리즈] JVM 구성 요소와 JVM Warm-up 전략 (1) | 2024.11.18 |
---|---|
Collection 객체의 복사 방법 (0) | 2024.11.17 |
DTO 클래스 vs Record 어떤 경우에 사용할까? (1) | 2024.11.15 |
Static 메서드를 사용하는 이유 (3) | 2024.11.14 |
일급 컬렉션을 사용하는 이유 (불변 객체, 방어적 복사) (1) | 2024.11.13 |
- Total
- Today
- Yesterday
- Kotlin
- JPA
- addFilterBefore
- junit5
- 오블완
- 배열
- QueryDSL
- Git
- Spring Security
- 스프링오류
- jvm warm-up 전략
- port
- FetchJoin
- 티스토리챌린지
- ChatGPT
- MultipleBagFetchException
- 자바 어플리케이션 실행 과정
- Java
- MongoDB
- checkout
- dto 클래스 생성자
- 스프링 스케줄링
- 추상클래스
- redisson 분산락
- spring boot 3
- n+1
- Cannot construct instance of
- bucket4j
- Linux
- array
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |