코틀린
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>
- 자바의 타입 변환과 같은 것
참고 자료
반응형