❐ Description
프리코스를 진행하면서 Immutable 객체를 복사해서 사용해야 할 일이 있었고,
작업 과정에서 알게된 내용을 정리하고 옛날에 공부했던 내용을 복습할까 한다.
❐ Shallow & Deep
1. Shallow
얕은 복사는 객체를 복사할 때, 객체의 필드 값만 복사하는 방식이다. 즉, 기본 타입(예: int, double 등)은
값 자체를 복사하지만, 참조 타입(예: 객체, 배열 등)은 참조 주소만 복사한다.
따라서 얕은 복사를 하면 복사된 객체와 원본 객체가 동일한 참조 필드를 공유하게 된다.
만약 복사된 객체나 원본 객체에서 참조 타입 필드가 가리키는 객체를 변경하면, 다른 객체도 영향을 받는다.
Arrays.copyOf() 메소드는 shallow copy를 수행한다.
2. Deep
깊은 복사는 객체를 복사할 때, 객체 내부에 있는 모든 참조 타입 필드에 대해서도 새롭게 복사하는 방식이다.
즉, 참조 타입 필드에 대한 새로운 객체를 생성하고, 그 객체의 값도 복사한다.
따라서 깊은 복사를 하면 원본 객체와 복사된 객체가 독립적으로 존재하게 된다. 원본 객체나 복사된 객체 중
하나를 변경하더라도, 다른 객체는 영향을 받지 않는다.
❐ Conversation with Effective Java author, Josh Bloch
Effective 자바 저자 Josh Bloch의 의견이 담긴 아티클이다.
그 중에서 Copy Constructor vs Cloning에 대한 내용을 요약해봤다.
요약내용📝
Josh bloch는 자신의 책에서 clone() 메서드와 Cloneable 인터페이스가 설계적으로 문제가 많다고 설명한다.
가장 큰 문제는 Cloneable 인터페이스에 clone() 메서드가 없다는 점이다. 이는 Cloneable을 구현한다고 해서
객체를 다룰 수 있는 방법을 제공하지 않으며, 단시 내부적으로 super.clone()을 호출하면 Object의 clone()
메서드가 호출되어 원본 필드 복사본을 반화한다는 의미만 가진다.
이로 인해 다형적으로 clone() 작업을 수행할 수 없고, 배열의 요소들을 모두 복사하는 것도 불가능하다.
또한 clone() 메서드는 필드 복사에 기반하고 있어 얕은 복사만 수행되며, 깊은 복사가 이루어지지 않으면
복사된 객체가 원본 객체와 상태를 공유하게 된다. 이로 인해 하나의 객체를 수정하면 다른 객체도 함께
변하게 되어 예기치 않은 문제가 발생할 수 있다.
이러한 이유로 블로흐는 Cloneable 대신 복사 생성자(copy constructor)를 사용할 것을 권장한다.
복사 생성자를 사용하면 객체의 표현을 다르게 만들 수 있고, LinkedList를 ArrayList로 복사하는 것과 같은
작업도 가능하다고 설명합니다.
결론적으로, 저자는 Cloneable이 많은 문제를 가지고 있으며 대부분의 경우 사용하지 말아야 한다고 주장한다.
clone()은 배열을 복사할 때는 유용하지만, 일반적인 객체 복사에는 복사 생성자나 다른 방식을 사용하는 것이 더
좋다고 말한다.
결론은 일반 배열을 복사할 때는 clone() 메서드를 사용해도 무방하지만, 객체를 복사할 경우 별도의 복사 생성자
또는 다른 방식을 사용하는 것을 추천한다.
❐ 복사 생성자 (Copy constructor)
public class MyClone {
public static class Member implements Serializable {
public String name;
public int age;
public Member(String name, int age) {
this.name = name;
this.age = age;
}
public static Member clone(Member order) {
return new Member(order.name, order.age);
}
}
public static void main(String[] args) {
Member member = new Member("gilbert", 29);
Member cloned = Member.clone(member);
System.out.println(member); //MyClone$Member@2c7b84de
System.out.println(cloned); //MyClone$Member@3fee733d
}
}
매우 간단한 예제지만, deepCopy가 필요하면 이런식으로 복사 생성자를 만들면 될 듯하다.
❐ SerializationUtils from Appache commons lang3
Appache commons lang3를 사용하면 보다 간단하게 deep copy를 할 수 있다.
public class MyClone {
public static class Member implements Serializable {
public String name;
public int age;
public Member(String name, int age) {
this.name = name;
this.age = age;
}
}
public static void main(String[] args) {
Member member = new Member("gilbert", 29);
Member cloned = SerializationUtils.clone(member);
System.out.println(member); //MyClone$Member@6267c3bb
System.out.println(cloned); //MyClone$Member@77a567e1
}
}
util을 사용하면 byte 단위로 직렬화를 하여 새로운 인스턴스를 생성해준다. 그래서 Serializable을
꼭 implements 해줘야 하는 부분이 있다. 별도로 복사 생성자를 구현하지 않아도 되기 때문에 코드가
좀 더 간결해지는 이점이 있고, deep copy를 구현하는 수고를 덜 수 있어서 좋은 대안이 될 것 같다.
'우테코 7기 > 1주차' 카테고리의 다른 글
메소드 파라미터에 final 사용하기 (0) | 2024.10.22 |
---|---|
[JUnit5] 입력 값을 mockking할 수 있을까? (0) | 2024.10.21 |
[Refactoring] Pattern을 캐싱하자 (0) | 2024.10.19 |
Regex 뿌시기 (0) | 2024.10.17 |
1주차 회고 (0) | 2024.10.17 |