kotlin 람다 써보기

람다 써보기

익명 클래스로 리스너 구현하기

// 자바
button.setOnClickListener ( new OnClickListener() {
    @Override
    public void onClick(View view) {  /* 클릭시 수행할 동작 */ }
});

// 코틀린
button.setOnClickListener { /* 클릭시 수행할 동작 */}

람다를 사용해서 최대값 찾기

val people = listOf(Person("앤디", 38), Person("실바", 32), Person("캐슬", 42))
println(people.maxBy { it.age })

// 멤버 참조를 사용하여 컬렉션 검색
println(people.minBy(Person::age))
// 람다식 문법
{ x: Int, y: Int -> x + y }

// 람다로 sum 함수 만들기
val sum = {x: Int, y: Int -> x + y}
println(sum(1,2))

run : 인자로 받은 람다를 실행해 주는 라이브러리 함수 이다.

run { println(42) }

people.maxBy 리팩토링 하기

// 연장자 찾기 정석 버전
people.maxBy({ p: Person -> p.age })

//가장 뒤에 있는 파라메터 람다는 밖으로 빼낼 수 있다.
people.maxBy() { p: Person -> p.age }

// 람다가 유일한 파라메터인 경우 괄호 삭제해도 됨
 people.maxBy { p: Person -> p.age }

// 파라메터 타입 생략 하기  : 컴파일러가 타입을 자동 유출함. 못하는 경우도 있는데 그경우만 명시해주면 됨
people.maxBy { p -> p.age }

// 람다의 파라메터가 하나뿐이고 그 타입을 컴파일러가 추론할 수 있는 경우 it이라는 파라메터 명을 사용할 수 있음
people.maxBy { it.age }

// 람다를 변수에 저장할 때에는 파라메터 타입으 추론할 문맥이 존재하지 않음. 그러므로 파라메터 타입을 생략하면 안됨
getAge = { p: Person -> p.age }
people.maxBy(getAge)

함수의 파라메터를 람다에서 사용가능

fun printMessageWithPrefix(messages: Collection<String>, prefix:String) {
    messages.forEach(
        println("$prefix $it")
    )
}

// output 
Error :  403 forbidden
Error :  404 not found

fun printProblemCounts(responses: Collection<String>) {
    var clientErrors = 0
    var serverErrors = 0
    responses.forEach {
        if (it.startsWith("4")) {
            clientErrors++
        } else if (it.startsWith("5")) {
                        serverErrors++
        }
    }
    println("$clientErrors client errors, $serverErrors server errors")
}


>>> val responses = listOf("200 OK", "418 I'm a teepot", "500 internal server error")
>>> printProblemCounts(responses)
// output
1 client errors, 1 server errors
fun tryToCountButtonClick(button: Button): Int {
    var clicks = 0
    button.Click { clicks++ }
    return clicks
}

멤버 참조

val getAge = Person::age

// 요거랑 동일함
val getAge = {person: Person -> person.age }

people.maxBy(Person::age)

// 탑레벨 멤버 레퍼런스 `::` 앞의 클래스를 생략
fun salute() = println("Salute!")
>>> run(::salute)
Salute!
val action = { person: Person, message: String -> sendEmail(person, message)}

val nextAction = ::sendEmail
data class Person(val name: String, val age: Int)

>>> val createPerson = ::Person
>>> val p = createPerson("Andy", 38)
>>> println(p)
Person(name=Andy, age=38)
fun Person.isAdult() = age >= 21
val predicate = Person::isAdult

isAdult는 Person의 멤버가 아니지만, 멤버처럼 사용할 수 있다.

컬렉션을 위한 함수형 API

필터와 맵

data class Person(val name: String, val age: Int)

>>> val list = listOf(1, 2, 3, 4)
>>> println(list.filter { it % 2 == 0 })

[2, 4]

연산자 오버로딩과 관례

산술 연산자 오버로딩

data class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point): Point { 
        return Point(x + other.x, y + other.y)
    }
}

>>> val p1 = Point(10, 20)
>>> val p2 = Point(30, 40)
>>> println(p1 + p2)
output

Point(x=40, y=60)
operator fun Point.plus(other: Point): Point {
    return Point(x + other.x, y + other.y)
}

오버로딩 가능한 산술 연산자

함수 이름
a * btimes
a / bdiv
a % brem
a + bplus
a - bminus
a += bplusAssign
-aunaryMinus
+aunaryPlus
!anot
++a, a++inc
--a, a--dec
a == bequals
a < b, a > b, a <= b, a >= bcompareTo
arr[1]get
arr[1] = 'a'set
..rangeTo
for a in arriterator
val (x, y) = pcomponent
class Point(val x: Int, val y: Int) {
    operator fun component1() = x
    operator fun component2() = y
}

