안녕하세요. 오늘은 코틀린 Scope Function에 대해서 알아보도록 하겠습니다.
Scope Function 란
코틀린으 표준 라이브러리에서는 Scope Function을 지원하고 있습니다. Scope Fuction은 객체 컨텍스트 내에서 코드 블럭을 실행할 수 있도록 해줍니다. 즉, Scope Fuction을 이용하면 객체의 정보를 기본적으로 가지고 있는 코드 블럭을 만들어 사용할 수 있어 간결한 코딩을 가능하게 해줍니다. 아래 예제를 보시면 좀 더 이해가 되실 것입니다.
오늘 포스팅에서 사용할 클래스의 코드는 아래와 같습니다.
class MappingService(
private var name: String,
private var age: Int
) {
fun changeName(name: String) {
this.name = name
}
fun incrementAge() {
age++
}
override fun toString(): String {
return "이름 = $name, 나이 = $age"
}
}
위 클래스의 인스턴스 하나 생성 한 후 출력, 그리고 수정하여 다시 출력하는 코드를 만들어 보겠습니다. 그러면 아래 처럼 코드를 작성할 수 있습니다.
val mappingService = MappingService("sabarada", 15)
println(mappingService) // 결과 : 이름 = sabarada, 나이 = 15
mappingService.changeName("karol")
mappingService.incrementAge()
println(mappingService) // 결과 : 이름 = karol, 나이 = 16
이것을 Scope Functions를 이용하여 작성해보도록 하겠습니다. 그러면 아래와 같이 작성할 수 있습니다. 아래 예제의 it
이라는 키워드는 객체 그 자신을 가리킵니다. 이렇게 봤을 때 Scope Functions를 사용했을 때 기존의 코드에 비해서 많이 가독성이 편해진 것을 느끼실 수 있으실 것입니다.
MappingService("sabarada", 15).let {
println(it) // 결과 : 이름 = karol, 나이 = 16
it.changeName("karol")
it.incrementAge()
println(it) // 결과 : 이름 = karol, 나이 = 16
}
용어 정리
Scope Functions은 let
, run
, with
, apply
, also
5가지의 종류를 가지고 있습니다. 이 5가지의 Scope Functions는 컨텍스트 객체를 참조하는 방법
, 반환 값
, 확장 함수 여부
여부에 따라 각각 다르게 사용되어집니다. 3가지 요소에 대해서 한번씩 알아보도록 하겠습니다.
컨텍스트 객체를 참조하는 방법(The way to refer to the context object)
scope function의 블럭은 람다(lambda) function 입니다. 이 람다의 파라미터로 2가지를 가지고 있는데요. this
와 it
입니다. 해당 키워드를 통해 객체를 이용가능하게 되는 것입니다. 둘이 커버할 수 있는 커버리지가 다르지는 않으며 단지 본인의 코딩 스타일에 따라서 선택하시면 좋을 것 같습니다.
let, also가 it을 통해서 context object를 참조합니다. it
은 this
보다 짧으며 조금 더 읽기 쉽다는게 장점이라고 합니다. 하지만 scope function block 안에 또다른 람다가 사용되어 it
이 사용된다면 두개의 it
을 구분짖기가 힘듭니다. 그러므로 단일 block에서만 사용할 때 추천드립니다.
MappingService("sabarada", 15).let {
println(it) // 결과 : 이름 = karol, 나이 = 16
it.changeName("karol")
it.incrementAge()
println(it) // 결과 : 이름 = karol, 나이 = 16
}
run, with, apply는 this
를 통해서 context object를 참조합니다. 그래서 사용할 때 class 내부에 있는 함수의 형식으로 사용하실 수 있습니다. 또한 this를 생략하는 것도 가능합니다. 하지만 이럴 경우 외부 함수와 context object 함수의 구분이 어렵기 때문에 this
는 붙이는것을 추천드립니다.
MappingService("sabarada", 15).run {
println(this) // 결과 : 이름 = karol, 나이 = 16
this.changeName("karol")
incrementAge() // this 생략
println(this) // 결과 : 이름 = karol, 나이 = 16
}
scope function에서 사용하고 있는 람다 내부 파라미터로 구분 지으면 아래와 같습니다.
- this : run, with, apply
- it : let, also
반환 값(Return value)
Scope Funtions에서는 context object를 반환또는 lambda 결과를 반환하는 scope function으로 구분 지어집니다.
apply와 also는 context object를 반환합니다. 즉, scope function을 호출 시킨 자기자신을 반환하는 것입니다. 그러므로 호출 시 call chaning을 이용할 수 있습니다. 아래 예제가 also를 call chaining을 구현한 코드입니다.
MappingService("sabarada", 15)
.also { println(it) } // 결과 : 이름 = karol, 나이 = 16
.also { it.changeName("karol") }
.also { it.incrementAge() }
.also { println(it) } // 결과 : 이름 = karol, 나이 = 16
let, run, with는 람다(lambda) 결과를 반환합니다. 즉, scope function block에서 마지막 명령어의 결과를 반환합니다. 아래 예제를 보도록 하겠습니다. also로 call chaning을 하면서 마지막에 let을 통해서 println
은 반환값을 가지지 않기때문에 전체 call chaining 도 반환 값을 가지지 않도록 합니다. 불필요한 객체 반환을 막은 것입니다.
MappingService("sabarada", 15)
.also { println(it) } // 결과 : 이름 = karol, 나이 = 16
.also { it.changeName("karol") }
.also { it.incrementAge() }
.let { println(it) } // 결과 : 이름 = karol, 나이 = 16
반환 값을 기준으로 구분지으면 아래와 같습니다.
- context object : apply, also
- lambda result : let, run, with
확장 함수 여부(Is extension function)
마지막으로 비교해볼 수 있는 점은 확장함수로 사용하는지 아니면 별도로 선언해서 사용하는지 구분해 볼 수 있습니다. 기본적으로 scope function은 확장함수 형식으로 사용합니다. 하지만 with
는 확장함수형식으로 사용하는 것이 아닌 context object를 파라미터로 받는 함수 형식으로 사용할 수 있습니다. 사용방법은 아래와 같습니다.
with(MappingService("sabarada", 15)) {
println(this) // 결과 : 이름 = karol, 나이 = 16
this.changeName("karol")
this.incrementAge()
println(this) // 결과 : 이름 = karol, 나이 = 16
}
구현체
위에서 Scope Functions의 구현이 컨텍스트 객체를 참조하는 방법
, 반환 값
, 확장 함수 여부
에 의해서 구분되어지는 것에 대해서 알아보았습니다. 이제 각 구현체들이 어떻게 구현하고 있는지, 그리고 어디에서 사용하면 좋을지에 대해서 알아보는 시간을 가져보도록 하겠습니다.
let
let
은 it
을 파라미터로 사용하고 있습니다. 그리고 람다(lambda) 결과를 반환하는 scope function 입니다. let
은 non-null 일 경우 특정 로직을 실행하도록 많이 사용합니다. 아래 예제를 보도록 하겠습니다.
val mappingService: MappingService? = createService() // nullable 한 함수 !
mappingService?.let { // mappingService가 null이 아닐 경우에만 let scope Funtion 실행
println(it) // 결과 : 이름 = karol, 나이 = 16
it.changeName("karol")
it.incrementAge()
println(it) // 결과 : 이름 = karol, 나이 = 16
}
run
run
은 this
를 이용하여 접근이 가능합니다. 그리고 람다 결과를 반환합니다. run
은 객체 환경설정과 연산된 결과를 반환하는데 사용할 수 있습니다. 또한 run
을 확장함수가 아닌 단독으로 사용하여 block
으로 값을 만들어 내는데도 사용할 수 있습니다.
val objectString: String = MappingService("sabarada", 15).run {
this.incrementAge()
this.toString()
}
println(objectString) // 결과 : 이름 = karol, 나이 = 16
val resultString: String = run {
val first: String = "sabarada"
val second: String = "Karol"
"$first is $second"
}
println(resultString) // 결과 : sabarada is Karol
with
with
는 확장 함수가 아닙니다. with는 객체를 파라미터로 받습니다. lambda fucntion 내부에서는 this
를 파라미터로 이용합니다. with는 collection 에서 별도의 그룹핑에 많이 사용된다고 합니다.
val mappingList: List<MappingService> =
listOf(MappingService("sabarada", 15), MappingService("karol", 15))
val sumValue = with(mappingList) {
var sumValue = 0
for (value in mappingList) {
sumValue += value.getAge()
}
sumValue
}
println(sumValue)
apply
apply는 this를 이용해서 해당 객체에 접근할 수 있습니다. 그리고 객체 자체를 반환하는 특성을 지니고 있습니다. 이런 apply는 주로 설정할 때 많이 사용됩니다.
val mappingService: MappingService = MappingService("sabarada", 15)
.apply {
this.incrementAge()
this.incrementAge()
}
println(mappingService) // 결과 : 이름 = sabarada, 나이 = 17
also
마지막으로 알아볼 scope function은 also
입니다. also
는 it을 이용하여 객체접근이 가능하며 객체 자체를 반환합니다. 일반적으로 call chaning에 많이 사용합니다.
MappingService("sabarada", 15)
.also { println(it) } // 결과 : 이름 = karol, 나이 = 16
.also { it.changeName("karol") }
.also { it.incrementAge() }
.also { println(it) } // 결과 : 이름 = karol, 나이 = 16
정리
함수 | 객체 참조 방법 | 반환 값 | 확장 함수 여부 |
---|---|---|---|
let | it | Lambda result | Yes |
run | this | Lambda result | Yes |
run | this | Lambda result | No: called without the context object |
with | this | Lambda result | No: takes the context object as an argument. |
apply | this | Context object | Yes |
also | it | Context object | Yes |
마무리
오늘은 이렇게 kotlin의 scope function에 대해서 알아보는 시간을 가져보았습니다.
감사합니다.
참조
'language, framework, library > kotlin' 카테고리의 다른 글
[kotlin] 코틀린 차곡차곡 - 10. 인라인(inline) 함수와 reified 키워드 (3) | 2021.07.14 |
---|---|
[kotlin] 코틀린 차곡차곡 - 9. 연산자 오버로딩 (Operator Overloading) (2) | 2021.07.04 |
[kotlin] 코틀린 차곡차곡 - 7. NPE(NullPointException) 안전하게 코딩하기 (2) | 2021.06.20 |
[kotlin] 코틀린 차곡차곡 - 6. 클래스 - static(companion)과 접근 제어자 (1) | 2021.06.19 |
[kotlin] 코틀린 차곡차곡 - 5. 클래스 - 상속과 인터페이스 (0) | 2021.06.15 |
댓글