들어가면서

Kotlin을 공부하면서 새로 알게되는 부분을 정리합니다.

Kotlin 람다식 예시

람다식으로 표현하는 방식과 익명함수로 표현하는 방식 2가지가 있음

fun compute(num1: Int, num2: Int, op: (Int,Int) -> Int): Int{
    return op(num1, num2)
}

fun main() {
    // 람다식
    println(compute(3,5) {a,b -> a+b})

    // 익명함수
    compute(5,3, fun(a:Int,b:Int) = a + b)
}

위의 방식을 함숫값, 함수 리터럴이라고 한다.

람다식은 반환 타입을 적을수 없고, 익명함수는 반환타입을 적을수 있다.


fun main() {
    iterate(listOf(1,2,3,4,5), fun(num){
        if(num == 3){
            return
        }
        println(num)
    })
}

fun iterate(numbers: List<Int>, exec:(Int) -> Unit){
    for(number in numbers){
        exec(number)
    }
}

람다식 에선 하위 코드가 작성이 안됨 -> return을 쓸수 없다.

fun main() {
    iterate(listOf(1,2,3,4,5)){
        num -> if(num == 3) return //return은 가장 가까운 fun을 종료하는 명령어
        println(num)
    }
}

함수 파라미터의 기본값 응용

좀더 객체 지향적으로 작성하면 아래처럼 응용할 수 있다. Java에서는 비슷한 구조를 위해 BiFunction 인터페이스를 사용하지만, Kotlin에선 함수가 1급 시민이니, 함수를 바로 사용할 수 있다.

fun main() {
    println(calculate(1,5, Operator.PLUS))
}

fun calculate(num1:Int, num2: Int, oper:Operator) = oper.calcFun(num1,num2)

enum class Operator(
    private val oper: Char,
    val calcFun: (Int, Int) -> Int,
){
    PLUS('+', {a,b->a+b}),
    MINUS('-', {a,b->a-b}),
    MULTIPLY('*', {a,b->a*b}),
    DIVIDE('/', {a,b ->
            if(b == 0) throw IllegalArgumentException()
            else a/b
    })
}

확장함수의 타입

확장 함수를 사용하는 케이스는 아래와 같이 표현 가능 함수를 변수처럼 사용할때마다 FunctionN이 늘어남

fun main() {
    val add = fun Int.(other: Long): Int = this + other.toInt()
    println(add.invoke(1,10)) // 11
    println(add(1,10)) // 11
    1.add(10L) // 11
}

고차함수의 단점

오버헤드가 존재함. -> inline 함수로 극복 가능

var num = 5
    num += 1
    val plusOne:() -> Unit = { num += 1 } // closure를 쓰는 경우엔 컴파일이 다름
    /*  Ref.IntRef의 element 객체를 바꾸는 식으로 바꿈 */

inline 함수

함수를 호출하는 쪽에 함수 본문을 붙여넣게 된다. -> 미리 변환 할수 있는 영역을 변환시켜놔서 컴파일러 성능을 향상시킴

다른 함수까지 inline을 시키는데, 강제로 인라인을 막을수도 있음

fun main() {
    repeat(2) { println("Hello World") }
}

inline fun repeat(
    times: Int,
    noinline exec: () -> Unit
){
    for( i in 1..times){
        exec()
    }
}

non-local-return을 사용할수있게 해줌

fun main() {
    iterate(listOf(1,2,3,4,5)){ num ->
        if( num == 3){
            return // main함수를 리턴함 .. 1,2
        }
        println(num)
    }
}
inline fun iterate(numbers: List<Int>, exec:(Int) -> Unit){
    for(number in numbers){
        exec(number)
    }
}

inline 함수의 함수 파라미터에서 non-local return을 금지시키는법?

crossinline

SAM과 reference

Single Abstract Method

  • 추상 메소드를 하나만 갖고 있는 인터페이스 자바에서는 SAM을 람다로 바꿀수 있음. but koltin에선 불가
@FunctionalInterface
public interface Runnable{
    public abstract void run();
}

변수명에 넣으려면 SAM 이름 + 람다식을 같이 넣어줘야됨. 근데 파라미터로 바로 쓸거면 상관없음

fun main() {
    val filter: StringFilter = StringFilter { str -> str?.startsWith("A") ?: false }
}
// ...

fun main() {
    consumeFilter({s -> s.startsWith("A")})
}

fun consumeFilter(filter: StringFilter) {}

기본적으로 SAM 인터페이스로 추정할수 있는 후보가 많을 경우엔, 구체화된 후보로 추정함 아닌 경우, 구체적으로 붙여주면됨

fun main() {
    consumeFilter({s:String -> s.startsWith("A")})
}

fun <T> consumeFilter(filter: Filter<T>) {} 

kotlin에선 SAM을 이렇게 만들 수 있으나, 1급 시민이여서 굳이 이렇게 만들 필요가 없음

fun main() {
    KStringFilter { it.startsWith("A") }
}

fun interface KStringFilter {
    fun predicate(str: String): Boolean
}

reference

Java와 Kotlin의 호출 가능 참조 차이점 Java에선 호출 가능 참조 결과값이 Consumer / Supplier 같은 함수형 인터페이스이지만, Kotlin에선 reflection 객체이다.