본문 바로가기

Kotlin

Kotlin - 함수 (Functions)

https://kotlinlang.org/docs/functions.html

 

Functions | Kotlin

 

kotlinlang.org

코틀린 함수는 fun 키워드를 이용해서 선언한다.

fun double(x: Int): Int {
    return 2 * x
}

 

사용법 (Function usage)

// 기본 사용법
val result = double(2)

// 점(.)을 이용해서 멤버 함수를 사용
Stream().read()

 

파라미터

함수의 파라미터는 파스칼 작성법 (이름: 타입)으로 작성한다. 파라미터는 콤마로 구분하며 각 파라미터는 반드시 타입이 명시 되어야 한다. trailing 콤마 (마지막 파라미터에 붙는 콤마)가 사용 가능 하다.

fun powerOf(
    number: Int,
    exponent: Int, // trailing comma
) { /*...*/ }

 

기본 인자 (Default arguments)

함수의 파라미터는 기본 값을 가질 수 있어서, 함수 호출시 해당 파라미터를 제공 하지 않는다면 기본값을 대체된다. 기본값은 변수와 마찬가지로 "=" 연산자를 통해 지정된다.

기본 값이 있는 파라미터를 가진 함수를 재정의(override) 하는 경우 재정의 하는쪽에서는 파라미터의 기본 값을 재 지정 할수 없으며, 함수 정의 시 기본값을 생략해야 한다.

open class FunctionA {
    open fun foo(i: Int = 10) {
        println("FunctionA.foo($i)")
    }
}

class FunctionB : FunctionA() {
    override fun foo(i: Int) {
        println("FunctionB.foo($i)")
    }
}

fun main() {
    val b = FunctionB()
    b.foo() // FunctionB.foo(10)
}

만약 기본 파라미터가 기본값이 없는 파라미터 보다 먼저 선언 된다면, 기본 파라미터를 호출 생략 하기 위해서는 함수를 호출할때 인자의 이름을 같이 작성 해야 한다.

fun foo(
    bar: Int = 0,
    baz: Int,
) { /*...*/ }

foo(baz = 1) // The default value bar = 0 is used
foo(2) // 인자로 넘긴 2가 bar로 인식 된다.

기본 파라미터의 미지막 인자가 람다식 인 경우, 마지막 인자를 넘길때 인자의 이름을 같이 작성 하거나, 괄호 밖에 람다 인자를 정의 한다.

fun foo(
    bar: Int = 0,
    baz: Int = 1,
    qux: () -> Unit,
) { /*...*/ }

foo(1) { println("hello") }     // Uses the default value baz = 1
foo(qux = { println("hello") }) // Uses both default values bar = 0 and baz = 1
foo { println("hello") }        // Uses both default values bar = 0 and baz = 1

 

명명된 인자 (Named arguments)

함수를 호출할때 파라미터 이름을 같이 작성할수 있다. 이 기능은 파라미터가 많거나, 불리언 타입이나 널 처럼 값을 보고 파라미터를 유추하기 어려운 경우 유용하다. 그리고 파라미터의 이름을 같이 작성하면 파라미터의 순서는 상관 없어진다.

reformat() 함수는 총 5개의 인자가 필요하며, 그 중 4개의 파라미터는 기본 값이 정해져 있다.

fun reformat(
    str: String,
    normalizeCase: Boolean = true,
    upperCaseFirstLetter: Boolean = true,
    divideByCamelHumps: Boolean = false,
    wordSeparator: Char = ' ',
) { /*...*/ }


// 기본 값이 있는 파라미터는 생략 가능하다.
reformat("This is a long String!")

// 일부는 기본 값을, 나머지는 값을 지정하고 싶다면 파라미터 이름을 같이 작성하면 된다.
reformat("This is a short String!", upperCaseFirstLetter = false, wordSeparator = '_')

 

반환 값이 없는(Unit인) 함수 (Unit-returning functions)

반환 값이 없는 함수는 Unit 타입을 반환한다. Unit은 Unit 하나의 값만 존재하고, 이 값은 명시적으로 리턴해서는 안된다. 함수를 작성할 때 반환 타입이 Unit인 경우 반환타입을 생략해도 된다.

fun printHello(name: String?) { // 반환타입이 Unit 이기 때문에 따로 작성하지 않음
    if (name != null)
        println("Hello $name")
    else
        println("Hi there!")
    // `return Unit` or `return` is optional
}

 

단일 표현식 함수 (Single-expression functions)

함수가 단일 표현식으로 작성되는 경우 중괄호를 생략할 수 있고, 함수 헤더에 표현식을 "=" 연산자로 할당 할수 있다. 이 경우 반환 타입을 생략 할수 있다.

