class Agent { // 요원 클래스
String name; // 이름
String nationality; // 국적
public Agent(String name, String nationality) {
this.name = name;
this.nationality = nationality;
}
}
위와 같이 요원의 이름과 국적을 멤버변수로 갖는 Agent 클래스가 있다.
요원 객체를 하나 생성하고, 그 객체를 복사해서 이름,국적만 바꾸려고 한다. 코드는 다음과 같다.
Agent jett = new Agent("Jett", "Korea");
Agent chamber = jett;
chamber.name = "Chamber";
chamber.nationality = "France";
이제 요원 객체 2개가 잘 생성되었는지 확인하기 위해 출력을 해보면 결과는 다음과 같다.
System.out.println(jett.name + " " + jett.nationality); // 출력결과 : Chamber France
System.out.println(chamber.name + " " + chamber.nationality); // 출력결과 : Chamber France
의도와 다르게, jett 객체의 정보도 같이 변경된 것을 확인할 수 있다.
이유는 바로, 얕은 복사를 했기 때문이다.
그렇다면, 얕은 복사는 무엇이고 의도한 결과를 도출하기 위해서는 어떻게 해야될까?
얕은 복사(Shallow Copy) : '주소값'을 복사한다.
얕은 복사는 주소값을 복사하기 때문에 실제로 참조하고 있는 값은 같다.
위에서 본 상황이 바로 얕은 복사의 예시이다.
즉, jett 객체와 chamber 객체는 같은 값을 참조하고 있었고, 그 값을 변경했기 때문에 jett의 이름과 국적도 함께 변경된 것처럼 보인 것이다.
우리가 원하는대로 복사한 객체에서 수정한 내용이 원래 객체에도 적용되는 것을 막기 위해서는 깊은 복사를 해야 된다.
깊은 복사(Deep Copy) : '실제값'을 새로운 메모리 공간에 복사한다.
깊은 복사를 구현하는 방법은 다음과 같다.
class Agent { // 요원 클래스
String name; // 이름
String nationality; // 국적
public Agent(){
}
public Agent(String name, String nationality) {
this.name = name;
this.nationality = nationality;
}
//복사 생성자
public Agent(Agent origianl) {
this.name = origianl.name;
this.nationality = origianl.nationality;
}
//복사 팩터리
public static Agent copy(Agent original) {
Agent copy = new Agent();
copy.name = original.name;
copy.nationality = original.nationality;
return copy;
}
}
복사 생성자와 복사 팩터리는 위 코드와 같이 구현한다.
아래 코드는 복사 생성자와 복사 팩터리로 복사한 객체에 대한 수정이 원래 객체에 영향을 미치는지 확인하는 코드이다.
Agent jett = new Agent("Jett", "Korea");
Agent chamber = new Agent(jett); // 복사 생성자로 복사
Agent phoenix = Agent.copy(jett); // 복사 팩터리로 복사
//복사 생성자로 복사한 객체 수정
chamber.name = "Chamber";
chamber.nationality = "France";
//복사 팩터리로 복사한 객체 수정
phoenix.name = "Phoenix";
phoenix.nationality = "UK";
System.out.println(jett.name + " " + jett.nationality); // 출력결과 : Jett Korea
System.out.println(chamber.name + " " + chamber.nationality); // 출력결과 : Chamber France
System.out.println(phoenix.name + " " + phoenix.nationality); // 출력결과 : Phoenix UK
원래 객체에 영향을 미치지 않고 잘 수정된 것을 확인할 수 있다.
Agent jett = new Agent("Jett", "Korea");
Agent chamber = new Agent(); // 객체 생성
// jett 객체에 접근하여 직접 복사
chamber.name = jett.name;
chamber.nationality = jett.nationality;
System.out.println(jett.name + " " + jett.nationality); // 출력결과 : Jett Korea
System.out.println(chamber.name + " " + chamber.nationality); // 출력결과 : Jett Korea
chamber.name = "Phoenix";
chamber.nationality = "UK";
System.out.println(jett.name + " " + jett.nationality); // 출력결과 : Jett Korea
System.out.println(chamber.name + " " + chamber.nationality); // 출력결과 : Chamber France
이 방법은 객체에 접근해서 멤버변수 값을 직접 읽어온 뒤에 새로 만든 객체의 멤버변수에 할당하는 것이다.
class Agent implements Cloneable{ // Cloneable 인터페이스 구현
String name;
String nationality;
@Override
protected Agent clone() throws CloneNotSupportedException { // Object 클래스의 clone() 메소드 구현
return (Agent) super.clone();
}
public Agent(String name, String nationality) {
this.name = name;
this.nationality = nationality;
}
}
Agent 클래스에서 Cloenable 인터페이스를 상속받는다.
Cloneable 인터페이스를 구현하기 위해서는 Object 클래스의 clone() 메소드를 반드시 구현해야 된다.
public static void main(String[] args) throws CloneNotSupportedException{
Agent jett = new Agent("Jett", "Korea");
Agent chamber = jett.clone(); // clone() 메소드로 복사
// 복사 결과 확인
System.out.println(jett.name + " " + jett.nationality); // 출력결과 : Jett Korea
System.out.println(chamber.name + " " + chamber.nationality); // 출력결과 : Jett Korea
// 복사한 객체 수정
chamber.name = "Phoenix";
chamber.nationality = "UK";
// 수정 결과 확인
System.out.println(jett.name + " " + jett.nationality); // 출력결과 : Jett Korea
System.out.println(chamber.name + " " + chamber.nationality); // 출력결과 : Chamber France
}
clone() 메소드를 통해서 깊은 복사가 잘 된 것을 확인할 수 있다.
하지만, 이 방법은 final 인스턴스 또는 배열이 아닌 경우 추천하지 않는 방법이라고 한다.
1차원 배열의 경우에는 clone() 메소드를 통해서 쉽게 복사할 수 있다.
int[] arr = {1, 2, 3, 4};
int[] copy = arr.clone();
for (int x : copy) System.out.print(x + " "); // 출력결과 : 1 2 3 4
int[] arr = {1, 2, 3, 4};
int[] copy = new int[];
System.arraycopy(arr, 0, copy, 0, arr.length);
for (int x : copy) System.out.print(x + " "); // 출력결과 : 1 2 3 4
원래 배열, 원래 배열 시작 위치, 새 배열, 새 배열 시작 위치, 배열 길이 순서로 파라미터를 입력한다.
2차원 배열의 경우에는 clone() 메소드나 System.arraycopy를 통해 바로 복사할 수 없다.
int[][] arr = {{1, 2},
{3, 4}};
int[][] copy = new int[2][2];
for (int i=0; i<2; i++){
for (int j=0; j<2; j++) {
copy[i][j] = arr[i][j];
}
}
2중 for문을 순회하며 값을 하나씩 입력한다.
int[][] arr = {{1, 2},
{3, 4}};
int[][] copy = new int[2][2];
for (int i=0; i<2; i++){
System.arraycopy(arr,0,copy,0,arr.length);
}
System.arraycopy를 2차원 배열의 행 수만큼 실행한다.
https://zzang9ha.tistory.com/372 clone() 재정의에 대한 내용 언급