책은 방어적 복사의 중요성에 대해 이야기한다.
방어적 복사에 앞서 깊은 복사와 얕은 복사를 알고 가야한다.
깊은 복사
객체 복사 시, 해당 객체와 인스턴스 변수까지 복사한다. 전부 복사해서 새 주소에 담는 ( 주소값은 달라진다)
즉 원본과 복사 객체는 독립적인 별개의 객체이되, 내부 정보는 동일한 상태로 초기화 하는 것.
@Override
public Object clone() throws CloneNotSupportedException{
return super.clone();
}
보통 위의 clone 메소드로 복사 하게된다.
public static void main(String[] args) {
try {
// 원본 객체 생성
Person originalPerson = new Person("Alice", 30);
// 깊은 복사 수행
Person copiedPerson = (Person) originalPerson.clone();
// 원본 객체 정보 출력
System.out.println("원본 객체: " + originalPerson.getName() + ", " + originalPerson.getAge());
// 복사된 객체 정보 출력
System.out.println("복사된 객체: " + copiedPerson.getName() + ", " + copiedPerson.getAge());
// 원본 객체의 이름을 변경
originalPerson.setName("Bob");
// 원본 객체 정보 출력
System.out.println("원본 객체: " + originalPerson.getName() + ", " + originalPerson.getAge());
// 복사된 객체 정보 출력
System.out.println("복사된 객체: " + copiedPerson.getName() + ", " + copiedPerson.getAge());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
얕은 복사 (Shallow copy)
객체 복사 시, 내부 객체를 새로 생성하지 않고 단순히 참조만 복사하는 방식이다. ( 주소값을 복사)
따라서 원본 객체와 복사본은 같은 하위 객체를 참조한다
public class ShallowCopyExample {
public static void main(String[] args) {
// 원본 객체 생성
Person originalPerson = new Person("Alice", 30);
// 얕은 복사 수행
Person copiedPerson = originalPerson;
// 원본 객체 정보 출력
System.out.println("원본 객체: " + originalPerson.getName() + ", " + originalPerson.getAge());
// 복사된 객체 정보 출력
System.out.println("복사된 객체: " + copiedPerson.getName() + ", " + copiedPerson.getAge());
// 원본 객체의 이름을 변경
originalPerson.setName("Bob");
// 원본 객체 정보 출력
System.out.println("원본 객체: " + originalPerson.getName() + ", " + originalPerson.getAge());
// 복사된 객체 정보 출력
System.out.println("복사된 객체: " + copiedPerson.getName() + ", " + copiedPerson.getAge());
}
}
originalPerson 의 정보가 바뀐다면 copiedPerson의 정보도 변경된다.
이는 복사본을 변경하면 원본 객체도 영향을 미치게된다는 것이다.
방어적 복사
원본을 복사하는 새로운 객체 생성시, 원본과는 다른 새로운 객체를 만들되, 객체 내부의 요소들은 원본과 동일한 주소를 참조한다.
class Address {
private String city;
private String street;
public Address(String city, String street) {
this.city = city;
this.street = street;
}
// Getter, Setter 메서드 생략
@Override
public String toString() {
return city + ", " + street;
}
}
class Person {
private String name;
private int age;
private Address address;
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
// Getter, Setter 메서드 생략
@Override
public String toString() {
return name + ", " + age + ", " + address.toString();
}
}
public class DefensiveCopyExample {
public static void main(String[] args) {
// 원본 객체 생성
Address originalAddress = new Address("New York", "123 Main Street");
Person originalPerson = new Person("Alice", 30, originalAddress);
// 방어적 복사 수행
Address copiedAddress = new Address(originalAddress.getCity(), originalAddress.getStreet());
Person copiedPerson = new Person(originalPerson.getName(), originalPerson.getAge(), copiedAddress);
// 원본 객체 정보 출력
System.out.println("원본 객체: " + originalPerson);
// 복사된 객체 정보 출력
System.out.println("복사된 객체: " + copiedPerson);
// 원본 객체의 주소 변경
originalAddress.setCity("Los Angeles");
// 원본 객체 정보 출력
System.out.println("원본 객체: " + originalPerson);
// 복사된 객체 정보 출력
System.out.println("복사된 객체: " + copiedPerson);
}
}
Person 은 Address 객체를 포함하는데 복사 시에는 originalAddress를 그대로 카피 하되, 방어적 복사를 통해 원본과 다른 새로운 객체로 Address를 담게 된다. 따라서 참조했던 원본 Address의 변경사항이 복사된 객체의 Address에는 반영되지 않는다.
왜 방어적 복사를 해야하는가?
방어적복사에는성능저하가따르고,또항상쓸수있는것도아니다.
그럼에도,
1. 불변 객체 유지
2. 보안
3. 다중스레드 환경 및 멀티 스레드 환경에서 값이 변경 되는 것을 막기 위해
와 같은 이유로 사용된다.
참고 , 이펙티브 자바 3판, 50 아이템
'Effective Java' 카테고리의 다른 글
[아이템 18] 상속보다는 컴포지션을 사용하라 (0) | 2023.04.06 |
---|---|
[아이템 13] clone 재정의는 주의해서 진행하라 (0) | 2023.02.15 |
[ 아이템 12 ] toString (0) | 2023.02.14 |