Binary World

함수(Functions) 본문

개발자의 길/Kotlin

함수(Functions)

모쿠 2019. 5. 30. 16:30

함수 선언(Function Declarations)

fun 키워드를 사용해서 함수를 선언한다.

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

 

 

함수 사용(Function Usage)

함수 호출은 일반적인 방법을 사용한다.

val result = double(2)

멤버 함수 호출은 점(.) 표기법을 사용한다.

Sample().foo() // 클래스의 인스턴스를 생성하고 foo()를 호출

 

매개 변수(Parameters)

함수 매개 변수 파스칼 표기법(이름 : 타입)을 사용하여 정의된다. 매개 변수는 쉼표(,)를 이용하여 구분된다. 각 매개 변수는 명시적 타입으로 선언되어야 한다.

fun powerOf(number: Int, exponent: Int) { ... }

 

기본 인자 값(Default Arguments)

함수 매개 변수는 기본 값을 가질 수 있고, 인자 값이 생략될 때 사용된다. 다른 언어와 비교해서 과부하를 더 줄일 수 있다.

fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) { ... }

기본 값은 '= after 타입'을 사용하여 정의된다. 

 

오버라이딩 메소드는 항상 기초 메소드와 같은 기본 매개 변수 값을 사용한다. 오버라이딩 메소드가 기본 매개 변수 값을 가질 때, 기본 매개 변수 값은 제거된다.

open class A {
    open fun foo(i: Int = 10) { ... }
}

class B : A() {
    override fun foo(i: Int) { ... }  // 기본 값을 허용하지 않는다.
}

기본 값이 없는 매개 변수 앞에 기본 값이 있는 매개 변수가 있는 경우, 기본 값은 명명된 매개 변수를 포함한 함수에서만 사용된다.

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

foo(baz = 1) // 기본 값 bar = 0이 사용된다.

기본 매개 변수 뒤에 마지막 매개 변수가 람다 표현식인 경우, 명명된 인자 값 또는 괄호 외부로 전달될 수 있다.

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

foo(1) { println("hello") }     // 기본 값 baz = 1 사용
foo(qux = { println("hello") }) // 기본 값 bar = 0 와 baz = 1 둘 다 사용 
foo { println("hello") }

 

명명된 인자 값(Named Arguments)

함수를 호출할 때, 매개 변수의 이름을 지정할 수 있다. 함수에 많은 수의 매개 변수 또는 기본 매개 변수가 있는 경우 매우 편리하다.

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

기본 인자 값을 사용해서 다음과 같이 호출할 수 있다.

reformat(str)

기본 값이 아닌 값으로 호출 할 때, 다음과 같이 사용한다.

reformat(str, true, true, false, '_')

명명된 인수를 사용하면 코드를 훨씬 쉽게 읽을 수 있다.

reformat(str,
    normalizeCase = true,
    upperCaseFirstLetter = true,
    divideByCamelHumps = false,
    wordSeparator = '_'
)

일부 인자 값만 사용하는 방법은 다음과 같다.

reformat(str, wordSeparator = '_')

호출 f(1, y = 2) 허용되고, f(x = 1, 2)는 허용되지 않는다. 즉, 명명된 인자 값 앞에 위치 인자 값이 있어야 한다.

 

가변 인자(vararg)는 스프레드(spread) 연산자를 사용한 명명된 형식으로 전달될 수 있다.

fun foo(vararg strings: String) { ... }

foo(strings = *arrayOf("a", "b", "c"))

참고 : 명명된 인자 값은 자바 함수 호출에서 사용할 수 없다. 자바 바이트 코드는 함수 매개 변수의 이름을 항상 유지하지 않기 때문이다.

 

단위 반환 함수(Unit-returning functions)

함수가 유용한 값을 반환하지 않으면, 리턴 타입은 Unit이 된다. Unit은 하나의 값만 있는 유형이다. 이 값을 명시적으로 반환할 필요는 없다.

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello ${name}")
    else
        println("Hi there!")
    // `return Unit` 또는 `return` 선택이다.
}

Unit 리턴 타입 또한 선택이다. 아래 코드는 위와 같다.

fun printHello(name: String?) { ... }

 

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

함수가 단일 표현을 리턴할 때, 중괄호는 생략할 수 있고 본문은 =기호에 저장된다. 

fun double(x: Int): Int = x * 2

리턴 타입이 컴파일러에 의해 추정되면, 명시적 리턴 타입 선언은 선택이다.

fun double(x: Int) = x * 2

 

