Kotlin

코드스피츠 코틀린 2강 정리

dev_roach 2022. 6. 14. 00:54
728x90

KClass

KClass - 코틀린 클래스를 얻는 방법?

  • 클래스::class
  • 인스턴스::class

KClass.members

  • Collection<KCallable<*>>

KCallable

    • 모든 호출가능한 요소
      • KProperty 속성
        • 여기서 의문은 왜 Class Property 는 호출 가능한 요소인가? 라고 하면 Kotlin 에서는 값을 만들면 getter 또는 setter 를 자동으로 만들어준다. 물론 이건 변수가 val /
          var 냐의 차이도 있지만, kotlin delegate 를 공부해보면 kotlin property 는 호출 가능한 요소임을 좀 더 와닿게 알 수 있다.
        • 예시가 허접하지만 대충 이렇게 된다
class Person {
  var name: String by PersonNameDelegator()
}

class PersonNameDelegator {
  operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
    return "$thisRef, thank you for delegating '${property.name}' to me!"
  }

  operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
     println("$value has been assigned to '${property.name}' in $thisRef.") 
  }
}

 

    • KFunction 함수, 메소드
      • ::함수, 인스턴스::메소드 (이런건 자바에서 많이해봐서 익숙했다.)
      • 함수의 리플렉션은 그 함수의 참조를 가르키는게 아니라, 람다를 계속해서 생산해 낸다. (오버헤드가 있음. 자바랑 마찬가지)

Kotlin Collection 구조

image

위 사진처럼 구조를 지니기에 Array 를 쓰지만 Iterator Interface 를 이용할 수 있고, Collection 을 이용할 수 있다.
반대로 Set 에서도 Iterator 를 사용할 수 있다. 이건 정말 좋은 기능같다. 사실 이렇게 까지 생각해본적은 없는데 생각해보니 정말 편리하게 잘 만든것 같다는 생각이 들었다.

프로그래밍 언어의 문과 식

  • "문(Statement)" 은 컴파일 되고 난뒤, CPU 에 내리는 명령으로 바뀜
  • "식(Expression)" 은 컴파일 되고 난뒤, 메모리에 올라가는 값으로 바뀜.
    • 함수형 언어나, 식을 강화하려는 언어는 "Statement" 를 "Expression" 으로 바꿨기 때문임.
      • Runtime 에 좀 더 자유로움을 주기 위해서
      • Statement 는 한번 실행하면 끝나고, 여러번 실행시킬수 없고 제어하기 힘들어서 이걸 Expression 으로 많이 바꿈.

이 말을 듣고 좀 더 Expression 이라는 것에 대한 이해가 생긴것 같기도 함. 그니까 자바의 Switch 나 If 를 보면 이 조건에 부합할때
code: 10 라인으로 가! 이런 느낌인데, 이건 사실 CPU Instruct 에 가깝고, 이걸 좀 더 런타임에서 유연하게 쓰기 위해 코틀린은 이걸 Expression 으로 변환했다.
이런식으로 느껴졌음.

그럼 어떻게 Converting 하는데? 보통은 식이였던 구문을 Function 으로 한차례 Wrapping 해서 이를 Expression 처럼 사용할 수 있게 함.

나만의 궁금증

그럼 삼항 연산자는 if..else 보일러 플레이트를 줄이기 위해 Statement 를 Expression 으로 올린건가?

코틀린 함수 기본값

image

위와 같이 있을때 sep 부터 기본 값이 있다고 해보자, 그러면 (T) -> String 도 기본값이 있어야 한다.
이러한 문제를 피하기 위해 Passing Trailing Lambdas 의 경우에는 기본값이 없어도 된다. (약간의 유연성을 위한 회피 느낌)
실제로 이런식의 코드가 코틀린 내부에 많다고 한다.

