코틀린 함수
자바 메소드처럼 코틀린 함수도 어떤 입력(parameter)을 받아 자신을 호출한 코드쪽에 출력값을 반환(return)할 수 있는 재사용 가능한 코드 블록입니다.
자바와 달리 코틀린에서 도달할 수 없는 코드(unreachable code)는 오류가 아닙니다. 따라서, return문 다음에 코드가 위치하고 있으면 실질적으로 죽어있는 코드지만 에러가 발생하진 않습니다.
→ 컴파일러가 경고를 표시하고 IDE는 해당 부분을 강조해줍니다.
fun increment(n: Int): Int {
return n++ // Error: can't change immutable variable
}
- 함수 반환 타입: 함수 본문 앞에 :을 붙여 작성합니다.
- 함수 파라미터: 파라미터 앞에는 val이나 var을 표시할 수 없습니다.
- 파라미터에 대입하는 중 실수할 가능성이 높고, 파라미터를 불변 값으로 강제하는 편이 깔끔하고 이해하기 편한 코드를 만듬
자바 메소드 파라미터는 default가 가변이라, 함수 내부에서 변경하지 못하게 하려면 final을 붙여 불변 값으로 바꾸어야 합니다. 하지만, 코틀린 함수 파라미터는 무조건 불변입니다.
코틀린은 값에 의한 호출(call-by-value) 의미론을 사용합니다. 즉, 파라미터 값에 호출하는 쪽의 인자를 복사한다는 의미입니다. 따라서, 호출 인자로 전달한 변수를 변경해도 호출된 함수 내부의 파라미터 값에는 영향이 없습니다.
fun circleArea(radius: Double): Double {
return PI*radius*radius
}
fun main() {
val radius = readLine()!!.toDouble()
println("Circle area: ${circleArea(radius)}")
}
- 예를 들어, radius에 들어가는 값이 바뀌어도 circleArea의 내부 파라미터에는 영향을 주지 않습니다.
하지만, 파라미터가 참조(예: 배열 타입)라면, 호출한 쪽의 데이터는 그대로 남아있고 해당 데이터에 대한 참조만 복사됩니다. 따라서, 호출된 함수 내부에서 파라미터 값이 변경되면 호출하는 쪽의 데이터도 영향을 받습니다.
fun increment(a: IntArray): Int {
return ++a[0]
}
fun main() {
val a = intArrayOf(1, 2, 3)
println(increment(a)) // 2
println(a.contentToString()) // [2, 2, 3] > 호출하는 쪽 데이터 영향 받음
}
파라미터의 경우, 변수와 달리 항상 타입을 지정해야 합니다.
- 함수 정의만 보고 컴파일러가 파라미터 타입을 추론할 수 없기 때문입니다.
반환 타입 역시, 항상 타입을 지정해야 합니다. 이는 함수에서 결괏값을 결정해 내부로 나가는 시점(return)이 여러 곳일 수 있어, 함수 본문의 모든 반환 지점을 살펴보고 반환 타입을 알아내기 어려울 수 있기 때문입니다.
- 예외적으로, 유닛(Unit) 타입을 반환하는 경우에 반환 타입 생략이 가능합니다. 유닛은 자바 void에 해당하는 코틀린 타입으로 함수가 의미 있는 반환값을 돌려주지 않는다는 뜻입니다.
- 또한, 식이 본문(expression-body)인 함수 역시 반환 타입 생략이 가능합니다. 어떤 함수가 단일 식으로 구현될 수 있다면, return 키워드와 블록을 만드는 중괄호({})를 생략하고 아래와 같이 함수를 작성할 수 있습니다.
- 다만 이 경우, 아래와 같이 작성하는 것보다 일반적인 블록 구문을 사용하는 편이 가독성을 높일 수 있습니다.
- 블록이 본문인 함수를 정의할 때 {} 앞에 =을 넣게 되면, 해당 블록이 익명 함수를 기술하는 람다로 해석되므로 유의해야 합니다.
fun circleArea(radius: Double) = PI*radius*radius // 반환값이 Double로 추론됨
fun circleArea_lambda(radius: Double) = {PI*radius*radius} // 익명 함수를 기술하는 람다로 해석되어 위와 다른 함수로 인식됨
fun main() {
println("radius: 3.0, circleArea: ${circleArea(3.0)}") // radius: 3.0, circleArea: 28.259999999999998
println("radius: 3.0, circleArea: ${circleArea_lambda(3.0)}") // radius: 3.0, circleArea: () -> kotlin.Double
}
위치 기반 인자와 이름 붙은 인자
기본적으로 함수 호출 인자는 순서대로 파라미터에 전달됩니다. 이를 코틀린에서는 위치 기반 인자(positional argument)라고 하며, 자바나 다른 여러 언어에서 위치 기반 인자를 널리 쓰고 있습니다.
코틀린에서는 위치 기반 인자 외에도 이름 붙은 인자(named argument)라고 불리는 방식 역시 제공합니다. 이름 붙은 인자는 위치가 아닌 파라미터의 이름을 명시함으로써 인자를 전달하는 방법입니다.
오버로딩과 디폴트 값
자바 메소드와 마찬가지로 코틀린 함수 역시 오버로딩이 가능합니다. 코틀린 역시 자바와 마찬가지로 함수 오버로딩 시, 컴파일러가 어떤 함수를 호출해야 할지 구분할 수 있도록 오버로딩한 함수의 파라미터 타입이 모두 달라야 합니다.
fun rectangleArea(width: Int = 3, height: Int) = width * height
fun main(args: Array<String>) {
println("rectangleArea: ${rectangleArea(height = 3)}")
}
또한, 코틀린은 함수 파라미터의 디폴트 값을 지정할 수 있어, 함수 파라미터를 모두 지정하지 않고도 함수 호출할 수 있습니다.
- 디폴트 값이 있는 파라미터는 함수 인자 목록 뒤쪽에 몰아두는 쪽이 더 좋은 코딩 스타일입니다.
함수의 영역과 가시성
코틀린 함수는 정의된 위치에 따라 세 가지로 구분할 수 있습니다.
- 파일에 직접 선언된 최상위 함수
- 어떤 타입 내부에 선언된 멤버 함수
- 다른 함수 안에 선언된 지역 함수
코틀린에서는 디폴트로 최상위 함수는 공개(public) 함수입니다. 즉, 디폴트로 선언된 최상위 함수는 함수가 정의된 파일 내부 뿐 아니라 프로젝트 어디에서나 쓰일 수 있습니다. 경우에 따라 프로젝트의 나머지 부분으로부터 구현 상세 내용을 숨겨 보호하고 싶다면, 최상위 함수 정의 앞에 private나 internal 키워드를 붙일 수 있으며 이를 가시성 변경자(visibility modifier)라고 부릅니다.
- 최상위 함수를 비공개(private)로 정의하면 함수가 정의된 파일 안에서만 해당 함수를 볼 수 있습니다.
- internal 변경자를 적용하면 함수가 적용된 모듈 내부에서만 함수가 사용됩니다.
- 코틀린에서 모듈은 기본적으로 함께 컴파일되는 파일 전부를 뜻합니다.
지역 함수는 자신을 둘러싼 함수, 블록에 선언된 변수나 함수에 접근할 수 있습니다. 여기서 말하는 변수에는 지역 함수를 둘러싼 함수의 파라미터 역시 포함됩니다. 지역 함수와 변수에는 가시성 변경자를 붙일 수 없습니다.
패키지와 임포트
패키지 디렉티브는 package 키워드로 시작하고 점(.)으로 구별된 식별자들로 이뤄진 패키지 전체 이름이 뒤에 옵니다. 기본적으로 전체 이름은 프로젝트 루트 패키지로부터 지정한 패키지에 도달하기 위한 경로입니다.
임포트 디렉티브를 사용하면, 전체 이름을 사용하지 않아도 되어 코드가 간단해 집니다. 예를 들어, 아래의 LocalDateTime의 경로를 import 해주면 소스에서는 LocalDateTime이라고만 작성해도 됩니다.
fun main() {
println("${java.time.LocalDateTime.now()}")
}
import java.time.LocalDateTime
fun main() {
println("${LocalDateTime.now()}")
}
자바와 달리 코틀린은 타입 멤버를 임포트하는 별도의 import static 디렉티브가 없습니다. 코틀린의 모든 선언은 일반적인 임포트 디렉티브 구분을 사용해 임포트할 수 있습니다.
조건문
코틀린의 if문은 자바와 다르게 식으로 사용할 수 있다는 점입니다. 따라서, 아래와 같이 함수를 더 간단하게 작성이 가능합니다.
fun max(a: Int, b: Int) = if (a > b) a else b
- 위처럼, if문을 식으로 사용할 때에는 if와 else 모두 다 존재해야 합니다. 만약, else가 빠지면 컴파일 시 에러가 발생합니다.
코틀린의 if문을 식으로 사용할 수 있어, 자바와 다르게 3항 연산자가 없습니다.
if 식에 return을 사용하면 위와 같이 return 문은 존재하지 않는 값을 뜻하는 Nothing이라는 특별한 타입의 값으로 간주됩니다. 이 return문은 if 식을 둘러싼 함수가 끝남을 의미하며, if문의 끝을 의미하는 것이 아닙니다. Nothing 타입은 모든 코틀린 타입의 하위 타입으로 간주되어 식이 필요한 위치에 return을 사용해도 타입 오류가 발생하지 않습니다.
when 문과 여럿 중에 하나 선택하기
if문은 두 가지 가능성 중 하나를 선택했다면, when을 이용하면 여러 가능성 중 하나를 선택할 수 있습니다.
fun hexDigitIf(n: Int): Char {
if (n in 0..9) return '0' + n
else if(n in 10..15) return 'A' + n - 10
else return '?'
}
fun hexDigitWhen(n: Int): Char {
return when(n) {
0, 1, 2, 3, 4, in 5..9 -> '0' + n // when(n)과 같이 대상이 있는 형태일 때에는 한 가지 안에 여러 조건을 콤마로 분리해 쓸 수 있다
in 10..15 -> 'A' + n - 10
else -> '?'
}
}
fun hexDigitWhen(n: Int): Char {
return when {
n in 0..9 -> '0' + n
n in 10..15 -> 'A' + n - 10
else -> '?'
}
}
위 함수들은 모두 같은 결과를 내는 함수로, 코틀린의 when은 자바 switch문과 비슷합니다.
자바의 switch문과 코틀린의 when문의 가장 큰 차이점은 when은 임의의 조건을 검사할 수 있지만, switch는 주어진 식의 여러 가지 값 중 하나만을 선택할 수 있다는 것입니다.
또한, 자바의 switch문은 폴스루(fall-through) 의미를 지녀 조건을 만족해도 명시적으로 break을 만날때까지 그 이후의 모든 조건을 검사합니다. 반면, 코틀린의 when문은 조건을 만족하는 가지만 실행하고 폴스루하지 않습니다.
JAVA12부터는 코틀린의 when과 비슷한 switch식이 등장했지만, 자바 switch식에는 몇가지 제약이 있습니다.
when문 역시, if처럼 식으로 사용할 수 있습니다. 위 예시를 식으로 고치면 아래와 같습니다.
fun hexDigitWhen(n: Int) = when(n) {
in 0..9 -> '0' + n
in 10..15 -> 'A' + n - 10
else -> '?'
}
루프
코틀린의 모든 루프는 식이 아니고 문이기 때문에, 어떤 값으로 평가되지 않고 부수 효과를 발생시킬 수만 있습니다.
코틀린의 for 루프는 iterable 인스턴스에 대한 루프를 간편하게 작성하도록 해주는 자바 for-each 루프와 비슷합니다. 하지만, 코틀린에는 선언, 초기화, 검사 및 갱신으로 구성되는 일반 자바 for 루프에 해당하는 언어 구조가 없습니다.
코틀린에서는 while 루프를 통해 일반 자바 for 루프를 표현하거나 범위와 진행을 사용하는 for 루프를 사용해야 합니다.
자바에서는 문자열의 각 문자에 대해 루프를 직접 수행할 수 없어 인덱스를 사용한 루프를 돌거나, 문자열을 문자 배열로 바꾸는 등의 대안이 필요합니다. 하지만 코틀린의 for 루프는 iterator() 함수를 지원하기만 한다면 모두 for 루프를 통해 루프를 수행할 수 있습니다.
참고 자료
'PROGRAMMING LANGUAGE > KOTLIN' 카테고리의 다른 글
[Kotlin] 널 가능성 (0) | 2022.08.12 |
---|---|
[Kotlin] 클래스 기초 (0) | 2022.08.12 |
[Kotlin] 코틀린 언어 기초 (0) | 2022.08.04 |
[Kotlin] 코틀린이란? (0) | 2022.08.04 |
[Kotlin] 코틀린에도 삼항 연산자(ternary operator)가 있나요? (0) | 2022.06.27 |