코틀린은 비트연산자를 지원하지 않음

자바코틀린
<<shl
>>shr
>>>ushr
&and
|or
^xor
~inv

위임 프로퍼티 (delegated property)

문법

class Foo {
    var p: Type by Delegate()
}

ex)

class Foo {
    private val delegate = Delegate()
    var p: Type
    set(value: Type) = delegate.setValue(..., value)
    get() = delegate.getValue(...)
}

지연 초기화를 위임 프로퍼티로 구현

class Person(val name: String) {
    val emails by lazy { loadEmails(this) }
}

헬퍼클래스를 만들어서 프로퍼티 변경 통지 만들기

class ObservableProperty(
    val propName: String, var propValue: Int,
    val changeSupport: PropertyChangeSupport
) {
    fun getValue(): Int = propValue
    fun setValue(newValue: Int) {
        val oldValue = propValue
        propValue = newValue
        changeSupport.firePropertyChange(propName, oldValue, newValue)
    }
}

class Person(
    val name: String, age: Int, salary: Int
) : PropertyChangeAware() {
    val _age = ObservableProperty("age", age, changeSupport)
    var age: Int
        get() = _age.getValue()
        set(value) { _age.setValue(value)}
    val _salary = ObservableProperty("salary", salary, changeSupport)
    var salary: Int 
        get() = _salary.getValue()
        set(value) { _salary.setValue(value)}
}

ObservableProperty를 프로퍼티 위임으로 사용할 수 있도록 리팩토링

class ObservableProperty(
    var propValue: Int, val changeSupport: PropertyChageSupport
) {
    operator fun getValue(p: Person, prop: KProperty<*>): Int = propValue
    operator fun setValue(p: Person, prop: KProperty<*>, newValue: Int) {
        val oldValue = propValue
        propValue = newValue
        changeSupport.firePropertyChange(prop.name, oldValue, newValue)
    }
}

class Person(
    val name: String, age: Int, salary: Int
) : PropertyChangeAware() {
    var age: Int by ObservableProperty(age, changeSupport)
    var salary: Int by ObservableProperty(salary, changeSupport)
}

Delegates.observable 표준 라이브러리를 사용하기.

class Person(
    val name: String, age: Int, salary: Int
) : PropertyChangeAware() {
    private val observer = {
        prop: KProperty<*>, oldValue: Int, newValue: Int -> 
            changeSupport.filrePropertyChange(prop.name, oldValue, newValue)
    }
    var age: Int by Delegates.observable(age, observer)
    var salary: Int by Delegates.observable(salary, observer)
}

8장 고차함수 : 파라메터와 리턴값으로 람다 사용하기

함수타입 선언하기

val sum = {x: Int, y: Int -> x + y}
val print = { println(42) }

(파라메터 타입, 파라메터 타입) -> 리턴 타입 이런식으로 좀 더 명시적으로 할 수 있다.

val sum: (Int, Int) -> Int = {x, y -> x + y}
val action: () -> Unit = {println(42)}

리턴타입이 null 이 되는 경우에는 아래와 같은 식으로 한다.

var canReturnNull: (Int, Int) -> Int? = { null }

인라인 함수 : 람다의 부가 비용 없애기

고차 함수 안에서 흐름제어

data class Person(val name:String, val age: Int)
val people = listOf(Person("Alice", 29), Person("Bob", 31))

fun lookForAlice(people: List<Person>) {
    for (person in people) {
        if (person.name == "Alice") {
            println("Found!")
            return
        }
    }
    println("Not found")
}

>>> lookForAlice(people)
Found!
fun lookForAlice(people: List<Person>) {
    people.forEach {
        if (it.name == "Alice" ) {
            println("Found!")
            return
        }
    }
    println("Not Found!")
}
fun lookforAlice(people: List<Person>) {
    people.forEach label@ {
        if (it.name == "Alice") return@label
    }
    println("Alice might be somewhere")
}
fun lookforAlice(people: List<Person>) {
    people.forEach {
        if (it.name == "Alice") return@forEach
    }
    println("Alice might be somewhere")
}

람다 말고 코드 볼록을 넘기는 방법으로 익명함수를 넘기는 방법이 있다.

fun lookforAlice(people: List<Person>) {
    people.forEach (fun (person) {
        if (person.name == "Alice") return // 가장가까운 함수를 가리킴
        println("${people.name} is not Alice")
    })
}