함수 선언 방식
우선 함수의 기본 구조는 이렇다.
// 기본 구조
fun 함수명(매개변수: 타입): 반환타입 {
// 실행할 코드
}
// 예시
fun add(a: Int, b: Int): Int {
return a + b
}
구조를 보면 알겠지만
fun 키워드로 시작하고, 매개변수와 반환 타입을 명시한다.
여기서 짚고가야 할 부분은
매개변수(parameter)와 인수는(argument)는 다른 개념이라는 것이다.
- 매개변수 : 함수를 정의할 때 선언하는 변수 (예시 코드의 a: Int, b: Int 중 a와 b)
- 인수 : 함수를 호출할 때 실제로 전달하는 값
fun add(a: Int, b: Int): Int { ... } // a와 b는 매개변수
add(3, 4) // 3, 4는 인수
그리고 코틀린에서 매개변수는 함수 내부에서 값을 바꿀 수 없다.
매개변수를 val처럼 취급하기 때문이다.
fun addTen(a: Int): Int {
a = a + 10 // 컴파일 에러
return a
}
반환값
함수는 값을 반환할 수 있다.
이렇게 반환된 값은 변수에 저장하여 사용이 가능하다.
fun add(a: Int, b: Int): Int { ... }
val result = add(5, 7) // result에 12 저장
Unit
반환할 값이 없는 경우에는 반환 타입을 생략하면 되는데,
이렇게 생략하게 되면 기본적으로 Unit이 된다.
자바의 Void와 같다고 보면 된다.
fun greet(name: String): Unit {
println("Hi $name!")
}
fun greet(name: String) {
println("Hi $name!")
}
이 두 함수는 동일하다.
단일 표현식 함수
함수 내용이 단순한 표현식 하나로 끝난다면
더 줄일 수도 있다!
fun add(a: Int, b: Int): Int = a + b
// 타입 추론이 가능하므로 반환 타입도 생략이 가능!
fun add(a: Int, b: Int) = a + b
자바였다면 무조건 중괄호에 return까지 써야 했는데,
아주 간결하게 사용할 수 있다는 걸 알 수 있다.
하지만 재귀 함수나 상호 재귀함수, fun empty() = null처럼 타입이 명확하지 않은 표현식의 경우에는
반환 타입을 명시해야 한다.
// 반환 타입 생략 불가 - 컴파일 에러
fun factorial(n: Int) = if (n <= 1) 1 else n * factorial(n - 1)
// 이렇게 반환 타입을 명시해야 함
fun factorial(n: Int): Int = if (n <= 1) 1 else n * factorial(n - 1)
예를 들어
factorial(5)를 호출하면 5 * factorial(4) * factorial(3) ... 이런 식으로 자기 자신을 반복하여 호출하는데,
컴파일러 입장에서는 자기 자신의 반환 타입이 결정되지 않은 상태라 추론이 불가능하다.
음...
컴파일러가 문제를 겪는 건 실행 시점이 아니라 코드를 읽는 시점인 컴파일 시점이다.
그러니까
fun factorial(n: Int) = if (n <= 1) 1 else n * factorial(n - 1)
컴파일러가 이 한 줄을 처음 읽을 때,
오른쪽 표현식의 타입을 추론하려고 한다.
그런데 표현식 안에 factorial(n - 1)이 있는데, 당장 이 타입이 확정되어야 한다.
뭐라는기고?
factorial의 타입을 알려면 factorial의 타입을 알아야 하는 순환 참조 상태가 되어 버려서
컴파일러는 추론을 포기하고 에러를 내버린다.
그리고 블록 바디 함수(우리가 일반적으로 사용하는 {}가 포함된 함수)는
Unit을 반환하는 경우에만 반환 타입 생략이 가능하고,
그 외에는 반드시 명시해야 한다.
// 반환 타입 생략으로 Unit으로 간주되지만, 실제로는 Int를 반환하려고 하니 타입 불일치로 컴파일 에러 발생
fun add(a: Int, b: Int) {
return a + b
}
// 1. 타입을 명시하거나
fun add(a: Int, b: Int): Int {
return a + b
}
// 2. 단일 표현식으로 변경
fun add(a: Int, b: Int) = a + b
다양한 매개변수 활용
기본값 (Default Parameter)
매개변수에 기본 값을 설정할 수 있다.
기본값이 있는 매개변수는 함수 호출 시 생략이 가능하다.
fun greet(name: String, greeting: String = "안녕") {
println("$greeting, $name!")
}
greet("민수") // "안녕, 민수!" 출력
greet("민수", "하잉") // "하잉, 민수!" 출력
네임드 파라미터
인수를 전달할 때 매개변수 이름을 명시할 수 있다.
이렇게 이름을 붙이면 순서를 바꿔도 된다.
fun greet(name: String, greeting: String = "안녕") {
println("$greeting, $name!")
}
greet(greeting = "하잉", name = "Park") // name과 greeting의 순서를 바꿨지만 정상동작!
가변인자
인수의 개수가 정해지지 않을 때 vararg 키워드를 사용한다.
fun printAll(vararg names: String) {
for (name in names) {
println(name)
}
}
// 개수에 상관없이 정상동작!
printAll("lee", "park")
printAll("song")
내부적으로는 배열로 처리된다.
단, vararg를 마지막이 아닌 위치에 선언할 경우,
이후에 오는 매개변수는 반드시 네임드 파라미터로 전달해야 한다.
보통 가변인자는 마지막에 선언한다.
fun example(vararg names: String, separator: String) { ... }
example("lee", "park", separator = ", ") // separator는 네임드로 전달
", "가 names인지 separator인지 알 수 있도록 명시해준다고 생각하면 될 것 같다.
고차 함수
이 부분이 중요한데,
함수를 값처럼 다룰 수 있다라는 의미는
함수를 인자로 넘기거나 반환값으로 사용할 수 있다는 것이다.
역시 말보다는 예시를 보고 이해하는 게 빠를 것 같다 ㅋ
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
여기서 operation: (Int, Int) -> Int가 핵심이다.
"두 개의 Int를 받아서 Int를 반환하는 함수(operation)"를 매개변수로 받겠다는 뜻이다.
fun add(a: Int, b: Int) = a + b
val result = calculate(3, 4, ::add) // 결과는 7
실제로 호출을 할 때에는 이런 식으로 사용한다.
::add는 "add라는 함수 자체를 값으로 넘긴다" 라는 표현이다.
람다 표현식
그런데 위의 예시처럼 함수를 매번 따로 선언하여 값으로 넘기기엔 번거롭다.
그래서 이름 없이 그 자리에서 바로 함수를 정의하는 것이 람다!
val result = calculate(3, 4, { a, b -> a + b })
...!
훨씬 간결해졌다.
여기서
{ a, b -> a + b }가 람다다.
"a와 b를 받아서 더한 값을 반환해!"를 축약한 것이다.
Trailing Lambda
코틀린에서 람다가 마지막 인자일 경우
괄호 밖으로 뺄 수 있다.
val result = calculate(3, 4, { a, b -> a + b })
val result = calculate(3, 4) { a, b -> a + b }
이 두 코드는 동일하다!
it 키워드
파라미터가 하나일 때에는 it이라는 이름을 사용하여 자동으로 받을 수 있다.
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 } // [2, 4, 6, 8, 10]
map이 고차 함수고, { it * 2 }는 람다다.
클로저
람다는 외부에 선언된 변수를 캡쳐하여 사용할 수 있다.
이것을 클로저라고 하는데, 설명만으로는 이해하기 어려울 것 같다.
갑자기 캡쳐...?
val prefix = "하잉"
val greet = { name: String -> println("$prefix, $name!")}
greet("코틀린") // "하잉, 코틀린!" 출력
코드를 보면
println 내부에서 외부에 있는 prefix 변수를 사용하고 있다.
즉, 람다 내부에서 외부 변수 prefix를 사용할 수 있다는 것이다.
그리고
코틀린에서는 var도 캡쳐가 가능하다.
var count = 0
val increment = { count++ } // 람다 내부에서 var로 선언된 외부 변수 count를 캡쳐
increment()
increment()
println(count) // 2 출력
var일 경우에는 내부적으로 Ref객체로 감싸서 처리하는데,
지금은 진짜로 var 변수를 직접 캡쳐하는 것이 아니라
내부적으로 래퍼 객체를 통해 우회하는 방식이다!라고만 알고 있으면 될 것 같다.
*잘못된 정보가 있을 경우 피드백 부탁드립니다 ( _ _ )*
'Android > 뽀개기' 카테고리의 다른 글
| [Android] 코틀린 뽀개기 - 코틀린의 주요 특징 (0) | 2026.02.23 |
|---|---|
| [Android] 코틀린 뽀개기 - 워밍업 (0) | 2026.02.20 |