모든 내용은 Do it! 코틀린 프로그래밍을 바탕으로 정리한 것입니다.
코틀린의 다양한 함수
익명 함수 (Anonymous Function)
이름이 없는 일반 함수
fun(x: Int, y: Int): Int = x + y
val add1: (Int, Int) -> Int = fun(x, y) = x + y
val add2 = fun(x: Int, y: Int) = x + y
val add3 = { x: Int, y: Int -> x + y }
val result = add1(10, 2)
- 변수 선언에 그대로 사용할 수 있음
- 익명 함수의 선언 자료형을 람다식 형태로 쓰면 변수를 함수처럼 사용할 수 있음
- 매개변수에 자료형을 쓰면 선언부에 자료형은 생략 가능
- 람다 표현식과 매우 유사
람다식으로 표기할 수 있는데 굳이 익명 함수를 쓰는 이유?
→ 람다식에서는 return, break, continue와 같은 제어문을 사용하기 어렵기 때문
→ 함수 본문 조건식에 따라 함수를 중단하고 반환해야 하는 경우 익명 함수를 사용해야 함
인라인 함수 (Inline Function)
- 함수 본문 내용을 모두 복사해 넣어 함수의 분기 없이 처리함
- 코드가 복사되어 들어가기 때문에 내용은 대게 짧게 작성
- 보통 함수는 호출되었으 ㄹ때 다른 코드로 분기해야 하기 때문에 내부적으로 기존 내용을 저장했다가 다시 돌아올 때 복구하는 작업에 프로세스오 메모리를 꽤 사용해야 하는 비용이 듦
- 인라인 함수는 내용이 복사되어 들어가기 때문에 매번 분기하지 않아도 됨
- 람다식 매개변수를 가지고 있는 함수에서 동작함
fun main() {
shortFunc(3) { println("First call : $it") }
shortFunc(5) { println("Second call : $it") }
}
inline fun shortFunc(a: Int, out: (Int) -> Unit) {
println("Before calling out()")
out(a)
println("After calling out()")
}
public final class InlineFunctionKt {
public static final void main() {
int a$iv = 3; // 1회 복사
int $i$f$shortFunc = false;
String var2 = "Before calling out()";
boolean var3 = false;
System.out.println(var2);
int var5 = false;
String var6 = "First call : " + a$iv;
boolean var7 = false;
System.out.println(var6);
var2 = "After calling out()";
var3 = false;
System.out.println(var2);
a$iv = 5; // 2회 복사
$i$f$shortFunc = false;
var2 = "Before calling out()";
var3 = false;
System.out.println(var2);
var5 = false;
var6 = "Second call : " + a$iv;
var7 = false;
System.out.println(var6);
var2 = "After calling out()";
var3 = false;
System.out.println(var2);
}
public static final void shortFunc(int a, @NotNull Function1 out) {
int $i$f$shortFunc = 0;
Intrinsics.checkNotNullParameter(out, "out");
String var3 = "Before calling out()";
boolean var4 = false;
System.out.println(var3);
out.invoke(a);
var3 = "After calling out()";
var4 = false;
System.out.println(var3);
}
}
디컴파일 결과에서 보면 shortFunc()의 내용이 main() 블록 안에 2번 복사된 것을 확인할 수 있음
인라인 함수 제한하기 (noinline 키워드)
매개변수로 사용한 람다식의 코드가 너무 길거나, 인라인 함수의 본문 자체가 너무 길면 컴파일러에서 성능 경고 할 수 있음
인라인 함수가 너무 많이 호출되면 오히려 코드 양만 늘어나서 좋지 않을 수 있음
noinline가 있는 람다식은 인라인으로 처리되지 않고 분기하여 호출됨
fun main() {
shortFunc(3) { println("First call $it") }
}
inline fun shortFunc(a: Int, noinline out: (Int) -> Unit) {
println("Before calling out()")
out(a)
println("After calling out()")
}
public final class NoinlineTestKt {
public static final void main() {
byte a$iv = 3;
Function1 out$iv = (Function1)null.INSTANCE;
int $i$f$shortFunc = false;
String var3 = "Before calling out()";
boolean var4 = false;
System.out.println(var3);
out$iv.invoke(Integer.valueOf(a$iv)); // 복사하지 않고 분기
var3 = "After calling out()";
var4 = false;
System.out.println(var3);
}
public static final void shortFunc(int a, @NotNull Function1 out) {
int $i$f$shortFunc = 0;
Intrinsics.checkNotNullParameter(out, "out");
String var3 = "Before calling out()";
boolean var4 = false;
System.out.println(var3);
out.invoke(a); // 인라인X
var3 = "After calling out()";
var4 = false;
System.out.println(var3);
}
}
인라인 함수와 비지역 반환 (crossinline 키워드)
익명 함수와 인라인 함수에서는 return을 사용하여 함수를 끝낼 수 있음
fun main() {
shortFunc(3) {
println("First call : $it")
return
}
}
inline fun shortFunc(a: Int, out: (Int) -> Unit) {
println("Before calling out()")
out(a)
println("After calling out()")
}
- out(a)는 인라인되어 대체되기 때문에 return문까지 포함됨
- 비지역 반환(Non-local Return) : 람다식 함수에서 return문을 만낫지만 의도하지 않게 바깥의 함수가 반환 처리 되는 것
- crossinline 키워드 : 비지역 반환을 금지해야 하는 람다식에 사용 → return을 금지함(코드 작성 단계에서 오류)
확장 함수 (Extension function)
클래스처럼 필요로 하는 대상에 함수를 더 추가할 수 있는 확장 함수의 개념 제공
fun 확장 대상.함수 이름(매개변수, ...): 반환값 { }
fun main() {
val source = "Hello World!"
val target = "Kotlin"
println(source.getLongString(target))
}
fun String.getLongString(target: String): String =
if (this.length > target.length) this else target
- String 클래스에 getLongString()을 새로운 멤버 메소드로 추가
- 기존 클래스의 선언 구현부를 수정하지 않고 외부에서 손쉽게 기능 확장 가능 (기존의 표준 라이브러리 확장에 유용)
- 확장하려는 대상에 동일한 이름의 멤버 함수 또는 메소드가 존재한다면 항상 확장 함수보다 멤버 메소드가 우선 호출!
중위 함수 (Infix Notation)
클래스 멤버를 호출할 때 사용하는 점(.)을 생략하고 함수 이름 뒤에 소괄호를 붙이지 않아 직관적인 이름을 사용할 수 있는 표현법
→ 일종의 연산자를 구현할 수 있는 함수 (특히 비트 연산자)
- 멤버 메소드 또는 확장 함수여야 함
- 하나의 매개변수를 가져야 함
- infix 키워드를 사용하여 정의함
fun main() {
val multi = 3 multiply 10
println("multi: $multi")
}
infix fun Int.multiply(x: Int): Int {
return this * x
}
→ Int의 확장 함수이며, 하나의 매개변수를 가지고 있으며 infix 키워드를 사용한 중위 함수임
3.multiply(10) 보다 3 multiply 10과 같은 직관적인 형태로 변경하여 사용할 수 있음
꼬리 재귀 함수 (Tail Recursive Function)
재귀 : 자기 자신을 다시 참조하는 방법
→ 반드시 조건에 맞게 설계 해야 함. 그렇지 않으면 스택 오버플로 오류가 발생함
- 무한 호출에 빠지지 않도록 탈출 조건을 만들어 둠
- 스택 영역을 이용하므로 호출 횟수를 무리하게 많이 지정해 연산하지 않음
- 코드를 복잡하게 하지 않음
코틀린에서는 꼬리 재귀 함수를 사용하여 스택 오버플로 현상을 해결할 수 있음 → tailrec 키워드
factorial 함수
<일반 재귀 함수>
fun main() {
val number = 4
val result: Long
result = factorial(number)
println("Factorial: $number -> $result")
}
fun factorial(n: Int): Long {
return if (n==1) n.toLong() else n * factorial(n-1)
}
<꼬리 재귀 함수>
fun main() {
val number = 5
println("Factorial: $number -> ${factorial(number)}")
}
tailrec fun factorial(n: Int, run: Int = 1): Long {
return if (n==1) run.toLong() else factorial(n-1, run*n)
}
- 일반적인 재귀에서는 재귀함수가 먼저 호출되고 계산되지만 꼬리 재귀에서는 계산을 먼저하고 재귀 함수가 호출됨
- 인자 안에서 팩토리얼의 도중 값을 계산하고 호출함
- 그때그때 계산하므로 스택 메모리를 낭비하지 않아도 됨 → n값을 크게 설정해도 상관 없음
함수와 변수의 범위
최상위 함수와 지역 함수
- 최상위 함수(Top-level Function) : 파일을 만들고 곧바로 main() 함수나 사용자 함수를 만들 수 있음 → 선언 순서 영향X
- 지역 함수(Local Function) : 함수 안에 또 다른 함수가 선언되어 있는 경우 → 선언 순서에 영향O
fun a() = b()
fun b() = println("b")
fun c() {
// fun d() = e()
fun e() = println("e")
}
fun main() {
a()
// e()
}
→ c()안의 d()는 지역 함수이며 e()의 이름을 모름 (아직 선언되지 않은 상태: Unresolved Reference 오류)
지역 변수와 전역 변수
- 지역 변수(Local Variable) : 특정 코드 블록 안에 있는 변수 → 블록을 벗어나면 프로그램 메모리에서 삭제됨
- 전역 변수(Global Variable) : 최상위에 있는 변수 → 프로그램이 실행되는 동안 값이 유지됨
var global = 10
fun main() {
val local1 = 20
val local2 = 21
fun nestedFunc() {
global += 1
val local1 = 30
println("nestedFunc local: $local1")
println("nestedFunc local2: $local2")
println("nestedFunc global: $global")
}
nestedFunc()
outsideFunc()
println("main global: $global")
println("main local1: $local1")
println("main local2: $local2")
}
fun outsideFunc() {
global += 1
val outVal = "outside"
println("outsideFunc global: $global")
println("outsideFunc outval: $outVal")
}
- global : 여러 함수에서 접근하면서 1씩 증가하여 최종 값은 12가 출력됨
- main() 블록의 local1과 nestedFunc() 블록의 local1은 서로 다른 변수임
- nestedFunc() 블록에서 local1을 새롭게 정의했기 때문에 main() 블록의 local1이 가려지게 된 것
- main()의 local1은 main() 블록이 끝나지 않는 한 계속 유지됨
- outVal : 오로지 outsideFunc() 블록에서만 유효함
'Kotlin > Kotlin 프로그래밍' 카테고리의 다른 글
[Kotlin] 프로퍼티와 초기화 (0) | 2021.04.20 |
---|---|
[Kotlin] 클래스와 객체 (2) (0) | 2021.04.19 |
[Kotlin] 클래스와 객체 (1) (0) | 2021.04.16 |
[Kotlin] 프로그램의 흐름 제어 : 조건문, 반복문, 예외처리 (0) | 2021.04.14 |
[Kotlin] 함수와 함수형 프로그래밍 (2) - 고차함수와 람다식 (0) | 2021.04.07 |
[Kotlin] 함수와 함수형 프로그래밍 (1) (0) | 2021.04.02 |
[Kotlin] 변수와 자료형, 연산자 (2) (0) | 2021.04.02 |
[Kotlin] 변수와 자료형, 연산자 (1) (0) | 2021.03.31 |
댓글