명시적 리턴 타입(Explicit return types)

블록 본문을 갖는 함수는 항상 특정 리턴 타입을 명시해야 한다. 코틀린은 블록 본문이 있는 함수의 반환 형식을 추정하지 않는다. 이러한 함수는 본문에서 복잡한 제어 흐름을 가질 수 있고, 반환 형식이 명확하지 않기 때문이다.

 

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

함수의 매개 변수(보통 마지막 매개 변수)는 varargs 수식어로 표시된다.

fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts는 배열이다.
        result.add(t)
    return result
}

가변 인자가 함수에 전달되도록 허용된다.

val list = asList(1, 2, 3)

함수 내에서 T타입의 vararg-매개 변수는 T의 배열로 볼 수 있다. 위 코드에서, ts 변수는 Array<out T> 타입을 갖는다.

 

오직 하나의 매개 변수만 vararg로 표시될 수 있다. vararg 매개 변수가 목록의 마지막 매개 변수가 아니면, 다음 매개 변수의 값은 명명된 인자 값 구문을 사용하거나, 매개 변수가 함수 타입이거나, 괄호 밖에서 람다 표현식을 통과시킴으로써 전달할 수 있다.

 

vararg 함수를 호출할 때, 인자를 하나씩 전달할 수 있다. 예) asList(1, 2, 3). 이미 배열이 있고, 그 내용을 함수에 전달하려면, 스프레드 연산자(배열 접두사 *)를 사용한다.

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

 

중위 표기법(Infix notation)

infix 키워드로 표시되는 함수는 중위 표기법 사용으로 호출할 수 있다. (호출을 위해 점과 괄호를 생략)

중위 표기법은 아래 요구사항을 만족해야 한다.

  • 멤버 함수나 확장 함수이다.
  • 단일 매개 변수이다.
  • 매개 변수가 가변 인자(varargs)를 허용하지 않고, 기본 값을 갖지 않는다.
infix fun Int.shl(x: Int): Int { ... }

// 중위 표기법을 사용한 함수 호출
1 shl 2

// 위와 같은 방법
1.shl(2)

* 중위 함수의 우선순위

중위 함수는 산술 연산자, 형 변환, rangeTo 연산자 보다 우선순위가 낮다. 예시는 아래와 같다.

  • 1 shl 2 + 3 and 1 shl (2 + 3)
  • 0 until n * 2 and 0 until (n * 2)
  • xs union ys as Set<*> and xs union (ys as Set<*>)

논리 연산자보다는 우선순위가 높다. 예시는 아래와 같다.

  • a && b xor c and a && (b xor c)
  • a xor b in c and (a xor b) in c

참고 : 중위 함수는 항상 수신자와 매개 변수를 지정해줘야 한다. 중위 표기법을 사용해서 현재 수신자를 메소드를 부를 경우, this를 사용해야 한다. 정규 표현식과는 다르게 this는 제거될 수 없다.

class MyStringCollection {
    infix fun add(s: String) { ... }
    
    fun build() {
        this add "abc"   // 정확
        add("abc")       // 정확
        add "abc"        // 부정확 : 수신자를 명식해야한다.
    }
}

 

지역 함수(Local Functions)

코틀린은 지역 함수를 지원한다. 지역 함수는 다른 함수 안에 함수가 들어가는 형태다.

fun dfs(graph: Graph) {
    fun dfs(current: Vertex, visited: Set<Vertex>) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v, visited)
    }

    dfs(graph.vertices[0], HashSet())
}

지역 함수는 외부 함수의 지역 변수(즉, 클로저)에 액세스 할 수 있다.

fun dfs(graph: Graph) {
    val visited = HashSet<Vertex>()
    fun dfs(current: Vertex) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v)
    }

    dfs(graph.vertices[0])
}

 

멤버 함수(Member Functions)

멤버 함수는 클래스 또는 객체 내부에 정의되는 함수이다.

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

점 표기법을 사용해서 호출한다.

Sample().foo() // Sample 클래스의 인스턴스를 생성하고 foo를 호출

 

제네릭 함수(Generic Functions)

함수는 함수 이름 앞에 단일 꺽쇠 괄호(<>)를 사용하여 지정된 제네릭 매개 변수를 가질 수 있다.

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

 

인라인 함수(Inline Functions)

- 다른 챕터에서 작성

 

확장 함수(Extension Functions)

- 다른 챕터에서 작성

 

고차 함수와 람다 함수(Higher-Order Functions and Lambdas)

- 다른 챕터에서 작성

 

Comments