fun double(x: Int): Int = x * 2
fun double(x: Int) = x * 2 //반환 타입 생략

 

가변 인자 (Variable number of arguments, varargs)

함수의 파라미터를 선언 할때 vararg 수정자를 사용하면 함수를 호출할때 해당 파라미터의 인자를 여러개 입력 할 수 있다.

// ts 파라미터는 T 타입으로 가변 인자 이다. 함수 본문에서 ts는 Array로 취급된다.
fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts is an Array
        result.add(t)
    return result
}

// 호출하는 방법은 아래와 같이 값을 여러개 입력한다.
val list = asList(1, 2, 3)
val list2 = asList(9, 8, 7, 6)

하나의 파라미터만 vararg로 작성 될 수 있다. vararg로 선언된 파라미터가 함수의 마지막 파라미터가 아닌경우 함수를 호출할때 파라미터 이름을 같이 작성해야 한다.

fun varargFunction(i: Int, vararg strings: String) {
    println ("=====varargFunction=====")
    strings.forEach { println(it) }
}

fun varargFunction2(vararg strings: String, i: Int) {
    println ("=====varargFunction2=====")
    strings.forEach { println(it) }
}

fun main() {
    varargFunction(10, "1", "2", "3")
    varargFunction2("1", "2", "3", i = 10)
}

vararg 파라미터를 전달할때 값을 하나씩 개별로 작성해도 되지만 spread 연산자(*)를 이용할 수도 있다.

val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)

vararg에 원시 타입 배열을 전달하고 싶은 경우 toTypedArray() 함수를 이용해 보통타입 배열로 변경 후 전달 가능하다.

val a = intArrayOf(1, 2, 3) // IntArray is a primitive type array
val list = asList(-1, 0, *a.toTypedArray(), 4)

 

중위 표기법 (Infix notation)

infix 키워드로 표시된 함수는 점과 괄호를 생략하고 호출 할 수 있다(중위 표기가 가능하다).

infix 함수는

  • 멤버 함수 거나 확장 함수여야 한다.
  • 파라미터가 1개 여야 한다.
  • 파라미터는 가변 인자를 가져서는 안되고 기본 값을 가질 수 없다.
infix fun Int.shl(x: Int): Int { ... }

// calling the function using the infix notation
1 shl 2

// is the same as
1.shl(2)

infix 함수는 항상 receiver와 파라미터를 명확히 작성해야한다. 따라서 receiver 가 없는 경우엔 this를 꼭 작성해야 한다.

class MyStringCollection {
    infix fun add(s: String) { /*...*/ }

    fun build() {
        this add "abc"   // Correct
        add("abc")       // Correct
        //add "abc"        // Incorrect: the receiver must be specified
    }
}

 

함수 범위 (Function scope)

Kotlin은 파일의 최상위 수준에 함수를 선언 할 수 있다. 함수를 선언하기 위해 클래스를 선언할 필요가 없다. 뿐만 아니라 코틀린에서는 local 함수와 확장 함수의 선언이 가능하다.

Local functions

코틀린은 함수안에 함수인 local 함수를 지원한다. 이때 내부의 함수에서는 외부 함수의 변수에 접근이 가능하다.(closure)

fun dfs(graph: Graph) {
    val visited = HashSet<Vertex>()
    fun dfs(current: Vertex) {
        if (!visited.add(current)) return // 외부 함수의 변수(visited)에 접근 가능하다.
        for (v in current.neighbors)
            dfs(v)
    }

    dfs(graph.vertices[0])
}

 

Member function

member function 은 클래스 내부에 선언된 함수를 말한다.

class Sample {
    fun foo() { print("Foo") }
}

Sample().foo() // creates instance of class Sample and calls foo

 

Generic functions

제네릭 파라미터를 갖는 제네릭 함수도 작성 가능하다.

fun <T> singletonList(item: T): List<T> { /*...*/ }

 

Tail recursive functions

코틀린은 꼬리재귀로 알려진 함수형 프로그래밍 스타일을 지원한다. 일반적으로 루프를 사용하는 알고리즘을 구현할때 stack overflow염려 없이 재귀 함수를 사용할수 있게 된 것이다.tailrec 수정자를 함수 앞에 작성하면 컴파일러는 재귀를 최적화 해서 루프(loop) 기반 보다 더 빠르고 효율적으로 동작하게 한다.

val eps = 1E-10 // "good enough", could be 10^-15

private fun findFixPoint(): Double {
    var x = 1.0
    while (true) {
        val y = Math.cos(x)
        if (Math.abs(x - y) < eps) return x
        x = Math.cos(x)
    }
}

//위 코드를 아래처럼 작성 할 수 있다.

tailrec fun findFixPoint(x: Double = 1.0): Double =
    if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))