public fun <T, A : Appendable> Iterable<T>.joinTo(buffer: A, separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((T) -> CharSequence)? = null): A {
    buffer.append(prefix)
    var count = 0
    for (element in this) {
        if (++count > 1) buffer.append(separator)
        if (limit < 0 || count <= limit) {
            buffer.appendElement(element, transform)
        } else break
    }
    if (limit >= 0 && count > limit) buffer.append(truncated)
    buffer.append(postfix)
    return buffer
}

흠 근데 위를 보면 기본값이 있는데, Hika 님이 말씀하시고자 했던건 어떤의미일까? 잘 모르겠다.
혹시 잘 이해한사람이 있다면 댓글이나 ISSUE 로 알려주면 고마울 것 같다 ㅎㅎ..

Smart Casting

image

SmartCast 는 쉽게 설명하면 저 밑줄그은 부분을 기점으로 위에서 null 이 아님을 체크하고 내려왔다면, 아래에서는 null 이 아님을 전제로 코딩할 수 있다는 뜻.
다만 필수 조건으로 변수가 immutable 해야 한다. 왜냐하면 Multi-Thread 나 여러 참조에 의해 예기치 못하게 값이 바뀔 수도 있기 때문이다.
그래서 이를 잘 이용하기 위해 SmartCast 가 필요한 Context 내부에서는 val 임시변수에 받아서 썼던 기억이 난다.

Matcher

Java 나 C 의 Switch, case 는 어셈블리가 작동할때 router map 처럼 작동해서 동시에 어디로 가게될지 안다고함 (분기를 이때함)
하지만 코틀린의 회(?) 는 위에서 아래 부터 순차적으로 작동하기 때문에 smartCasting 이 가능한 이유임. 반대로 말하면 Kotlin 의 회는 부하가 심하다는 뜻임.
그래서 많이 걸리는 케이스를 위로 올려야 함. (한 40% 이해한 거 같음.) -> 3개월뒤에 이 강의를 다시 들어보자.. 그땐 다르겠지

Keyword

  • Matcher 를 제공하는 언어

중복 관련 이야기

1번 코드

fun <T : Any> jsonObject(target: T): String {
    val builder = buildString {
        target::class.members
            .filterIsInstance<KProperty<*>>()
            .joinTo(buffer = this, separator = ", ", prefix = "{", postfix = "}") {
                val value = it.getter.call(target) // call 은 java reflection 의 invoke 를 생각하면 됨.
                "${jsonString(it.name)} : ${when (value) {
                    null -> "null"
                    is String -> jsonString(value)
                    is Boolean, is Number -> value.toString()
                    is List<*> -> jsonList(value)
                    else -> jsonObject(value)
                }}"
            }
    }

    return builder
}

2번 코드

fun <T : Any> jsonObject(target: T): String {
    val builder = buildString {
        target::class.members
            .filterIsInstance<KProperty<*>>()
            .joinTo(buffer = this, separator = ", ", prefix = "{", postfix = "}") {
                "${jsonValue(it.name)} : ${jsonValue(it.getter.call(target))}"
            }
    }

    return builder
}

위의 중복 이야기는 흥미로웠음. "${jsonValue(it.name)} : ${jsonValue(it.getter.call(target))}" 으로 코드가 바뀌는 과정을 설명해줄때 참 좋았음.
단순 코드가 같은 것만이 중복이 아니다. 나도 이 의견에 동의. 결국 context 가 하고자 하는것이 겹치거나 다르게 표현되고 있으면 그것이 중복아닌가? 라는 생각

순차적으로 작성하다가 결국 코드가 아래처럼 됬을때, 뭔가 깨달음이 딱 왔다.

image

근데 코드 짜는걸 보면서 참 잘 느껴지는 것이 Recursive 와 Divide And Conquer 처럼 코드를 작성하시는게 놀라웠음. 약간 재귀가 생활화 되있는 느낌..?

  • 난 재귀적 사고가 강하지 않아서 알고리즘을 열심히 풀며 노력해야 겠다는 생각도 많이들었다.

