본문 바로가기
Kotlin

Kotlin in Action #3. 함수 정의와 호출

by jayden-lee 2020. 2. 23.
728x90

'Kotlin in Action' 책을 학습하고 정리한 내용입니다.

3. 함수 정의와 호출

코틀린에서 함수 정의와 호출하는 방법에 대해서 살펴보는 것을 중점으로 한다. 그리고 더 나아가 확장 함수 및 프로퍼티를 사용함으로써 코틀린이 자바와 함께 사용했을 때 어떤 장점이 있는지 알아보자.

코틀린에서 컬렉션 만들기

코틀린에서 set, list, map 컬렉션 객체를 생성하고 어떤 클래스에 속하는지 출력하면 익숙한 텍스트를 볼 수 있다.

 

fun main(args: Array<String>) {
    var set = hashSetOf(1, 5, 10)
    var list = arrayListOf(1,2,3)
    var map = hashMapOf(1 to "one", 3 to "three", 5 to "five")

    println(set)
    println(list)
    println(map)
}

 

출력 결과 화면

 

class java.util.HashSet
class java.util.ArrayList
class java.util.HashMap

 

코틀린은 자체 컬렉션 기능을 제공하지 않고 자바의 컬렉션을 가져와서 사용한다. 표준 자바 컬렉션을 가져와서 사용하기 때문에 기존에 작성된 자바 코드와 상호작용하기가 쉽다. 그리고 컬렉션을 코틀린 <-> 자바로 변경할 필요가 없는 장점이 있다. 자바와 동일한 컬렉션 클래스이지만 코틀린에서의 컬렉션은 더 많은 기능을 지원한다.

함수 이름 붙인 인자

코틀린에서는 함수를 호출할 때 인자 중 일부 이름을 직접 명시할 수 있다.

 

다음과 같은 joinToString 함수가 정의되어 있다.

 

fun joinToString(separator: String, prefix: String, postfix: String): String {
    // ...
}

 

함수를 호출하기 위해서 시그니처를 외우거나 IDE의 도움을 받으면 쉽게 작성이 가능하다. 이럴 때, 함수 인자 이름을 명시해서 가독성 있게 코드를 작성할 수 있다.

 

joinToString(separator=",", prefix = "[", postfix = "]")

디폴트 파라미터 값

자바에서 동일한 이름의 메서드에 파라미터 개수를 다르게 해서 여러 기능을 제공하는 것을 오버로딩 메서드라고 한다. 오버로딩은 비슷한 형태의 시그니처를 가진 메서드가 많아지는 단점이 있다. 코틀린에서는 함수 선언에 파라미터의 디폴트 값을 지정해서 오버로딩 함수 개수를 줄일 수 있다.

 

fun joinToString(separator: String = ", ", prefix: String = "", postfix: String = ""): String {
    // ...
}

 

자바에는 디폴트 파라미터 값 개념이 없기 때문에 코틀린 함수를 호출할 때 모든 인자를 명시해야 한다. 자바에서 코틀린 함수를 자주 호출한다면 @JvmOverloads 애노테이션을 함수에 추가해야 한다. 코틀린 컴파일러는 애노티에션이 붙은 함수를 확인하고 파라미터를 하나씩 생략한 오버로딩한 자바 메서드를 생성해준다.

최상위 함수와 프로퍼티

자바는 메서드를 모두 클래스 안에 넣어서 작성한다. 흔히 유틸 클래스의 경우에 클래스의 접근 제한자를 private을 정의하고 내부 상태값은 가지지 않으며, 선언된 메서드 모두 static 키워드를 붙여 정적 메서드만 정의한다. 코틀린은 클래스 내부에 속하지 않은 함수를 선언할 수 있다.

 

join.kt라는 파일에 패키지를 명시하고 최상위에 joinToString 함수를 정의했다.

 

package strings

fun joinToString(...): String { ... }

 

패키지의 멤버 함수를 외부에서 호출하고 싶을 때는 패키지를 임포트하고 사용하면 된다. 위에 코드는 클래스가 없이 선언되어 있지만, 컴파일러에 의해 새로운 클래스 안에 있는 함수로 컴파일 된다. 이 때 사용하는 클래스 이름은 최상위 함수가 들어있던 코틀린 소스 파일의 이름과 대응한다.

 

package strings

public class JoinKt {
    public static String joinToString(...) { ... }
}

 

이전 알아본 최상위 함수처럼 프로퍼티도 파일의 최상위 수준에 놓일 수 있다.

 

var opCount = 0

const val UNIX_LINE_SEPARATOR = "\n"

fun performOperation() {
    opCount++
}

확장 함수와 확장 프로퍼티

확장 함수

확장 함수란 어떤 클래스의 멤버 메서드인 것처럼 호출할 수 있도록 정의한 함수를 의미한다. 확장 함수는 해당 클래스 안에가 아닌 밖에서 정의되어 있다.

 

