코틀린

Kotlin을 정복해봅시다 2

newwisdom 2021. 8. 6. 13:59
반응형

2021-05-26글

코틀린 DSL

DLS란?

  • 도메인 특화 언어 (Domain-specific language) ↔️ 범용 프로그래밍 언어
  • 선언적 언어
  • 세부 실행은 언어를 해석하는 엔진에 맡김
  • 컴파일 시점에 제대로 검증하는 것이 어려움

코틀린 DSL이란?

  • 범용 언어(= 코틀린)로 작성된 프로그램의 일부
  • 범용 언어와 동일한 문법 사용
  • 호출 결과를 객체로 변환하기 위해 노력할 필요 없음
  • 타입 안정성 보장

코틀린은 간결한 구문을 어떻게 지원하는가?

  • 확장 함수
  • 중위 호출
  • 연산자 오버로딩
  • get 메서드에 대한 관례
  • 람다를 괄호 밖으로 빼는 관례
  • 수신 객체 지정 람다

확장 함수 Extension functions

  • 코틀린은 클래스를 확장해서 새로운 기능을 개발할 수 있도록 지원
  • 상속 과는 조금 다른 개념
  • ex) 외부 라이브러리를 사용할 때 이 자체 클래스는 변경할 수 없지만 이를 확장해 원하는 새로운 함수를 만들 수 있음
"Kotlin".lastChar()

fun String.lastChar(): Char {
    return this.get(this.length - 1)
}

중위 표기 Infix notation

중위표기법?

infix(중위표기법) : 일상생활에서의 수식 표기법으로 두 개의 피연산자 사이에 연산자가 존재하는 표현방식이다. ex) X + Y

Kotlin에서 infix 키워드를 사용하여 중위표기법으로 함수를 호출할 수 있다. 단, 아래 요건을 충족해야 한다.

  • They must be member functions or extension functions. (멤버 함수 혹은 확장 함수일 때)
  • They must have a single parameter. (단일 매개 변수일 때)
  • The parameter must not accept a variable number of arguments and must have no default value. (가변인자를 받으면 안되고 기본 값을 가지면 안된다.)
1 to "one"

infix fun Any.to(other: Any) = Pair(this, other)

연산자 오버로딩 Operator overloading

Point(0, 1) + Point(1, 2)

data class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point): Point = Point(x + other.x, y + other.y)
}
  • plus 함수 앞에 operator 키워드를 붙여 연산자 오버로딩을 하는 함수임을 명시
  • 확장 함수로 정의할 수도 있음

이항 산술 연산 오버로딩

연산 우선순위 함수 이름
1 a * b times
1 a / b div
1 a % b mod(1.1부터 rem)
2 a + b plus
2 a - b minus
  • 더 많은 연산자에 대한 메서드는 공식문서 참고

get 메서드에 대한 관례 Indexed access operator

val names = listOf("am", "mazzi")
names.get(0)
names[0]
  • get이 아닌 인덱스로 접근한다.

람다를 괄호 밖으로 빼내는 관례 Passing a lambda to the last parameter

check(false) { "Check failed." }

수신 객체 지정 람다 Lambda with receiver

  • 람다 함수를 쓸 때 내가 자주 쓰고싶은 객체를 미리 지정해서 사용하는 람다

수신 객체?

  • 확장 함수에서의 this는 확장된 클래스의 객체
  • 즉 확장 함수를 사용하는 그 객체를 의미하는데 이 객체가 바로 수신 객체

with

  • 첫 번째 인자로 받은 객체를 두 번째 인자로 받은 람다의 수신 객체로 만듦

with를 사용하지 않을 경우

fun alphabet(): String {
    val result = StringBuilder()
    for (letter in 'A'..'Z') {
        result.append(letter)
    }
    result.append("\nNow I know this alphabet!")
    return result.toString()
}
  • result 의 중복이 발생

with를 사용한 경우

fun alphabet(): String {
    val stringBuilder = StringBuilder()
    return with(stringBuilder) {
        for (letter in 'A'..'Z') {
            this.append(letter)
        }
        append("\n amazzi~~~!")
        this.toString()
    }
}

// 불필요한 stringBuilder 변수를 없애면 alpabet 함수가 식의 결과를 바로 반환하게 된다.
// 람다 식의 본문에 있는 마지막 식의 값을 반환
fun alphabet(): String = with(StringBuilder()) {
        for (letter in 'A'..'Z') {
            append(letter)
        }
        append("\nNow I know this alphabet!")
        toString()
    }
}
  • with(stringBuilder, { ... }) 와 같은 람다 함수

apply

  • with와 유사
  • 유일한 차이는 항상 자신에게 전달된 객체를 반환
  • 객체의 인스턴스를 만들면서 즉시 프로퍼티 중 일부를 초기화해야되는 경우 유용
