[Java] 얕은 복사 / 깊은 복사
2022. 2. 17. 15:36ㆍLanguage`/Java
얕은 복사 (Shallow Copy)
- "주소값"을 복사한다
- 따라서, 실제 참조하고 있는 값이 동일하다
- "="연산자는 얕은 복사를 수행
>> 따라서, 한 쪽에서 Update가 되면, 주소값을 참조하고 있는 다른쪽도 같이 Update된다
import java.util.*;
public class test3{
public static void main(String[] args) {
int [] a = {1, 2, 3, 4, 5};
int [] b = a; // shallow copy
a[0] = 5;
b[4] = 3;
/*
예상
a : 5 2 3 4 3
b : 5 2 3 4 3
*/
System.out.println("Array a : " + Arrays.toString(a));
System.out.println("Array b : " + Arrays.toString(b));
}
}
- '='연산자를 통해서 Array b가 Array a를 Copy했기 때문에 a를 Update하면 b도 Update될 것이라고 예측했다
- 왜냐면 '='연산자를 통해서 b는 a의 "주소값"을 참조하기 때문에 어느 쪽에서 Update되든간에 같이 Update된다
깊은 복사 (Deep Copy)
- "실제값"을 새로운 메모리 공간에 복사한다
- 따라서, 실제값이 서로 다르다
- 새로운 메모리 공간을 할당하기 때문에, 메모리 소요가 크고 속도도 느리다
import java.util.*;
public class test3{
public static void main(String[] args) {
int [] a = {1, 2, 3, 4, 5};
int [] b = a.clone(); // deep copy
a[0] = 5;
b[4] = 3;
/*
예상
a : 5 2 3 4 5
b : 1 2 3 4 3
*/
System.out.println("Array a : " + Arrays.toString(a));
System.out.println("Array b : " + Arrays.toString(b));
}
}
1차원 배열
얕은 복사
- 단순히 '='연산을 통해서 얕은 복사 가능
import java.util.*;
public class test3{
public static void main(String[] args) {
int [] a = {1, 2, 3, 4, 5};
int [] b = a; // shallow copy
a[0] = 5;
b[4] = 3;
/*
예상
a : 5 2 3 4 3
b : 5 2 3 4 3
*/
System.out.println("Array a : " + Arrays.toString(a));
System.out.println("Array b : " + Arrays.toString(b));
}
}
깊은 복사
- clone()을 통해서 깊은 복사
import java.util.*;
public class test3{
public static void main(String[] args) {
int [] a = {1, 2, 3, 4, 5};
int [] b = a.clone(); // deep copy
a[0] = 5;
b[4] = 3;
/*
예상
a : 5 2 3 4 5
b : 1 2 3 4 3
*/
System.out.println("Array a : " + Arrays.toString(a));
System.out.println("Array b : " + Arrays.toString(b));
}
}
1차원 객체 배열
얕은 복사
- '='
깊은 복사
- clone() / System.arraycopy()
※ System.arraycopy(src, srcPos, dest, destPos, length)
- src : 원본
- srcPos : 원본의 어디서부터 읽을건가
- dest : 복사본
- destPos : 복사본에 어디부터 원본을 저장할까
- length : 원본 > 복사본으로 Data 얼마나 읽을까
import java.util.*;
public class test3{
public static void main(String[] args) {
String [] a = {
"abcde",
"edcba"
};
String [] b = a; // shallow copy
String [] c = a.clone(); // deep copy
String [] d = new String[2];
System.arraycopy(a, 0, d, 0, a.length); // deep copy
a[0] = "ABCDE";
b[1] = "EDCBA";
/*
예상
a : "ABCDE", "EDCBA"
b : "ABCDE", "EDCBA"
c : "abcde", "edcba"
d : "abcde", "edcba"
*/
System.out.println("Array a : " + Arrays.toString(a));
System.out.println("Array b : " + Arrays.toString(b));
System.out.println("Array c : " + Arrays.toString(c));
System.out.println("Array d : " + Arrays.toString(d));
}
}
2차원 배열
- 2차원 배열의 각 row에 대한 주소값을 보유하고 있다
- 따라서, clone()을 통해서도 얕은 복사가 된다
import java.util.*;
public class test3{
public static void main(String[] args) {
int [][] a = {
{1, 2, 3},
{4, 5, 6}
};
int [][] b = a; // shallow copy
int [][] c = a.clone(); // shallow copy
a[0][0] = 10;
a[1][2] = 100;
/*
예상
a : {10, 2, 3} / {4, 5, 100}
b : {10, 2, 3} / {4, 5, 100}
c : {10, 2, 3} / {4, 5, 100}
*/
System.out.println("Array a : " + Arrays.deepToString(a));
System.out.println("Array b : " + Arrays.deepToString(b));
System.out.println("Array c : " + Arrays.deepToString(c));
}
}
2차원 배열 깊은복사 방법
1. 2중 반복문을 통해서 하나하나 복사
2. 반복문 + System.arraycopy() 사용
import java.util.*;
public class test3{
public static void main(String[] args) {
int [][] a = {
{1, 2, 3},
{4, 5, 6}
};
int [][] b = new int[a.length][a[0].length];
int [][] c = new int[a.length][a[0].length];
// a->b :: deep copy by 'double loop'
for(int i=0; i<a.length; i++){
for(int j=0; j<a[i].length; j++)
b[i][j] = a[i][j];
}
// a->c :: deep copy by (loop + System.arraycopy())
for(int i=0; i<a.length; i++){
System.arraycopy(a[i], 0, c[i], 0, a[i].length);
}
a[0][0] = 10;
a[1][2] = 100;
/*
예상
a : {10, 2, 3} / {4, 5, 100}
b : {1, 2, 3} / {4, 5, 6}
c : {1, 2, 3} / {4, 5, 6}
*/
System.out.println("Array a : " + Arrays.deepToString(a));
System.out.println("Array b : " + Arrays.deepToString(b));
System.out.println("Array c : " + Arrays.deepToString(c));
}
}
2차원 객체 배열
- 2차원 객체 배열의 경우
- System.arraycopy()나 clone()을 통해서 깊은 복사를 할 수 없다
- 2차원 객체 배열은 직접 2중 for문을 통해서 복사를 해야 한다
clone?
- 자바의 Object 클래스내에 존재하는 메소드
- 어떠한 객체를 복제(복사)하는 메소드
- "clone은 실제로는 Object 메소드가 아니기 때문에 모든 클래스를 복사할수는 없다"
- clone은 사실 "Cloneable" 인터페이스의 추상메소드이다
- "Cloneable" 인터페이스가 구현된 클래스만 복사가 가능하다
- clone()을 통해서 깊은 복사가 되지 않는다면 직접 Cloneable 인터페이스의 clone() 추상메소드를 구현해야 한다
protected native Object clone() throws CloneNotSupportedException;
- 접근 제한자 protected : 복사하려는 객체 외부에서 접근 X / 객체 안에서 clone()을 super 키워드로 접근해야 한다
- native : 자바 언어가 아닌 JNI를 사용해서 구현되었다
- CloneNotSupportedException : java.lang.Cloneable 인터페이스를 구현하지 않으면 예외 던지기
- checked exception이기 때문에, 호출하는 쪽에서 반드시 예외처리를 해야한다
class Person implements Cloneable{
private String name;
private int age;
Person(String name, int age){
this.name = name;
this.age = age;
}
@Override
public Person clone(){
try{
return (Person) super.clone();
}catch(CloneNotSupportedException e){
return null;
}
}
String getName(){
return name;
}
int getAge(){
return age;
}
void setName(String name){
this.name = name;
}
void setAge(int age){
this.age = age;
}
}
Example :: Shallow Copy vs Deep Copy (by clone())
public class test3{
public static void main(String[] args) {
Person a = new Person("David", 30);
Person b = a; // shallow copy
Person c = a.clone(); // deep copy
a.setAge(50);
System.out.println("a : " + print(a));
System.out.println("b : " + print(b));
System.out.println("c : " + print(c));
}
static String print(Person p){
return "Hashcode : " + p.hashCode() + " / Name, Age : " + p.getName() + ", " + p.getAge();
}
}
- a-b : shallow copy이므로, 객체의 주소값을 서로 공유 / 값을 변경하면 서로 같이 Update
- a-b : deep copy이므로, 서로 다른 주소값을 가지고 있고, 값을 변경해도 서로 아무런 영향이 없다
>> 모든 멤버 변수가 primitive 타입 or immutable하다면 @Override한 clone()을 사용해도 깊은 복사가 된다
- 하지만 멤버 변수가 mutable하다면, 깊은 복사가 이루어지지 않고, 얕은 복사가 이루어진다
Mutable variable
import java.util.*;
class Address{
private int num;
private String city;
Address(int num, String city){
this.num = num;
this.city = city;
}
int getNum(){
return num;
}
String getCity(){
return city;
}
void setNum(int num){
this.num = num;
}
void setCity(String city){
this.city = city;
}
}
class Person implements Cloneable{
private String name;
private int age;
private Address adr;
Person(String name, int age, Address adr){
this.name = name;
this.age = age;
this.adr = adr;
}
@Override
public Person clone(){
try{
return (Person) super.clone();
}catch(CloneNotSupportedException e){
return null;
}
}
String getName(){
return name;
}
int getAge(){
return age;
}
Address getAdr(){
return adr;
}
void setName(String name){
this.name = name;
}
void setAge(int age){
this.age = age;
}
}
- Person내에 Mutable Variable인 Address를 추가해봤다
public class test3{
public static void main(String[] args) {
Person a = new Person("David", 30, new Address(10101, "Seoul"));
Person b = a; // shallow copy
Person c = a.clone(); // deep copy
a.getAdr().setCity("Busan");
System.out.println("a >> Name, Age : " + a.getName() + ", " + a.getAge() + " : " + a.getAdr().getCity());
System.out.println("b >> Name, Age : " + b.getName() + ", " + b.getAge() + " : " + b.getAdr().getCity());
System.out.println("c >> Name, Age : " + c.getName() + ", " + c.getAge() + " : " + c.getAdr().getCity());
}
}
- a.getAdr().setCity("Busan")을 통해서 a의 city를 변경했는데
- b는 얕은복사니까 당연히 변경되었고
- c는 clone()을 통해서 복사를 하였는데도 변경이 되었다
>> 이럴 경우 직접 Mutable Variable을 포함한 깊은 복사를 구현해야 한다
- Mutable Variable 내에 clone()을 구현하고 해당 변수를 사용하는 객체에서도 clone()을 구현해야 한다
@Override
public Address clone(){
try{
return (Address) super.clone();
}catch(CloneNotSupportedException e){
return null;
}
}
@Override
public Person clone(){
try{
Address c_adr = adr.clone(); // 일단 참조변수부터 clone()
Person p = (Person) super.clone();
p.setAddress(c_adr);
return p;
}catch(CloneNotSupportedException e){
return null;
}
}
- 이제서야 clone()을 통한 깊은 복사가 완료되었다
반드시 clone()?
- Cloneable 인터페이스를 구현하는 것보다, 복사 생성자 or 복사 메소드를 정의하는게 더 좋은 방법이라고 생각한다