스코프 함수를 이야기 하기 전에 잠깐 람다함수의 몇가지 특별한 케이스들을 살펴보도록 하겠습니다!
1. 람다함수도 여러 구문의 사용이 가능
람다함수도 일반 함수처럼 여러 구문을 사용할 수 있는데요,
아래 예시와 같이 람다함수도 여러줄로 표현이 가능합니다
val c : (String) -> Unit = { str ->
println("$str 람다함수")
println("여러 구문을")
println("사용 가능합니다")
}
그리고 람다함수가 여러줄이 되는 경우에는 마지막 줄에 있는 결과값을 반환하게 됩니다
val calculate: (Int, Int) -> Int = {a, b ->
println(a)
println(b)
a+b
}
// a+b의 값을 Int로 반환
따라서 위 코드에선 마지막 구문인 a+b의 값만 Int로 반환하게 됩니다!
2. 파라미터가 없는 람다함수는 실행할 구문만 나열
파라미터가 없는 경우에는 중괄호 안에 실행할 구문만 나열해 주면 됩니다!
val a: () -> Unit = {println("파라미터가 없어요")}
3. 파라미터가 하나뿐이라면 it을 사용
파라미터가 여러개라면 람다함수 내에서 파라미터 이름을 일일히 써주었는데요!
만약 파라미터가 하나뿐이라면 it을 사용할 수 있습니다
아래 예시에서 파라미터 이름을 생략하고 println 안에서 it을 사용한 것을 확인할 수 있습니다
val c: (String) -> Unit = {println("$it 람다함수")}
이제 스코프 함수에 대해 알아보도록 하겠습니다
<스코프 함수 scope function>
스코프 함수는 특정 객체의 컨텍스트 안에서 특정 동작(속성 초기화, 활용 등)을 실행하기 위한 목적만을 가진 함수입니다
클래스에서 생성한 인스턴스를 스코프 함수에 전달하면 인스턴스의 속성이나 함수를
스코프 함수 내에서 편하게 사용할 수 있도록 하는 기능입니다
스코프 함수를 람다 함수로 사용하게 되면 임시로 스코프를 형성하게 되는데,
이 스코프 내에서는 객체의 이름을 일일히 참조할 필요없이 객체에 접근할 수 있다는 장점이 있습니다
스코프 함수에는 apply, run, with, also, let로 5가지가 있습니다
1. apply
apply는 인스턴스를 생성한 후 특정 변수에 할당하기 전에 '초기화 과정'을 수행할 때 사용됩니다
fun main() {
var a = Book("디모의 코틀린", 10000)
a.name = "[초특가]" + a.name
a.discount()
}
class Book(var name: String, var price: Int) {
fun discount() {
price -= 2000
}
}
이름과 가격을 받는 Book이라는 클래스를 만들고,
그 안에 2000원을 할인해주는 discount라는 함수를 만들었습니다
그리고 main 함수에서 인스턴스 a를 만들었습니다
이후 인스턴스 a의 책 이름에 [초특가] 라는 문자열을 넣어주고 discount를 수행하기 위해서
인스턴스를 저장한 변수 a에 참조 연산자인 .을 이용하여 속성과 함수를 사용할 수 있는데요
참조연산자 대신 아래와 같이 apply를 사용하여 나타낼 수도 있습니다
fun main() {
var a = Book("디모의 코틀린", 10000).apply {
name = "[초특가]" + name
discount()
}
}
class Book(var name: String, var price: Int) {
fun discount() {
price -= 2000
}
}
apply를 사용하면 인스턴스를 생성하자마자 그 인스턴스에 참조연산자를 사용하여 apply를 붙이고
중괄호로 람다 함수를 하나 만들어 apply의 scope 안에서 직접 인스턴스의 속성과 함수를 참조연산자 없이 사용이 가능합니다
또한 apply는 인스턴스 자신을 다시 반환하므로 위처럼 생성되자마자 조작된 인스턴스를 변수에 바로 넣어줄 수 있습니다
따라서 apply는 새로 생성된 인스턴스를 반환한다는 특징이 있습니다
(반환된 결과는 객체 자신!)
그리고 이렇게 apply와 같은 스코프 함수를 사용하면 main 함수와 '별도의 scope'에서 인스턴스의 변수와 함수를 조작하므로
코드가 깔끔해진다는 장점이 있습니다!
2. run
run은 apply처럼 run 스코프 안에서 참조연산자를 사용하지 않아도 된다는 점은 같지만
일반 람다함수처럼 인스턴스 대신 마지막 구문에 스코프 내 결과값을 반환한다는 차이점이 있습니다
var b = a.run {
println(a.price)
a.name
}
// 가격은 출력하지만 마지막 구문인 이름은 반환하여 b라는 변수에 할당됨
따라서 이미 인스턴스가 만들어진 후에 인스턴스의 함수나 속성을 scope 내에서 사용해야 할 때 유용합니다!
이번엔 위에서 apply를 사용해 만든 변수 a의 내용을 run을 이용해 출력해보도록 하겠습니다
fun main() {
var a = Book("디모의 코틀린", 10000).apply {
name = "[초특가]" + name
discount()
}
a.run {
println("상품명: ${name}, 가격: ${price}원")
}
}
class Book(var name: String, var price: Int) {
fun discount() {
price -= 2000
}
}
/* 출력
상품명: [초특가]디모의 코틀린, 가격: 8000원
*/
변수 a에 참조 연산자를 사용하여 run을 붙이고 중괄호 안에서 인스턴스의 속성 이름을 직접 사용하여 (name, price)
내용을 출력해주면 됩니다
실행해보면 apply에서 수정한 책 이름과 할인된 가격인 8000원이 출력되는 것을 확인할 수 있습니다
3. with
with는 run과 동일한 기능을 가지지만 단지 인스턴스를 참조연산자 대신 파라미터로 받는다는 차이가 있습니다
a.run {...} // 참조연산자를 사용하는 run
with(a) {...} // 참조연산자 대신 파라미터를 사용하는 with
a.run {
println("상품명: ${name}, 가격: ${price}원")
}
with(a) {
println("상품명: ${name}, 가격: ${price}원")
}
4. also / let
also는 apply와 비슷하게 처리가 끝나면 인스턴스를 반환하고,
let은 run과 비슷하게 처리가 끝나면 최종값을 반환하는 기능을 가지고 있습니다
그렇지만 한가지 공통적인 차이점이 있는데요
apply와 run이 참조연산자 없이 인스턴스의 변수와 함수를 사용할 수 있었다면
also와 let은 마치 파라미터로 인스턴스를 넘긴것처럼 it을 이용해 인스턴스를 사용할 수 있습니다!
이 두 함수가 굳이 파라미터를 통해 인스턴스를 사용하는 이유는 바로 같은 이름의 변수나 함수가
'scope 바깥에 중복'되어 있는 경우에 혼란을 방지하기 위해서 입니다
실제 개발을 하다보면 변수이름이 같은 것들이 사용될 때 이와 같은 혼란이 일어날 수 있습니다
따라서 이런 혼란을 방지하기 위해서는 필요한 경우 also와 let을 사용해주는 것이 좋습니다
fun main() {
var price = 5000
var a = Book("디모의 코틀린", 10000).apply {
name = "[초특가]" + name
discount()
}
a.run {
println("상품명: ${name}, 가격: ${price}원")
}
}
class Book(var name: String, var price: Int) {
fun discount() {
price -= 2000
}
}
/* 출력
상품명: [초특가]디모의 코틀린, 가격: 5000원
*/
Book 클래스의 속성이름과 같은 price 변수를 하나 만들어 5000이라는 숫자를 할당해보았습니다
그리고 실행을 해보면 가격이 8000원이 아닌 5000원이 출력되는 것을 확인할 수 있는데요
이는 run 함수가 인스턴스 내의 price 속성보다 run이 속해있는 'main 함수'의 price 변수를 우선시하고 있기 때문입니다
이럴때는 run 대신 let을 사용하고 name 대신 it.name, price 대신 it.price를 사용해주면
인스턴스의 값이 정상적으로 출력할 수 있습니다
fun main() {
var price = 5000
var a = Book("디모의 코틀린", 10000).apply {
name = "[초특가]" + name
discount()
}
a.run {
println("상품명: ${name}, 가격: ${price}원")
}
a.let {
println("상품명: ${it.name}, 가격: ${it.price}원")
}
}
class Book(var name: String, var price: Int) {
fun discount() {
price -= 2000
}
}
/* 출력
상품명: [초특가]디모의 코틀린, 가격: 5000원
상품명: [초특가]디모의 코틀린, 가격: 8000원
*/
그리고 apply 역시 같은 경우가 있다면 also로 대체하여 사용하면 됩니다
스코프 함수는 인스턴스의 속성이나 함수를 scope 내에서 깔끔하게 분리하여 사용할 수 있다는 점 때문에
코드의 가독성을 향상시킨다는 장점이 있습니다!
<스코프 함수>는 여기서 마치도록 하겠습니다
'Kotlin' 카테고리의 다른 글
[Kotlin] 익명객체와 옵저버 패턴 (0) | 2023.07.28 |
---|---|
[Kotlin] 오브젝트 Object (0) | 2023.07.27 |
[Kotlin] 고차함수와 람다함수 (0) | 2023.07.25 |
[Kotlin] 변수, 함수, 클래스의 접근범위와 접근제한자 (0) | 2023.07.24 |
[Kotlin] 코틀린 프로젝트의 구조 (0) | 2023.07.23 |