ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 아이템1 - 가변성을 제한하라
    Kotlin/Effective Kotlin 요약 2022. 12. 26. 00:01
    728x90

    Kotlin에서는 read-write property인 var을 사용하거나, mutable 객체를 사용하면 상태를 가질 수 있습니다.

    var a = 10
    var list: MutableLost<Int> = mutableListOf()

     

    상태를 가지는 경우는 양날의 검입니다.

    장점 : 시간의 변화에 따라서 변하는 요소를 표현할 수 있다.

     

    단점 : 상태를 적절하기 관리하는 것은 어렵다

     

    1. 어디서 변경이 일어났는지 추적하기 힘들다.

    2. 어떤 값을 가지고 있는지 알아야 코드의 실행을 예측할 수 있습니다 (예를 들어 분기문이 있을 때)

    3. 멀티스레드 프로그래밍일 때는 적절한 동기화가 필요합니다. (Lock, 원자적 연산 등)

    4. 테스트하기 어렵습니다 (모든 상태를 테스트해야 합니다, 상태가 늘어나면 더 힘들어집니다)

    5. 상태 변경이 일어날 때, 변경을 다른 부분에 알려야 하는 경우가 있습니다. (정렬되어 있는 리스트에 가변 요소가 추가된다면 다시 정렬해야 합니다)

     

    이런 어려움때문에 하스켈같은 순수 함수형 언어는 가변성을 완전하게 제한합니다.

     

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

    - val

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

    - 데이터 클래스의 copy

     

    하지만 val을 사용하는 경우라도 객체를 담고 있다면 내부적으로 바뀔 수 있습니다.

    val list = mutableListOf(1,2,3)
    list.add(4)

     

    또한 var 프로퍼티를 사용하는 val 프로퍼티도 내부적으로 바뀔 수 있습니다.

    var name: String = "junwoo"
    var surname: String = "kim"
    val fullName
    	get() = "$name $surname"

     

     

    유연한 프로퍼티

    코틀린의 프로퍼티는 기본적으로 캡슐화되어 있고, 추가적으로 사용자 정의 게터, 세터를 가질 수 있습니다.

     

    var은 게터와 세터를 모두 제공합니다.

    val은 변경이 불가능하므로 게터만 제공합니다.

     

    그래서 val을 var으로 오버라이드 할 수 있습니다.

    interface Element{
    	val active: Boolean
    }
    
    class Actualelemnt: Element{
    	override var active: Boolean = false
    }

     

    val은 읽기 전용 프로퍼티이지만, 불변을 의미하는것은 아님을 기억해야 합니다!

     

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

    프로퍼티가 val, var으로 구분됩니다.

    이처럼 컬렉션도 읽기전용과 읽고 쓸 수 있는 컬렉션으로 구분됩니다.

    Iterable, Collection, Set, List 인터페이스는 읽기 전용입니다.

    반대로 MutableIterable, Mutablecollection, MutableList, MutableSet은 읽고 쓸 수 있는 컬렉션입니다.

    mutable이 붙은 인터페이스는 대응되는 읽기 전용 인터페이스를 상속받아, 변경을 위한 메서드를 추가한 것입니다.

     

    읽기 전용 컬렉션이 내부의 값을 변경할 수 없다는 의미는 아닙니다.

    예를 들어 Iterable<T>.map은 ArrayList를 반환합니다.

    inline fun <T, R> Iterable<T>.map(
    	transformation: (T) -> R
    ): List<R>{
    	val list = ArrayList<R>()
    	for(ele in this){
    		list.add(transformation(elem))
    	}
    }

    코틀린이 내부적으로 immutable하지 않은 컬렉션을 외부적으로 immutable 하게 보이게 만들어서 얻어지는 안정성입니다.

     

    실제로 다운캐스팅을 시도하게 되면 문제가 되고 코틀린을 할 때 허용하면 안 되는 큰 문제입니다.

    val list = listOf(1,2,3)
    
    //이렇게 하지 마세요
    if(list is MutableList){
    	list.add(4)
    }

    실행 결과는 플랫폼에 따라 다릅니다.

    JVM에서 listOf는 자바의 List 인터페이스를 구현한 Array.ArrayList 인스턴스를 리턴합니다.

    List 인터페이스는 add,set 메서드를 제공하여 mutableList로 변경할 수 있지만 ArrayList는 이러한 연산을 구현하고 있지 않습니다.

     

    다음과 같이 작성하면 어떤 규약도 어기지 않고 기존의 객체는 immutable이라 수정할 수 없어 안전하게 사용할 수 있습니다.

    val list = listOf(1,2,3)
    val mutableList = list.toMutableList()
    mutableList.add(4)

     

    데이터 클래스의 copy

    immutable객체의 장점

    - 상태가 변하지 않아 코드를 이해하기 쉽다

    - 병렬처리를 안전하게 할 수 있다

    - 참조는 변경되지 않기 때문에 쉽게 캐시 할 수 있다

    - 방어적 복사본을 만들 필요가 없다

    - immutable 객체는 다른 객체를 만들 때 활용하기 좋습니다

    - 변하지 않기 때문에 set, map의 키로 활용할 수 있습니다. (변하게 되면 해당 객체가 있어도 false를 리턴합니다)

     

    copy메서드를 활용하면, 모든 기본 생성자 프로퍼티가 같은 새로운 객체를 만들어 낼 수 있습니다.

    data class User(
    	val name: String,
    	val surname: String,
    )
    
    var user = User("junwoo", "kim")
    user = user.copy(surname = "kim1")
    print(user) // User(name=junwoo, surname= kim1)

     

    다른 종류의 변경 가능 지점

    val list: MutableList<Int> = mutableListOf()
    var list2: List<Int> = listOf()

     

    변경법

    list.add(1)
    list2 = list2 + 1

     

    mutable list 대신 mutable 프로퍼티를 사용하는 형태는 사용자 정의 세터를 활용하여 변경을 추적할 수 있습니다.

     

    Delegates.observable사용 예시

    fun main(args: Array<String>) {
        var name by Delegates.observable(listOf<String>()){
            _, old, new -> println("changed $old to $new")
        }
        name += "Fabio"
    }
    
    //출력 changed [] to [Fabio]

     

    최악의 방식

    var list3 = mutableListOf<Int>()

    최악은 프로퍼티와 컬렉션을 모두 변경 가능한 지점으로 만드는 것입니다.

    모호성이 발생해서 +=를 사용할 수 없게 됩니다.

     

    변경 가능 지점 노출하기 말기

    만약 변경 가능 지점을 노출해야 한다면 방어적 복사를 사용하는 것이 좋습니다.

    이때 data 한정자로 만들어지는 copy 메서드를 활용하면 좋습니다.

     

    또는 읽기 전용 슈퍼 타입으로 업케스트하여 가변성을 제한할 수도 있습니다.

     

     

    새로 알게 된 점

    - 오버라이드 할 수 있음

    - var은 스마트캐스트 불가능

    - Delegates.observable 신기

     

     

     

     

     

     

    728x90

    댓글

Designed by Tistory.