=
를 사용하면 기본적으로 얕은 복사가 수행된다.data class Car(
val name: String,
var position: Int,
){
fun move(){
position++
}
}
fun main() {
val car = Car("롤스로이스", 3)
val copyCar = car
copyCar.move()
// 원본 객체의 값 역시 변경된다.
}
car
과 copyCar
의 주소 값이 같은 것을 알 수 있다! copyCar
의 move
에 영향을 받아 원본 객체의 position까지 변경되는 모습
data class
에서는 copy
라는 메소드를 제공한다. copy
는 원본 객체의 인스턴스 주소와 다른 새로운 주소에 원본 객체의 값과 같은 인스턴스를 생성한다.
data class
가 아닌 경우 메소드를 직접 구현해서 깊은 복사를 수행할 수 있다.
class Car(
val name: String,
var position: Int,
){
fun move(){
position++
}
fun copy(): Car = Car(name, position)
}
fun main() {
val car = Car("롤스로이스", 3)
val copyCar = car.copy()
copyCar.move()
}
생성자로 받은 가변 데이터들을 외부에서 변경하는 것을 막기 위해 복사본을 이용하는 방법
class Cars(val cars: List<Car>)
fun main() {
val cars = Cars(
listOf(
Car("롤스로이스", 0),
Car("아반떼", 0)
)
)
}
인자로 들어오는 cars
를 조작하면 일급 컬렉션 내부의 cars
에 대해서 영향을 줄 수 있다!
class HyundaiCars(val cars: List<Car>)
fun main() {
val cars = (
mutableListOf(
Car("제네시스", 0),
Car("아반떼", 0)
)
)
val hyundaiCars = HyundaiCars(cars)
cars.add(Car("소나타", 0))
println("현대차 개수: %d".format(hyundaiCars.cars.size))
}
위와 같은 상황을 예방하는 방법
class HyundaiCars(cars: List<Car>){
//인자로 들어온 cars가 참조하고있는 주소와 다른 주소를 참조하는 list를 넣어주자!
val cars = cars.toList()
}
fun main() {
val cars = (
mutableListOf(
Car("제네시스", 0),
Car("아반떼", 0)
)
)
val hyundaiCars = HyundaiCars(cars)
cars.add(Car("소나타", 0))
println(hyundaiCars.cars.size)
}
인자로 들어오는 cars
에 toList
를 사용해 원본 List가 참조하고 있는 주소와 다른 주소를 참조하는 새로운 List를 생성한다!
원본 cars
에 add
를 해주어도 HyundaiCars
내부에 존재하는 cars
에 영향을 주지 못한다.
data class Car(
val name: String,
var position: Int,
) {
fun move() {
position++
}
}
class HyundaiCars(cars: List<Car>) {
val cars = cars.toList()
}
fun main() {
val genesis = Car("제네시스", 0)
val avante = Car("아반떼", 0)
val cars = mutableListOf(genesis, avante)
val hyundaiCars = HyundaiCars(cars)
//외부에서 genesis를 조작하면 hyundaiCars안에 있는 gensis의 position또한 변경될까? 그렇다...
genesis.move()
genesis.move()
hyundaiCars.cars.forEach { hyundaiCar ->
println("%s: %d".format(hyundaiCar.name, hyundaiCar.position))
}
}
💡 우리는 toList
로 원본 List의 head가 가리키고 있는 주소만 변경했을 뿐 HyudaiCars
의 프로퍼티 cars
안에 존재하는 각각의 Car
인스턴스 주소는 여전히 원본 Car
인스턴스 주소와 같기 때문이다.
일급 컬렉션 내부에서 관리하고 있는 객체가 가변 객체일때 외부에서의 변경을 막으려면 어떻게 해야할까? List의 head를 바꾸는 것 뿐만 아니라 List에 존재하는 각각의 인스턴스에 대해서 깊은 복사를 수행해주어야 한다.
data class Car(
val name: String,
var position: Int,
) {
fun move() {
position++
}
}
class HyundaiCars(cars: List<Car>) {
val cars = cars.map{
//list에 존재하는 각각의 인스턴스에 대해서 깊은 복사 수행!
it.copy()
}
}
fun main() {
val genesis = Car("제네시스", 0)
val avante = Car("아반떼", 0)
val cars = mutableListOf(genesis, avante)
val hyundaiCars = HyundaiCars(cars)
genesis.move()
genesis.move()
hyundaiCars.cars.forEach { hyundaiCar ->
println("%s: %d".format(hyundaiCar.name, hyundaiCar.position))
}
}
genesis
를 move
해도 HyundaiCars
내부의 cars
에는 영향을 미치지 못한다.
data class Car(
val name: String,
var position: Int,
) {
fun move() {
position++
}
}
class HyundaiCars(cars: List<Car>) {
val cars = cars.map{
it.copy()
}
}
fun main() {
val genesis = Car("제네시스", 0)
val avante = Car("아반떼", 0)
val cars = mutableListOf(genesis, avante)
val hyundaiCars = HyundaiCars(cars)
//이렇게 hyndaiCars안의 cars를 꺼내서 move를 수행하면 cars들이 영향을 받을 것이다.
hyundaiCars.cars.forEach { car ->
car.move()
}
hyundaiCars.cars.forEach { hyundaiCar ->
println("%s: %d".format(hyundaiCar.name, hyundaiCar.position))
}
}
만약 HyundaiCars
내부의 cars
들을 외부에서 꺼내 사용할 필요가 있지만 외부에서는 cars
에게 영향을 줄 수 없도록 하기 위해서는 어떻게 해야할까?
data class Car(
val name: String,
var position: Int,
) {
fun move() {
position++
}
}
class HyundaiCars(cars: List<Car>) {
// 이런식으로 내부에서 사용하는 컬렉션에는 언더바를 붙여서 관리한다. backing property라는 이름으로 불린다!
private val _cars = cars.toList().map { it.copy() }
// 외부로 노출되는 cars는 내부의 _cars에 존재하는 인스턴스들을 대상으로 깊은 복사를 수행한다.
val cars: List<Car>
get() = _cars.map { it.copy() }
fun myCars() {
_cars.forEach { hyundaiCar ->
println("%s: %d".format(hyundaiCar.name, hyundaiCar.position))
}
}
}
fun main() {
val genesis = Car("제네시스", 0)
val avante = Car("아반떼", 0)
val cars = mutableListOf(genesis, avante)
val hyundaiCars = HyundaiCars(cars)
hyundaiCars.cars.forEach { car ->
car.move()
}
hyundaiCars.myCars()
}
외부에서 아무리 hyundaiCars
의 cars
를 조작해도 영향을 주지 못한다!
블랙잭 미션을 진행하면서 다음과 같이 Card
를 관리하는 클래스 Cards
를 생성했는데 Card
가 가변 객체가 아니기 때문에 외부에 노출되는 cards
에 대해서는 깊은 복사를 수행하지 않았다.
class Cards(
cards: List<Card> = listOf(Card.draw(), Card.draw()),
) {
private val _cards: MutableList<Card> = cards.toMutableList()
val cards: List<Card>
get() = _cards.toList()
...
}
원본 객체의 인스턴스들에 의해서 원하지 않은 변경이 일어날 수 있는 경우가 굉장히 많은 것을 보았다. 이때까지 코드를 구현하면서 크게 생각하지 않았던 부분이지만 앞으로는 깊은 복사를 잘 이용해 의도치 않은 상황이 일어나지 않도록 주의해야겠다.
제가 방어적 복사를 잘 알고 있다고 생각했는데, 이 글을 읽고 방어적 복사를 할 때, 리스트 내 객체에 대해서도 깊은 복사를 수행해야 한다는 것을 처음 알았네요 ! 감사합니다 ~ 👻