fun alphabet(): String = StringBuilder().apply {
        for (letter in 'A'..'Z') {
            append(letter)
        }
        append("\nNow I know this alphabet!")
    }.toString()

초기화를 지연하는 방법

  • 코틀린에서는 변수 선언을 먼저하고, 초기회는 뒤로 미루는 기능들을 제공
  • 사용할지 모른는 데이터를 미리 초기화할 필요가 없어 성능 향상에 도움

lateInit

  • 필요할 때 초기화하고 사용
  • 초기화 하지 않고 사용하면 예외 발생
  • var 에만 사용 가능
  • 원시 타입에는 적용할 수 없음
  • custom getter/setter 사용 불가
  • non-null 프로퍼티만 사용 가능

lazy

  • 변수를 선언할 때 초기화 코드도 함께 정의
  • 변수가 사용될 때 초기화 코드도 동작하여 변수가 초기화 됨

0602 코드리뷰

enum class Symbol(val symbol: String) {
    DIAMOND("다이아몬드"),
    SPADE("스페이드"),
    HEART("하트"),
    CLOVER("클로버"),
  ;
}
  • 1.4부터 , 로 끝나도 컴파일 에러가 안남

Property와 Field

Field

  • 단순히 값만 가짐
  • 값을 가져오거나 변경할 때는 직접 참조
  • 함수나 블록 내부에 선언된 지역 변수는 모두 필드로 간주
var count = 100 // 메모리가 할당되고 값이 저장됨println(count) // count 변수값을 직접 참조하여 가져옴count += 200 // count 변수값을 직접 변경

Property

  • 최상위 변수(함수나 클래스 외부에 정의됨)나 클래스의 멤버 변수로 선언됨
  • 선언 시 해당 속성의 getter/ setter가 자동으로 생성됨
  • val 로 선언시 getter 만 생성됨
  • 값을 가지지만 속성의 값을 가져오거나 변경할 때는 자동으로 관련 함수가 호출됨
    • 이를 접근자 라고 함
var count = 100 // 메모리가 할당되고 값이 저장됨println(count) // count 속성의 접근자가 호출되어 속성값을 반환count += 200 // count 속성의 접근자가 호출되어 속성값을 변환

엥 근데 필드와 동일하게 코드를 작성하는데? 🤔

  • 프로그래머가 보는 관점에서는 같지만, 코틀린 컴파일러는 다르게 동작함
  • 다음과 같이 count 속성의 접근자를 자동으로 생성
  • count 속성의 값을 가져오거나 변경할 때 자동으로 호출 됨
var couhnt = 100
    get() = field
    set(value: Int) {
    field = value
  }
  • get()set() 이 접근자
fun main(args: Array<String>) {
  pro1 += pro2
  println(pro1)
}

var pro1 = 100 // 최상위 수준의 변수이므로 속성임
var pro2 = 200 // 최상위 수준의 변수이므로 속성임
  • pro2의 게터가 호출되어 값을 가져옴
  • pro1의 게터에서 반환된 값과 더함
  • 이 값이 pro1의 세터의 인자로 전달되어 pro1의 값이 변경됨
  • pro1 게터에서 반환된 값을 출력

프로퍼티에 = 을 이용해서 할당하는거랑 get을 사용해서

val shouldDraw  = cards.score()
val shouldDraw2 : Boolean
get() = cards.scroe()
  • get을 쓰는 것은 매번 돌때마다 계산이 됨
  • 프로퍼티에 접근은 계산되어 있는 값을 씀

Backing fields

  • 커스텀 getter와 setter를 제공할 경우 사용
  • 속성이 필드의 값을 필요로 할 때 코틀린은 지원 필드 키워드를 제공
  • getter와 setter 범위에서만 사용 가능
  • field 지시자를 통해 속성의 게터나 세터에서 사용
var counter = 0 // the initializer assigns the backing field directly    set(value) {        if (value >= 0)            field = value            // counter = value // ERROR StackOverflow: Using actual name 'counter' would make setter recursive    }

아래 예제의 this는 backing field가 아님

val isEmpty: Boolean    get() = this.size == 0

Backing properties

  • Backing fields의 체계에 맞지 않는 작업을 수행할 경우 이는 Backing properties가 됨
class Skills(skills: List<String> = mutableListOf()) {
    private val _skills: MutableList<String> = skills.toMutableList()
    val skills: List<String>
        get() = _skills.toList()

    fun soft(soft: String) {
        this._skills.add(soft)
    }

    fun hard(hard: String) {
        this._skills.add(hard)
    }
}

as 키워드 사용해도 되나여?

  • val results = resultBoard.values as List<GameResult>
  • 자바의 타입 변환과 같은 것

참고 자료

반응형