Named Argument (a.k.a Name Parameter)

나는 개인적으로 이게 엄청 좋다고 생각한다. 예전 루비했을때 Named Argument 가 존재해서 진짜 좋았음.
이유는 Java 에서 Builder 를 쓰는 이유는 순전 Named Argument 가 없어서 라고 생각함. 왜냐면 어떤 속성을 넣는지 알기 힘드니까. (그래서 코틀린에서 Builder 를 써야 한다는 말을 잘 이해하지 못하겠음)

JoinTo

image

위 사진 처럼 계속해서 StringBuilder 객체를 생성해야 하는 부담(오버헤드)이 있음. 사실 하나의 StringBuilder 객체면 충분한 구조임.

확장 함수

개인적으로 되게 좋아하는 기능 ㅎㅎ. 난 이걸 통해서 객체지향을 좀 더 할 수 있다고 생각함. 이게 없어서 다른 언어는 XXUtil 로 Convert 하는게 너무 많음.

  • 확장함수 안에서는 this 를 사용가능. (수신 객체)
  • Extension Function 에 접근제한자를 private 으로 걸면 그 function 은 해당 파일내에서만 사용 가능하다.
    • C external 예시를 들었는데, JS 의 external 이나 default 처럼 나도 생각했다.

개선된 로직

image

위처럼 모든 함수에서 builder 를 인자로 받고 builder 를 이용함. 그렇다면 어떻게 이 인자를 줄일 수 있을까?
확장 함수를 통해서 가능(요건 수신객체의 개념을 이해하면 됨.) 코틀린에서는 위와 같은 상황이 중복일 수 있음. 따라서 아래와 같이 중복 제거 가능

image

--- ---

image

위처럼 Builder 를 변수로 이용하기에 사이에 많은 공격을 통해 로직 도중에 값을 변경시킬 수 있음. (하나의 트랜잭션이라고 이해하면 편함)
그래서 교수님께서는 run() 을 쓰셨으나, 나는 buildString 를 썼음.

fun stringify(value: Any?): String = buildString {
    jsonValue(value)
}

내장 확장함수

image

Annotation

Code 외적으로 힌트를 추가하는 Meta Programming 의 일종.

  • Annotation 의 장점은 기존 로직에 아무런 영향도 없다.
    • 예를 들어 이건 절대 건드리지 마시오를 @DoNotModify 요런식으로 작성해도 된다는 뜻.
    • 물론 프로그래밍에 활용 가능함 (Spring 에서 @Bean 과 같이)

image

Java Annotation Target 을 바이너리로 해두면, Compile 단계의 Annotation Processor 과정에서 이용 가능하다. 근데 런타임으로 바꾸면 이를 런타임에서 참조 가능하다.
옛날에 Web Server -> Application Server 로 천천히 컨버팅할때 Meta Programming 비스무리 하게 했었는데 이때가 기억났다 ㅎㅎ..

Annotation 의 단점은 알아야지만 사용할 수 있다는 점. JPA 에서 Entity 를 표현하기 위해서는 @Entity, @Id 를 알아야지만 사용가능하다. (xml 이나 다른 방식도 있지만 예시로..)

개인적으로 코틀린의 Annotation 이 참 간결하게 만들었다는 생각이 들었음.

Kotlin 사용할 때 생각하면 좋은점

  • 다른 언어에서 뻔한 보일러 플레이트는 코틀린 built-in 에 존재하지 않는지 찾아보면 좋음
  • Kotlin 우선 순위 처리 로직을 잘 알자!

'Kotlin' 카테고리의 다른 글

Kotlin Coroutines  (1) 2022.06.19
코드스피츠 코틀린 3강 정리  (0) 2022.06.18
코드스피츠 1강 정리  (0) 2022.06.12
코루틴으로 Heap 사용량 최적화(?) 한 이야기 끄적  (0) 2022.06.02
Kotlin Coroutines - Basic  (0) 2022.05.29