[Kotlin] 함수(2)
📚 함수(2)
📔 람다 표현식
- 예약어가 없고 함수 이름도 없는 게 특징
- 코드 블록인 중괄호 안에 직접 매개변수와 표현식을 작성
- 매개변수와 표현식을 구분하는 기호 “->”로 구분함. 매개변수는 괄호 처리를 하지 않음
- 매개변수가 없을 시, 이를 생략하고 표현식만 작성
- 람다 표현식을 작성하고 바로 실행하려면 람다 표현식 다음에 호출 연산자인 괄호를 사용해 매개변수에 해당 인자를 전달
- 람다 표현식에는 기본적으로
return
키워드를 사용할 수 없지만, 인라인 함수로 사용할 때는 실제 람다 표현식 내부 로직이 호출된 곳에 코드로 삽입되기 때문에return
키워드 사용 가능. - 람다 표현식 중간에 함수를 빠져나갈 경우, 레이블 된
return
키워드 사용 가능. 이때 반환값이 아닌 현재 처리하는 람다 표현식을 종료하는 조건으로만 사용 - 람다 표현식에서 매개변수가 하나일 경우 별도의 매개변수를 지정하지 않고 하나의 매개변수라는 의미로
it
을 사용 가능
{ println("아무 인자가 없음") }() // 인자가 없는 경우
println({x: Int -> x * x}(10)) // 인자가 하나 있는 경우
println({x: Int, y: Int -> x + y}) // 인자가 두 개 있는 경우
val a = { x: Int, y: Int -> x + y } // 재사용하려면 변수에 할당
println(a(200, 300))
fun func(x: Int, y: Int, f: (Int, Int) -> Int ): Int { // 함수 매개변수를 가지는 함수
return f(x, y)
}
println(func(100, 200, {x, y -> x + y})) // 인자로 람다 표현식 전달
아무 인자가 없음
100
(kotlin.Int, kotlin.Int) -> kotlin.Int
500
300
📖 람다 표현식과 익명 함수 비교
람다 표현식은 좀 더 축약된 표현과 return
키워드를 사용하지 않는 것만 제외하면 익명 함수와 동일하다.
println({ p1: Int, p2: Int -> p1 + p2 } (100, 200)) // 람다 함수를 정의하고 즉시 실행
fun add(x: Int, y: Int) = x + y // 함수는 정의 후 호출
println(add(100, 200))
println({ x: Int, y: Int -> x + y } (100, 200))
println((fun (x: Int, y: Int) = x + y)(100, 200)) // 익명 함수를 정의하고 즉시 실행
300
300
300
300
📖 람다 표현식 활용
fun ret(): () -> Int {
return { 100 } // 함수 반환값
}
val r = ret()
println(r())
val ll = listOf(1, 2, 3)
println(ll.map { it * it }) // 함수의 인자
class LL(val action: (Int) -> Int) // 클래스의 속성
val l = LL({it * 2})
println(l.action(10))
100
[1, 4, 9]
20
📔 클로저
📖 외부 함수와 내부 함수
- 외부 함수(outer function): 지역 함수를 가진 함수
- 내부 함수(inner function): 외부 함수 내 정의된 저역 함수
📖 클로저(Closer)
함수 밖에 정의된 변수들을 사용할 수 있는 함수
fun outer(x: Int) { // 외부 함수
fun inner(y: Int) = x + y // 외부 함수 변수 사용
println(inner(x)) // 외부 함수 지역 변수 인자 제공
}
outer(10)
fun outer1(x: Int): Int {
fun inner(y: Int) = x + y
return inner(x)
}
println(outer1(10))
fun outer2(x: Int): Int {
return (fun(y: Int): Int {
return x + y
})(10) // 내부 함수를 익명 함수로 처리
}
println(outer2(10))
fun outer3(x: Int): Int {
return { y: Int -> x + y }(10) // 내부 함수를 람다식으로 처리
}
println(outer3(10))
20
20
20
20
📖 함수 반환 처리
내부 함수를 실행하지 않고 반환 처리해 클로저 구성
- 함수 참조: 일반 함수는 직접 함수의 레퍼런스르 가질 수 없어 함수 참조를 별도의 방식으로 가져와야 함
- 함수 참조 표기법: 콜론을 두 개 붙이고 다음에 함수 이름을 정의
- 함수 자료형 표기법: (매개변수 자료형, …) -> 반환 자료형 으로 지정. 이때 매개변수는 반드시 괄호 내에 표기
fun outer(x: Int): (Int) -> Int { // 함수를 참조로 반환
fun inner(y: Int) = x + y
return ::inner
}
val inner1 = outer(10)
println(inner1(10))
fun outer2(x: Int): (Int) -> Int { // 람다식 반환
return { y: Int -> x + y }
}
val inner2 = outer2(10)
println(inner2(10))
fun outer3(x: Int): (Int) -> Int { // 익명 함수 반환
return fun(y: Int) = x + y
}
val inner3 = outer3(10)
println(inner3(10))
20
20
20
📔 렉시컬 스코핑
함수는 모두 지역 영역을 가지는데 이러한 지역 영역을 변수 스코프(variable scope)라고 부른다. 그리고 외부 함수와 내부 함수는 각각 스코프를 구성한다. 내부 함수는 외부 함수의 스코프를 참조할 수 있으므로 스코프 계층이 생긴다. 렉시컬 스코핑(lexical scoping)은 이런 계층에서 변수를 검색하는 방법을 의미한다.
- 렉시컬 스코프의 처리기준: 항상 자기 함수 내부(local)를 조회하고 없으면 외부 함수(non-local)를, 이 마저도 없으면 전역인 패키지의 변수(global)를 찾음
- 보통 LGB(Local -> Global -> Built in) 순으로 변수를 검색
val factor = 2
fun func(x: Int): Int = x * factor // 전역 변수 사용
println(func(20))
40
📖 내부 함수 반환 처리 시, 스코프 처리 방식
fun funcLen(length: Int): (String) -> Boolean { // 매개변수가 하나이며, 함수를 반환
return { input: String -> input.length == length } // 내부 함수로 람다식 사용, length는 외부 함수의 매개변수
}
val len = funcLen(4)
println(len("가나다라마바사")) // 내부 함수 호출: 외부 함수 매개변수 사용
println(len("가나다라"))
val len1 = funcLen(6)
println(len1("가나다라마바사"))
println(len1("가나다라"))
false
true
false
false
📔 함수 자료형
📖 함수 자료형 표기법
- 매개변수 자료형 표시: 괄호 안에 매개변수의 개수에 맞춰 자료형을 쉼표로 구분해서 정의
- 반환 자료형 표시: -> 다음에 자료형 표시. 이 과정에서 괄호를 사용하지 않음
- 매개변수 자료형에 함수가 전달될 때는 함수 자료형 지정
- 보통 반환 자료형이 함수일 때도 함수 자료형 지정
- 변수에 함수가 할당될 때는 익명 함수, 람다식, 함수 참조에서 정의된 것을 확인하고 타입추론이 가능
- 함수 자료형에 여러 개 묶여 전달 시, “->” 을 기준으로 우측에 표기된 것을 괄호로 묶어 처리
📖 변수에 함수 자료형 처리
val a: () -> Unit = { println("함수 ") } // 매개변수가 없고 반환값은 Unit으로 처리
val b: (Int) -> Int = { x -> x * 3 } // 매개변수가 하나이고 반환값은 Int
val c: (Int, Int) -> Int = { x , y -> x + y } // 매개변수가 두 개이고 반환값은 Int
a()
println(b(10))
println(c(10, 20))
함수
30
30
익명 함수 또한 람다식과 같은 방법으로 함수 자료형을 지정해 변수에 할당 가능
val a: () -> Unit = fun(){ println ("함수 ") }
val b: (Int) -> Int = fun(x: Int): Int { return x * 3 }
val c: (Int, Int) -> Int = fun(x: Int, y: Int): Int { return x + y }
a()
println(b(10))
println(c(10, 20))
함수
30
30
함수 정의 또한 마찬가지로 위와 같이 사용할 수 있다.
fun unit(){ println("함수 ") }
fun triple(x: Int): Int { return x * 3 }
fun add(x: Int, y: Int): Int { return x + y }
val a: () -> Unit = ::unit
val b: (Int) -> Int = ::triple
val c: (Int, Int) -> Int = ::add
a()
println(b(10))
println(c(10, 20))
함수
30
30
📖 내부 함수를 반환하는 함수를 변수에 할당
val producePrinter: () -> () -> Unit =
val producePrinter1 = { { println("함수 전달 2") } }
fun outer(): () -> Unit {
return { println("함수 전달 3") }
}
val producePrinter2: () -> () -> Unit = ::outer
producePrinter()()
producePrinter1()()
producePrinter2()()
함수 전달 1
함수 전달 2
함수 전달 3
📔 nullable한 함수 자료형 정의
📖 nullable 함수 자료형 표기법
- nullable 함수 자료형:
(함수 자료형)?
, 함수 자료형 전체를 소괄호로 묶고 그다음 물음표를 붙임 - nullable 함수 자료형 호출:
함수명?.invoke(인자)
, 널이 들어온 경우 안전호출을 처리하기 위해invoke()
메서드로 처리
📖 매개변수에 nullable 함수 자료형
- 인자의 개수가 하나이거나 마지막 인자이면, 람다식을 전달할 때 괄호를 생략해서 전달할 수 있다.
fun nullFunc(action: (() -> Unit)?): Long { // 함수 자료형 전체를 괄호로 묶은 후 물음표
val start = System.nanoTime() // 시스템 시간을 조회
action?.invoke() // 널값이 들어올 수 있으니 ? 이후 실행 처리
return System.nanoTime() - start // 처리 시간 표시
}
println(nullFunc(null))
println(nullFunc { println("Hello Word") })
println(nullFunc(fun(): Unit { println("익명 함수") }))
fun unitFunc() = println("함수 처리")
println(nullFunc(::unitFunc))
400
Hello Word
107700
익명 함수
105100
함수 처리
106000
📖 함수 반환을 nullable 함수 자료형 처리
val innerFunc: (Int) -> ((Int) -> Int)? = { n -> null } // 반환값을 전체로 nullable 함수 자료형 처리, 함수 전달 대신 null 반환
println(innerFunc(10)?.invoke(20)) // 안전 호출
val innerFunc1: (Int) -> ((Int) -> Int)? = { n -> { m -> n + m } } // 반환값을 전체로 nullable 함수 처리, 함수 표현식으로 전달, 반환 자료형도 함수 표현
println(innerFunc1(10)?.invoke(20))
val innerFunc2: (Int) -> ((Int) -> Int)? = fun(n: Int): ((Int) -> Int)? {
return fun(m: Int): Int {
return n + m
}
}
println(innerFunc2(10)?.invoke(20))
fun funcDT(n: Int): ((Int) -> Int)? {
fun inner(m: Int): Int { return n + m }
return ::inner
}
println(funcDT(10)?.invoke(20))
null
30
30
30
📔 호출 메서드 - invoke()
📖 함수에서 호출 연산자 사용
val toUpperCase = { str: String -> str.uppercase() }
println(toUpperCase.invoke("summer")) // 람다식은 invoke() 메서드 사용 가능
val upper = fun(str: String): String {
return str.uppercase()
}
println(upper.invoke("fall in love")) // 익명 함수도 invoke() 메서드 사용 가능
fun toLowerCase(str: String): String {
return str.lowercase()
}
println(toLowerCase("SUMMER")) // 함수를 직접 호출할 경우, 호출 연산자만 사용 가능
val lower = ::toLowerCase // 함수 참조로 사용하면, invoke() 메서드 사용 가능
println(lower.invoke("Summer"))
SUMMER
FALL IN LOVE
summer
summer
📖 클래스에 연산자 오버로딩
- 함수 자료형 구분
- 함수 자료형 표기법: 코틀린 문법에서 사용하는 함수 자료형은 매개변수 자료형과 반환 자료형으로 표기
- 함수 인터페이스 표기법: 코틀린 패키지 내부에 함수 자료형과 일치하는 인터페이스를 제공. 예) Function1<Int, Int>: 매개변수 1개와 반환값 1개를 꺾쇠 내부에 표시
class MyFunction : () -> Unit { // 함수 자료형 상속
override operator fun invoke() { // 실행 연산자 오버라이딩
println("실행 연산 실행 1")
}
}
val function = MyFunction() // 함수 객체 생성
function() // 함수 실행
class A : Function<Unit> { 함수 자료형 인터페이스 상속
operator fun invoke() { // 실행 연산자 오버라이딩
println("실행 연산 실행 2")
}
}
val functionA = A() // 함수 객체 생성
functionA()
val a = object : (Int, Int) -> Int { // object 표현식, 함수를 상속
override fun invoke(x: Int, y: Int): Int { //연산자 오버라이딩
println("객체 invoke 호출 1")
return x + y
}
}
val x = a(10, 20)
println(x)
object Func: (Int, Int) -> Int { // object 정의, 함수를 상속
override fun invoke(x: Int, y: Int): Int { // 연산자 오버라이딩
println("객체 invoke 호출 2")
return x * y
}
}
val y = Func(10, 20)
println(y)
객체 invoke 호출 1
30
객체 invoke 호출 2
200
📔 함수 오버로딩
함수의 매개변수의 개수와 자료형만 달리하면 같은 이름의 함수를 여러 개 정의할 수 있다. 이것을 ‘함수 오버로딩’이라 한다.
📖 함수 식별자
- 함수 이름 + 시그니처를 식별자로 봄. 예) 두 개의 매개변수를 갖진 함수일 경우, 함수명 + (Int, Int)가 함수 식별자
- 함수 식별자가 다르면 다른 함수로 봄
📖 매개변수가 다른 함수 오버로딩
fun func(a: String, b: String): String { // 동일한 타입의 매개변수 두 개
return "func" // 반환값은 다름
}
fun func(a:String): Int { // 동일한 타입의 매개변수 하나
return 100 // 반환값은 다름
}
println(func("가", "나"))
println(func("가"))
func
100
📖 매개변수의 개수가 동일한 함수 오버로딩
fun test1(a: String, b: String? = null) { // 두 개의 매개변수
println("test1")
}
fun test1(a: Int, b: String) { // 두 개의 매개변수
println("test2")
}
test1(100, "a")
test1("ccc", "a")
test2
test1
📖 초깃값과 가변 인자로 함수 오버로딩 줄이기
fun test3(a: Any): String = "매개변수 하나"
fun test3(a: String, b: String = "Hello"): String = "매개변수 둘" // 초깃값 지정
fun test3(vararg a: String): String = "가변 매개변수" // 가변 인자 지정
println(test3("a", "b", "c", "d"))
println(test3("a"))
println(test3(100))
가변 매개변수
매개변수 둘
매개변수 하나