https://product.kyobobook.co.kr/detail/S000201206714
아토믹 코틀린 | 브루스 에켈 - 교보문고
아토믹 코틀린 | 코틀린을 익히고 레벨업하는 가장 확실한 방법! 한 번에 하나씩 + 명확한 예제를 실행하면서 + 훌륭한 멘토의 설명으로 배워라!코틀린의 기본 개념을 명확히 이해하고, 더 나은
product.kyobobook.co.kr
7부 파워 툴
79. 확장 람다
- 확장 람다는 확장 함수와 비슷, 차이는 함수가 아니라 람다라는 점
package extensionlambdas
import atomictest.eq
val va: (String, Int) -> String = { str, n ->
str.repeat(n) + str.repeat(n)
}
val vb: String.(Int) -> String = {
this.repeat(it) + repeat(it)
}
fun main() { // 확장 람다 사용한 코드
va("Vanbo", 2) eq "VanboVanboVanboVanbo"
"Vanbo".vb(2) eq "VanboVanboVanboVanbo"
vb("Vanbo", 2) eq "VanboVanboVanboVanbo"
// "Vanbo".va(2) // 컴파일되지 않음
}
val zero: Int.() -> Boolean = {
this == 0
}
val one: Int.(Int) -> Boolean = {
this % it == 0
}
val two: Int.(Int, Int) -> Boolean = {
arg1, arg2 ->
this % (arg1 + arg2) == 0
}
val three: Int.(Int, Int, Int) -> Boolean = {
arg1, arg2, arg3 ->
this % (arg1 + arg2 + arg3) == 0
}
fun main2() { // 파라미터 사용한 코드
0.zero() eq true
10.one(10) eq true
20.two(10, 10) eq true
30.three(10, 10, 10) eq true
}
class A {
fun af() = 1
}
class B {
fun bf() = 2
}
fun f1(lambda: (A, B) -> Int) =
lambda(A(), B())
fun f2(lambda: A.(B) -> Int) =
A().lambda(B())
fun main3() { // 함수의 파라미터 사용한 코드
f1 { aa, bb -> aa.af() + bb.bf() }
f2 { af() + it.bf() }
}
- 확장 람다의 반환 타임이 Unit 이면, 람다 본문이 만들어낸 결과 무시
package extensionlambdas
fun unitReturn(lambda: A.() -> Unit) =
A().lambda()
fun nonUnitReturn(lambda: A.() -> String) =
A().lambda()
fun lambdaUnitReturn () {
unitReturn {
"Unit ignores the return value" +
"So it can be anything ..."
}
unitReturn { 1 } // ... 임의의 타입 ...
unitReturn { } // ... 아무 값도 만들어내지 않는 경우
nonUnitReturn {
"Must return the proper type"
}
// nonUnitReturn { } // 이렇게 쓸 수 없음
}
- 확장 람다를 사용해서 빌더 작성하기
- 빌더 패턴 장점
- 객체 생성을 여러 단계로 나누면 객체 생성이 복잡할 때 유용
- 동일한 기본 생성 코드를 사용해 다양한 조합의 객체 생성
- 공통 생성 코드와 특화한 코드 분리 가능. 객체들의 변종 유형에 따른 코드를 더 쉽게 작성 가능. 가독성 증가.
- 빌더 패턴 장점
package sandwich
import atomictest.eq
open class Recipe : ArrayList<RecipeUnit>()
open class RecipeUnit {
override fun toString() =
"${this::class.simpleName}"
}
open class Operation : RecipeUnit()
class Toast : Operation()
class Grill : Operation()
class Cut : Operation()
open class Ingredient : RecipeUnit()
class Bread : Ingredient()
class PeanutButter : Ingredient()
class GrapeJelly : Ingredient()
class Ham : Ingredient()
class Swiss : Ingredient()
class Mustard : Ingredient()
open class Sandwich : Recipe() {
fun action(op: Operation): Sandwich {
add(op)
return this
}
fun grill() = action(Grill())
fun toast() = action(Toast())
fun cut() = action(Cut())
}
fun sandwich(
fillings: Sandwich.() -> Unit
): Sandwich {
val sandwich = Sandwich()
sandwich.add(Bread())
sandwich.toast()
sandwich.fillings()
sandwich.cut()
return sandwich
}
fun main() {
val pbj = sandwich {
add(PeanutButter())
add(GrapeJelly())
}
val hamAndSwiss = sandwich {
add(Ham())
add(Swiss())
add(Mustard())
grill()
}
pbj eq "[Bread, Toast, PeanutButter, " +
"GrapeJelly, Cut]"
hamAndSwiss eq "[Bread, Toast, Ham, " +
"Swiss, Mustard, Grill, Cut]"
}
80. 영역 함수
- 영역 함수는 객체의 이름을 사용하지 않아도 그 객체에 접근할 수 있는 임시 영역을 만들어주는 함수
- 오로지 코드를 더 간결하고 가독성을 높이기 위해 존재, 추가 기능은 없음
- let(), run(), with(), apply(), also()라는 5가지 영역 함수
- 각각 람다와 함께 사용 되고 임포트 필요 없음
- 문맥 객체를 this로 접근할 수 있는 영역 함수(run(), with(), apply()) 를 쓰면 영역 블록 안에서 가장 깔끔한 구문 사용 가능
- 문맥 객체를 it 으로 접근할 수 있는 영역 함수(let(), also()) 에서는 람다 인자에 이름을 붙임 가능
- 결과를 만들어야 하는 경우, 람다의 마지막 식의 값을 돌려주는 영역 함수(let(), run(), with()) 를 사용 가능
- 객체에 대한 호출 식을 연쇄적으로 사용해야 하는 경우, 변경한 객체를 돌려주는 영역 함수(apply() 와 also()) 를 사용
- run()은 확장 함수, with()는 일반 함수, 이 특징을 제외하면 두 함수는 같은 일을 한다.
- 수신 객체가 널이 될 수 있거나 연쇄 호출이 필요한 경우에 run() 사용
package scopefunctions
import atomictest.eq
data class Tag(var n: Int = 0) {
var s: String = ""
fun increment() = ++n
}
fun main() {
// let(): 객체를 'it'으로 접근하고
// 람다의 마지막 식의 값을 반환한다
Tag(1).let {
it.s = "let: ${it.n}"
it.increment()
} eq 2
// let()을 사용하면서 람다 인자에 이름을 붙인 경우다
Tag(2).let { tag ->
tag.s = "let: ${tag.n}"
tag.increment()
} eq 3
// run(): 객체를 'this'로 접근하고
// 람다의 마지막 식의 값을 반환한다
Tag(3).run {
s = "run: $n" // 암시적 'this'
increment() // 암시적 'this'
} eq 4
// with(): 객체를 'this'로 접근하고
// 람다의 마지막 식을 반환한다
with(Tag(4)) {
s = "with: $n"
increment()
} eq 5
// apply(): 객체를 'this'로 접근하고
// 변경된 객체를 다시 반환한다
Tag(5).apply {
s = "apply: $n"
increment()
} eq "Tag(n=6)"
// also(): 객체를 'it'으로 접근하고
// 변경된 객체를 다시 반환한다
Tag(6).also {
it.s = "also: ${it.n}"
it.increment()
} eq "Tag(n=7)"
// also()에서도 람다의 인자에 이름을 붙일 수 있다
Tag(7).also { tag ->
tag.s = "also: ${tag.n}"
tag.increment()
} eq "Tag(n=8)"
}
- 영역함수 특성 정리표
return 값 | this 문맥 객체 | it 문맥 객체 |
마지막 식의 값을 돌려줌 | with, run | let |
수신 객체를 돌려줌 | apply | also |
- let(), run(), apply(), also() 에 대한 안전한 접근 연산자(?.)를 쓰는 수신 객체가 null 인 경우 전체 영역이 무시
- 영역 함수는 인라인 된다
- 영역 함수를 inline 으로 사용하면 모든 실행 시점 부가 비용 X, 그래서 영역 함수를 주저하지 않고 원하는 대로 사용 가능
81. 제네릭스 만들기
- 제네릭스는 ‘나중에 지정할’ 타입에 대해 작동하는 코드
- Any : 코틀린 클래스 계층의 루트
- 방법 1 : Any 에 대해서만 연산 수행, 다른 타입 요구 X. 극히 제한적(equals(), hashCode(), toString())
- 방법 2: 확장함수를 사용할 수 있으나 Any 타입 객체에 대해 직접 연산 적용 불가.
- 제네릭스 정의하기
- 중복된 코드는 제네릭 함수나 타입으로 변환하는 것을 고려해볼 만하다.
package creatinggenerics
fun <T> gFunction(arg: T): T = arg
class GClass<T>(val x: T) {
fun f(): T = x
}
class GMemberFunction {
fun <T> f(arg: T): T = arg
}
interface GInterface<T> {
val x: T
fun f(): T
}
class GImplementation<T>(
override val x: T
) : GInterface<T> {
override fun f(): T = x
}
class ConcreteImplementation
: GInterface<String> {
override val x: String
get() = "x"
override fun f() = "f()"
}
fun basicGenerics() {
gFunction("Yellow")
gFunction(1)
gFunction(Dog()).bark() // [1]
gFunction<Dog>(Dog()).bark()
GClass("Cyan").f()
GClass(11).f()
GClass(Dog()).f().bark() // [2]
GClass<Dog>(Dog()).f().bark()
GMemberFunction().f("Amber")
GMemberFunction().f(111)
GMemberFunction().f(Dog()).bark() // [3]
GMemberFunction().f<Dog>(Dog()).bark()
GImplementation("Cyan").f()
GImplementation(11).f()
GImplementation(Dog()).f().bark()
ConcreteImplementation().f()
ConcreteImplementation().x
}
- 타입 변성
package variance
class Box<T>(private var contents: T) {
fun put(item: T) { contents = item }
fun get(): T = contents
}
class InBox<in T>(private var contents: T) {
fun put(item: T) { contents = item }
}
class OutBox<out T>(private var contents: T) {
fun get(): T = contents
}
- Box<T> 무공변(invariant)
- Box<Cat> 과 Box<Pet> 사이에 아무런 하위 타입 관계 X
- 둘 중 어느 쪽도 반대쪽으로 대입 불가
- OutBox<out T> 공변(variant)
- OutBox<Cat> 을 OutBox<Pet> 으로 업캐스트하는 방향이 Cat을 Pet으로 업캐스트하는 방향과 같은 방향으로 변함
- InBox<in T> 반공변(contravariant)
- InBox<Pet> 이 InBox<Cat> 의 하위 타입, InBox<Pet> 을 InBox<Cat> 으로 업캐스트 하는 방향이 Cat을 Pet으로 업캐스트하는 방향과 반대 방향으로 변함
- 좀 더 자세한 내용은 다른 책으로 또는 Documentation 으로 확인 필요
82. 연산자 오버로딩
- 이미 존재하는 어떤 대사ㅔ 추가로 의미를 더하는 것
- 역작은따옴표로 감싼 함수 이름
- 해당 기능은 사용할 때, 이해할 수 없는 코드를 쉽게 만들어 낼 수 있으므로 조심해서 사용할 것
package operatoroverloading
import atomictest.eq
infix fun String.`#!%`(s: String) =
"$this Rowzafrazaca $s"
fun main() {
"howdy" `#!%` "Ma'am!" eq
"howdy Rowzafrazaca Ma'am!"
}
83. 연산자 사용하기
- 연산자 오버로드는 직접 라이브러리 생성시 사용
84. 프로퍼티 위임
- 프로퍼티는 접근자 로직 위임 가능
- val(또는 var) 프로퍼티이름 by 위임객체이름
85. 프로퍼티 위임 도구
- 표준 라이브러리에는 특별한 프로퍼티 위임 연산이 들어 있음
86. 지연 계산 초기화
- 프로퍼티 초기화 방법
- 방법1. 프로퍼티를 정의하는 시점이나 생성자 안에서 초기값을 저장
- 방법2. 프로퍼티에 접근시 값을 계산하는 커스텀 Getter 를 정의
- 방법3. 초기값 계산 비용이 많이 들지만, 프로퍼티 선언하는 시점에 즉시 필요하지 않거나 아예 전혀 필요하지 않을 수도 있는 경우
- 복잡하고 오래 걸리는 계산
- 네트워크 요청
- 데이터베이스 접근
- 문제점
- 애플리케이션 초기 시작 시간이 길어질 수 있음
- 전혀 사용하지 않거나 나중에 계산해도 될 프로퍼티 값을 계산하기 위해 불필요한 작업을 수행 가능
- 해법
- 코틀린에서 제시하는 해법은 지연 계산 프로퍼티
- 생성시점이 아니라 처음 사용할 때 초기화
- 프로퍼티 값을 읽기 전까지는 결코 비싼 초기화 계산을 수행 X
- val lazyProperty by lazy { 초기화 코드 }
- 코틀린에서 제시하는 해법은 지연 계산 프로퍼티
package lazyinitialization
import atomictest.*
val idle: String by lazy {
trace("Initializing 'idle'")
"I'm never used"
}
val helpful: String by lazy {
trace("Initializing 'helpful'")
"I'm helping!"
}
fun main() {
trace(helpful)
trace eq """
Initializing 'helpful'
I'm helping!
"""
}
87. 늦은 초기화
- 때로는 by lazy()를 사용하지 않고 별도의 멤버 함수에서 클래스의 인스턴스가 생성된 다음에 프로퍼티를 초기화하고 싶은 경우 있음
- 제약 사항
- lateinit 은 var 프로퍼티에만 적용가능, val에 는 적용 불가
- 프로퍼티의 타입은 널이 아닌 타입이어야 함
- 프로퍼티가 원시 타입의 값이 아니어야 함
- 추상 클래스의 추상 프로퍼티나 인스턴스의 프로퍼티에 lateinit 적용 불가
- 커스텀 Gettter 및 Setter 를 지원하는 프로퍼티에 lateinit을 적용 불가
- 제약 사항
package lateinitialization
import atomictest.eq
class BetterSuitcase : Bag {
lateinit var items: String
override fun setUp() {
items = "socks, jacket, laptop"
}
fun checkSocks() = "socks" in items
}
fun main() {
val suitcase = BetterSuitcase()
suitcase.setUp()
suitcase.checkSocks() eq true
}
'Study' 카테고리의 다른 글
[Book] Kafka IN ACTION - 05, 06장. 컨슈머: 데이터 열기, 브로커 (0) | 2024.09.05 |
---|---|
[Book] Kafka IN ACTION - 03장. 카프카 프로젝트 설계 (0) | 2024.09.05 |
[Book] 아토믹 코틀린 - 5부 객체 지향 프로그래밍 - 2 (0) | 2024.09.05 |
[Book] 아토믹 코틀린 - 4부 함수형 프로그래밍 (0) | 2024.09.05 |
[Book] 아토믹 코틀린 - 2부 객체 소개 - 2 (0) | 2024.09.05 |