String 문자열 확장 함수 예제를 작성해보자. 이 함수는 문자열 마지막 문자를 돌려주는 메서드이다.

 

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

println("Kotlin".lastChar())

 

확장 함수를 만들기 위해서는 함수 이름 앞에 확장하고자 하는 클래스의 이름(String)을 명시한다. 그리고 확장 함수 내부에서 this는 확장 함수가 호출되는 대상이 되는 값(Kotlin 문자열)을 나타낸다.

 

확장 함수는 오버라이드 할 수 없다. 컴파일 시점에 알려진 변수 타입에 따라 정해진 메서드를 호출하는 방식인 정적 디스패치이기 떄문이다.

as 키워드

한 클래스에 같은 이름의 확장 함수가 둘 이상이 있는 경우에 이름이 충돌할 수 있다. 전체 이름을 사용하면 구별이 가능하지만 그렇게 되면 소스 코드가 길어지는 문제점이 있다. 굳이 패키지 이름의 모든 이름 알 필요가 없기 때문이다.

 

이러한 경우에 as 키워드를 사용하면 임포트한 클래스나 함수를 다른 이름으로 호출해서 사용할 수 있다.

 

import strings.lastChar as last

val c = "Kotlin".last()

확장 프로퍼티

확장 프로퍼티를 사용하면 기존 클래스 객체에 대한 프로퍼티 형식의 구문을 사용할 수 있는 API를 추가할 수 있다. 프로퍼티라는 이름으로 불리긴 상태를 저장할 수 없다.

 

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

가변 인자 함수

간단한 리스트를 생성하기 위해서 listOf 함수를 사용했다. 이 함수에는 원소를 원하는 크기만큼 전달할 수 있다. 자바에서는 이러한 가변 인자를 다루기 위해서 메서드 파라미터 마지막에 ... 키워드를 사용한다.

 

val list = listOf(1, 3, 4, 9, 10)

 

listOf 함수가 어떻게 정의되어 있는지 살펴보자. vararg 키워드를 파라미터 앞에 붙임으로써 가변 길이 인자로 만들 수 있다.

 

public fun <T> listOf(vararg elements: T): List<T> = if (elements.size > 0) elements.asList() else emptyList()

 

가변 길이 인자에 배열 값을 넣을 때 자바에서는 단순히 배열을 넘겨도 된다. 코틀린에서는 이 부분이 자바와는 다르다. 배열을 명시적으로 풀어서 배열의 각 원소가 인자로 전달될 수 있도록 스프레드 연산자를 붙여야 한다.

 

fun main(args: Array<String>) {
    val list = listOf(*args)
}

중위 호출과 구조 분해 선언

맵 객체를 만들기 위해서 mapOf 함수를 사용한다. 여기서 to는 특별한 키워드가 아닌 함수이다. 이 코드는 중위 호출이라는 특별한 방식으로 to라는 일반 메서드를 호출한 것이다.

 

val map = mapOf(1 to "one", 5 to "five", 6 to "six")

 

중위 호출 시에는 수신 객체(1)와 유일한 메서드 인자(one) 사이에 메서드 이름(to)을 넣는다. 코드를 보면 이해하기 쉬울 것이다.

 

1.to("one") // 일반적인 호출
1 to "one" // 중위 호출

 

to 함수는 다음과 같이 구현되어 있다고 하자. 인자가 하나뿐인 일반 메서드 또는 확장 함수에 중위 호출을 사용할 수 있다. 중위 호출을 허용하고 싶으면 infix 키워드를 함수에 붙인다.

 

infix fun Any.to(other: Any) = Pair(this, other)

 

Pair는 두 원소로 이뤄진 순서쌍을 표현한다. Pair에 들어있는 원소를 이용해서 두 변수를 초기화 할 수 있다. 이를 구조 분해 선언이라고 부른다.

 

val (number, name) = 1 to "one"
val (number, name) = Pair(1, "one")

요약

  • 코틀린 자바 컬렉션 클래스를 확장해서 더 풍부한 기능을 제공한다.
  • 함수 파라미터의 디폴트 값을 정의할 수 있으며, 이름 붙인 인자를 사용해서 함수를 호출할 수 있다.
  • 코틀린 파일에서 함수와 프로퍼티를 최상위에 직접 선언할 수 있다.
  • 확장 함수와 프로퍼티를 사용해서 외부 클래스 수정 없이 기능을 확장할 수 있다.
  • 중위 호출과 구조 분해 선언을 제공한다.
  • 정규식과 일반 문자열을 처리할 때 다양한 문자열 처리 함수를 제공한다.
  • 3중 따옴표로 문자열을 감싸서 이스케이프가 필요한 문자열을 깔끔하게 표현할 수 있다.

댓글