Home Effective Kotlin - Item 1. 가변성을 제한하라
Post
Cancel

Effective Kotlin - Item 1. 가변성을 제한하라

kotlin

코틀린에서 가변성 제한하기


코틀린에서 가변성을 제한하기 위해 굉장히 많은 방법을 활용할 수 있지만 많이 사용되고 중요한 방법은 아래와 같다.

  • 읽기 전용 프로퍼티(val)
  • 가변 컬렉션과 읽기 전용 컬렉션 구분
  • 데이터 클래스의 copy

읽기 전용 프로퍼티(val)

코틀린에서는 val 키워드로 읽기 전용 프로퍼티를 만들 수 있다.

  • 읽기 전용 프로퍼티로 정의해도 Mutable한 데이터를 담고 있다면 내부적으로 변경이 가능하다.

가변 컬렉션과 읽기 전용 컬렉션 구분하기

아래와 같은 컬렉션 다운 캐스팅 금지

1
2
3
4
5
val list = listOf(1,2,3)

if (list is MutableList) {
    list.add(4)
}

JVM 기준 Arrays.ArrayList에는 add가 구현되어 있지 않아 UnsupportedOperationException이 발생한다.

읽기 전용에서 가변으로 변경하려면 아래와 같은 toMutableList를 활용한다.

1
2
val list = listOf(1,2,3)
val mutable = list.toMutableList()

데이터 클래스의 copy

Immutable 객체를 사용하면 아래와 같은 장점이 있다.

  • 한번 정의된 상태가 유지되므로 코드를 이해하기 쉽다.
  • Immutable 객체는 공유시에도 충돌이 발생하지 않아 병렬 처리를 안전하게 할 수 있다.
  • Immutable 객체에 대한 참조는 변경되지 않으므로, 쉽게 캐시할 수 있다.
  • Immutable 객체의 방어적 복사본을 만들 필요가 없다. 또한 깊은 복사를 따로 할 필요가 없다.
  • Immutable 객체는 다른 객체를 만들때 활용하기 좋으며 실행을 더 쉽게 예측할 수 있다.
  • Immutable 객체는 Set 혹은 Map의 키로 활용할 수 있다. (Mutable은 요소에 수정이 일어나면 해시 테이블 내부에서 요소를 찾을 수 없어 활용할 수 없음)

Int와 같이 내부적으로 plus, minus와 같이 불변한 Int를 리턴하는 메소드를 만들어 불변을 유지할 수 있다.

매번 모든 프로퍼티 대상으로 plus, minus와 같은 함수를 만드는 것은 번거로우니 data class 및 copy 메소드를 활용하자

다른 종류의 변경 가능 지점(mutating point)


변경 가능한 리스트를 만들 때 아래와 같은 두가지 선택지가 존재한다.

  • val + MutableList
    1
    2
    3
    
    val list1: MutableList<Int> = mutableListOf()
    var + ImmutableList
    var list2: List<Int> = listOf()
    

두 가지 모두 변경은 가능하지만 내부적인 동작이 다르다.

1
2
list1 += 1 // list1.plusAssign(1)
list2 += 1 // list2.plus(1)

첫번째 코드는 멀티스레드 처리 시 내부의 동기화 여부를 알 수 없어 위험하며 두번째 코드가 멀티스레드 안정성이 더 좋다. (하지만 잘못 만들면 일부 요소의 손실 가능)

mutable 프로퍼티를 사용하면 사용자 정의 setter(또는 이를 사용하는 델리게이트)를 활용해 변경을 추적할 수 있음

1
2
3
var names by Delegates.observable(listOf<String>()) { _, old, new -> 
    println("Names chanaged from $old to $new")
}

MutableCollection 대신 mutable 프로퍼티인 var를 활용하는게 객체 변경을 더 제어하기가 쉽다.

최악의 방법은 프로퍼티와 컬렉션 모두 Mutable로 만드는 것

1
2
var list = mutableListOf<Int>()
list += 1 // 모호성이 발생해 해당 연산자 사용 불가능

변경 가능 지점 노출하지 말기 Mutable 객체가 외부에 노출되는 경우 수정이 발생할 수 있으니 위험하다.

리턴되는 mutable 객체 복제해 반환하는 방어적 복제(defensive copying) 사용

1
2
3
4
5
6
7
class UserHolder {
   private val user: MutableUser()

   fun get(): MutableUser {
       return user.copy()
   }
}

정리

  • var 보다는 val 사용
  • mutable 프로퍼티보다는 immutable 프로퍼티 사용
  • mutable 객체와 클래스 보다는 immutable 객체와 클래스 사용
  • 변경이 필요한 대상이 있다면 immutable data class로 만들고 copy 활용
  • 컬렉션에 상태를 저장해야 한다면, mutable 보다는 immutable 컬렉션 사용
  • 변이 지점을 적절하게 설게하고 불필요한 변이 지점을 만들지 않는게 좋다.
  • mutable 객체의 외부 노출은 피하자.
  • 효율성 때문에 immutable 보다 mutable을 다루는게 좋은 경우도 있다.

레퍼런스

이펙티브 코틀린 - 프로그래밍 인사이트, 마르친 모스칼라 지음, 윤인성 옮김

This post is licensed under CC BY 4.0 by the author.

네이버 클라우드 플랫폼 사용 회고

Effective Kotlin - Item 2. 변수의 스코프를 최소화하라