Java의 Collection은 기본적으로 얕은 복사(shallow copy)는 제공하나 깊은 복사기능은 제공하지 않는다. 즉, 원본 목록과 복제된 목록에 저장된 객체가 동일하고 그렇다는것은 Java 힙 공간에서 동일한 메모리 위치를 가리켜 문제가 될 수 있다.
예를 들면 직원 정보가 들어있는 ArrayList의 생성자를 사용하여 복제시엔 직원 객체 정보는 그대로 있으므로 복제된 list의 객체를 수정시에 같이 영향을 받게 된다.
아래 예제이다.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CollectionCloningTest {
public static void main(String args[]) {
List<Employee> org = new ArrayList<>();
org.add(new Employee("MA", "Manager"));
org.add(new Employee("AH", "Developer"));
org.add(new Employee("JM", "Developer"));
// 생성자를 통해 copy list 생성.
List<Employee> copy = new ArrayList<>(org);
System.out.println("ORG LIST : " + org);
System.out.println("COPY LIST : " + copy);
Iterator<Employee> itr = org.iterator();
while (itr.hasNext()) {
itr.next().setDesignation("Specialist");
}
//org list 에 해당하는 값을 바꿨으나 copy list 에 들어있는 객체값도 같이 바뀌게 된다.
System.out.println("ORG LIST : " + org);
System.out.println("COPY LIST : " + copy);
}
}
class Employee {
private String name;
private String designation;
public Employee(String name, String designation) {
this.name = name;
this.designation = designation;
}
public String getDesignation() {
return designation;
}
public void setDesignation(String designation) {
this.designation = designation;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return String.format("%s: %s", name, designation);
}
}
- 출력값 : org list 에 해당하는 값을 바꿨으나 copy list 에 들어있는 객체값도 같이 바뀌게 된다.
ORG LIST : [MA: Manager, AH: Developer, JM: Developer]
COPY LIST : [MA: Manager, AH: Developer, JM: Developer]
ORG LIST : [MA: Specialist, AH: Specialist, JM: Specialist]
COPY LIST : [MA: Specialist, AH: Specialist, JM: Specialist]
복제본이 단순 복사이고 힙의 동일한 Employee 개체를 가리 키기 때문에 원본 컬렉션의 Employee 개체 ( “Specialist”로 변경된 지정)가 복제 된 컬렉션에도 반영되어 있음을 확인할 수 있다.
이 문제를 해결하려면 Collection을 반복할 시 Employee 객체를 깊게 복제해야하며 그 전에 Employee 객체의 복제 메소드를 재정의해서 넣어야한다.
해결방법
1) 객체 복사를 할 수 있도록 하는 인터페이스 구현 (Employee가 Cloneable 인터페이스를 구현하도록 한다.) 2) Employee 클래스에 clone() 메소드 추가
/** 1. 기존 Employee 클래스에 Cloneable을 구현하도록 추가. 2. clone 메소드 구현. */
class Employee implements Cloneable {
private String name;
private String designation;
public Employee(String name, String designation) {
this.name = name;
this.designation = designation;
}
public String getDesignation() {
return designation;
}
public void setDesignation(String designation) {
this.designation = designation;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return String.format("%s: %s", name, designation);
}
@Override
public Employee clone() {
Employee clone = null;
try {
clone = (Employee) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
return clone;
}
}
생성자를 사용하여 List를 복사하는 대신 다음 코드를 사용하여 Java에서 콜렉션 전체 복사
public class CollectionCloningTest {
public static void main(String args[]) { // deep cloning Collection in Java
....위와 같은 로직...
//deep copy 로직
List<Employee> deepCopy = new ArrayList<>(org.size());
Iterator<Employee> iterator = org.iterator();
while (iterator.hasNext()) {
deepCopy.add(iterator.next().clone());
}
//deepCopy list 에 해당하는 값을 바꿨으나 org list 에 있는 객체값이 영향을 받지 않는다..
deepCopy.get(0).setDesignation("Expert");
System.out.println("ORG LIST : " + org);
System.out.println("DEEP COPY LIST : " + deepCopy);
}
}
참고자료
http://jmlim.github.io/java/2018/12/13/java-deep-copy/