[Kotlin In Action] 3. 함수 정의와 호출

[Kotlin In Action] 3. 함수 정의와 호출

#1. 함수 호출

fun <T> joinToString(
        collection: Collection<T>,
        separator: String,
        prefix: String,
        postfix: String
): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
}
    result.append(postfix)
    return result.toString()
}

이름 붙인 인자

  • 자바
joinToString(collection, /* separator */ " ",  /* prefix */ " ",
            /* postfix */ ".");
  • 코틀린
joinToString(collection, separator = " ", prefix = " ", postfix = ".")

함수에 전달하는 인자 중 일부(또는 전부)의 이름을 명시할 수 있다.
호출 시 인자 중 어느 하나라도 이름을 명시하고 나면 혼동을 막기 위해 그 뒤에 오는 모든 인자는 이름을 꼭 명시해야 한다.

자바로 작성한 코드를 호출할 때는 이름 붙인 인자를 사용할 수 없다. (코틀린은 JDK 6와 호환된다.)

디폴트 파라미터 값

코틀린에서는 함수 선언에서 파라미터의 디폴트 값을 지정할 수 있으므로, 오버로드를 줄일 수 있다.

fun <T> joinToString(
        collection: Collection<T>,
        separator: String = ", ",
        prefix: String = "",
        postfix: String = ""
): Strin
코틀린 함수를 자바에서 호출하는 경우에는 그 코틀린 함수가 디폴트 파라미터 값을 제공하더라도 모든 인자를 명시해야 한다.
@JvmOverloads를 함수에 추가하면 코틀린 컴파일러가 자동으로 맨 마지막 파라미터로부터 파라미터를 하나씩 생략한 오버로딩한 자바 메서드를 추가해준다.

최상위 함수

함수를 직접 소스 파일의 최상위 수준, 클래스 밖에 위치시킬 수 있다.

JVM이 클래스 안에 들어있는 코드만을 실행할 수 있기 때문에, 컴파일할 때 새로운 클래스를 정의해준다.

package strings;
public class JoinKt { //join.kt -> 코틀린 소스 파일 이름과 자바 클래스 이름이 대응된다.
    public static String joinToString(...) { ... }
}

@file:JvmName(“StringFunctions”): 클래스 이름을 지정하는 애노테이션

최상위 프로퍼티

프로퍼티도 최상위 수준에 놓을 수 있다.
-> 정적 필드에 저장된다.

var opCount = 0
fun performOperation() {
    opCount++
    // ...
}
fun reportOperationCount() {
    println("Operation performed $opCount times")
}

최상위 프로퍼티를 활용해 코드에 상수를 추가할 수 있다.

val UNIX_LINE_SEPARATOR = "\n"

최상위 프로퍼티도 다른 프로퍼티처럼 접근자 메서드를 통해 자바 코드에 노출된다.
const 변경자를 추가하면 프로퍼티를 public static final 필드로 컴파일하게 만들 수 있다.
(primitive 타입과 String 타입만 const로 지정할 수 있다.)

#2. 확장 함수와 확장 프로퍼티

확장 함수

확장 함수는 어떤 클래스의 멤버 메서드인 것처럼 호출할 수 있지만, 그 클래스의 밖에 선언된 함수다.

  • 수신 객체 타입(receiver type): 클래스 이름
  • 수신 객체(receiver object): 확장 함수가 호출되는 대상이 되는 값(객체)

확장 함수를 사용하기 위해서는 import해야 한다.

import strings.lastChar
val c = "Kotlin".lastChar()
import strings.lastChar as last //as 키워드를 사용하면 다른 이름으로 부를 수 있다.
val c = "Kotlin".last()
  • 자바에서 확장 함수 호출 내부적으로 확장 함수는 수신 객체를 첫 번째 인자로 받는 정적 메소드이다.
char c = StringUtilKt.lastChar("Java");

확장 함수는 오버라이드할 수 없다.

수신 객체로 지정한 변수의 정적 타입에 의해 어떤 확장 함수가 호출될지 결정된다.

* 클래스의 멤버 함수 중 확장 함수와 이름 및 시그니처가 같은 함수가 있다면, 멤버 함수가 호출된다.
* 멤버 함수의 우선순위가 더 높다.

확장 프로퍼티

val String.lastChar: Char
    get() = get(length - 1)

뒷받침하는 필드(Backing field)가 없어서 기본 Getter 구현을 제공할 수 없으므로, 최소한 Getter는 꼭 정의해야 한다.
초기화 코드에서 계산한 값을 담을 장소가 없으므로, 초기화 코드도 쓸 수 없다.

var StringBuilder.lastChar: Char
    get() = get(length - 1)
    set(value: Char) {
        this.setCharAt(length - 1, value) //실제로 확장 프로퍼티는 아무 상태도 가질 수 없다.
    }

#3. 가변 인자 함수

vararg 키워드 사용

fun listOf<T>(vararg values: T): List<T> { ... }

코틀린에서는 배열을 명시적으로 풀어서 배열의 각 원소가 인자로 전달되게 해야 한다.

fun main(args: Array<String>) {
    val list = listOf("args: ", *args) //스프레드 연산자(*)를 붙이면 배열의 내용을 펼쳐준다.
    println(list)
}

#4. 값의 쌍

중위 호출

인자가 하나뿐인 메서드나 확장 함수에 중위 호출을 사용할 수 있다.
수신 객체와 유일한 메서드 인자 사이에 메서드 이름을 넣어 호출한다.
infix를 함수 선언 앞에 붙여서, 중위 호출을 허용할 수 있다.

infix fun Any.to(other: Any) = Pair(this, other)
1.to("one") //일반적인 방식의 호출
1 to "one" //중위 호출

구조 분해 선언

val (number, name) = 1 to "one" //두 변수를 즉시 초기화할 수 있다.

루프에서도 구조 분해 선언을 활용할 수 있다.

for ((index, element) in collection.withIndex()) {
    println("$index: $element")
}

#5. 문자열

3중 따옴표 (""")

3중 따옴표 안에서는 역슬래시를 포함한 어떤 문자도 이스케이프할 필요가 없다.
줄바꿈을 표현하는 아무 문자열이나 그대로 들어간다.

#6. 로컬 함수

함수에서 추출한 함수를 원 함수 내부에 중첩시킬 수 있다.
로컬 함수는 자신이 속한 바깥 함수의 모든 파라미터와 변수를 사용할 수 있다.

class User(val id: Int, val name: String, val address: String)
fun User.validateBeforeSave() {
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException(
               "Can't save user $id: empty $fieldName")
        }
    }

    validate(name, "Name")
    validate(address, "Address")
}
fun saveUser(user: User) {
    user.validateBeforeSave()
    // Save user to the database
}

한 객체만을 다루면서 객체의 비공개 데이터를 다룰 필요는 없는 함수는 확장 함수로 만들면 객체.멤버처럼 수신 객체를 지정하지 않고도 공개된 멤버 프로퍼티나 메서드에 접근할 수 있다.

Comments