개발/안드로이드

[Andriod - Kotlin] 객체 복사

이쓴 2021. 11. 26. 21:03

객체를 복사할 때 얕은 복사(shallow copy)와 깊은 복사(deep copy)가 있다는 것을 알고 계신가요?

개발을 하다 보면 리스트나 객체를 복사해서 사용해야 하는 경우가 생기는데 이때 우리가 늘 사용하는 대입 연산자(=)를 사용하면 얕은 복사가 되어 값을 변경하면 원본 객체와 복사본 객체의 값이 모두 변경됩니다. kotlin에서는 clone(), copy()를 사용하여 깊은 복사를 할 수 있습니다. 

얕은 복사와 깊은 복사 개념

1) 얕은 복사

class Student(id : Int, name : String, score : MutableList<Int>) : Cloneable{
    var id : Int
    var name : String
    var score : MutableList<Int>
    
    init{
        this.id = id
        this.name = name
        this.score = score
    }
}
var a = Student(201, "bell", mutableListOf<Int>(90, 90, 80, 70))
var b = a
    
println("a : " + a) //a : Student@4a574795
println("b : " + b) //b : Student@4a574795

얕은 복사는 복사하는 대상의 주소를 참조합니다. 따라서 원본 객체나 복사한 객체 중 하나를 변경하면 나머지 객체도 같이 변경됩니다. 조금 더 이해하기 쉽게 예를 들어서 Student라는 클래스를 정의해보겠습니다. 학생의 id, 이름, 그리고 점수를 받아서 저장하는 Student클래스가 있습니다. main함수에서 변수 a에 Student객체를 저장하고, 변수 b에 a를 복사한 후 출력하면 a변수와 b변수 모두 같은 주소를 출력합니다. 같은 메모리를 가리키고 있으니, a나 b를 수정하고 a, b를 출력하면 같이 변경된 값을 출력합니다.

 

2) 깊은 복사

깊은 복사는 우리가 원하는 바로 그 복사! 객체와 인스턴스가 모두 복사됩니다. 깊은 복사는 얕은 복사와 달리 a의 주소에는 원본 b의 데이터가 저장됩니다. 따라서 원본 객체가 변경되어도 복사된 객체에는 영향을 주지 않으며, 반대로 복사된 객체가 변경되어도 원본 데이터에는 영향을 주지 않습니다. 

Kotlin에서 깊은 복사 방법

1) Cloneable사용

class Student(id : Int, name : String, score : MutableList<Int>) : Cloneable{
    var id : Int
    var name : String
    var score : MutableList<Int>
    
    init{
        this.id = id
        this.name = name
        this.score = score
    }

    
    public override fun clone(): Student{
        return super clone() as Student
    }
}

Kotlin에서 객체를 복사할 때는 clone()을 사용하여 깊은 복사를 할 수 있습니다. 클래스를 생성할 때 colne을 override 하여 재정의해서 사용할 수 있습니다. 위 Student코드에서 clone()을 override 하고, 메인에서 clone()을 사용하여 객체를 복사하면 완전히 복사되어 서로 다른 메모리 주소를 출력하는 것을 확인할 수 있습니다.

var a = Student(201, "bell", mutableListOf<Int>(90, 90, 80, 70))
var b = a.clone()

println("a : " + a)	//a : Student@4a574795
println("b : " + b)	//b : Student@3f99bd52

그런데 여기서 문제가 하나 더 있습니다. 기본 타입의 변수는 clone()을 override 하는 것만으로 복사가 되지만, 기본 타입이 아닌 변수는 단순하게 clone()을 override 한다고 복사되지 않습니다. 따라서 clone()을 재정의해야 합니다. 

class Student(id : Int, name : String, score : MutableList<Int>) : Cloneable{
    var id : Int
    var name : String
    var score : MutableList<Int>
    
    init{
        this.id = id
        this.name = name
        this.score = score
    }
    
    public override fun clone(): Student{
        var student = super.clone() as Student
        student.score = mutableListOf<Int>().apply{ addAll(score) }
        return student
    }
}

위 코드처럼 clone()을 재정의해서 사용하면 Student객체의 score(list)까지 깊은 복사가 됩니다.

 

2) 데이터 클래스

kotlin에서 데이터 클래스를 만든 경우 copy()를 사용하여 깊은 복사를 할 수 있습니다. 하지만 copy도 clone처럼 기본 타입 이외의 변수는 얕은 복사가 되기 때문에 데이터 클래스에서 copy함수를 정의해서 사용해야 합니다.

 

Reference

https://developer.android.com/reference/java/lang/Cloneable

 

Cloneable  |  Android Developers

 

developer.android.com