모든 코드는 Kotlin Playground에서 실행했습니다.
data class User(var name: String)
우선 테스트를 위해 대충 만들어진 data class 하나.
스코프 함수는 객체에 바로 접근하여 scope 내에서 해당 객체의 이름 대신 키워드로 변수를 사용할 수 있게 한다. 일종의 기능이 있는 do while(false) 라고 생각하면 될 것 같다.
스코프 함수가 필수인 건 아님. 일종의 Syntex sugar. (하지만 취업하려면 필수일 것이다)
총 다섯 종류 let, apply, also, run, with이 있다.
- 당연히 모두 Generic이다.
- with 빼고는 모두 확장함수이다. (with은 객체를 인자로 받는 일반 함수이다)
- with 빼고는 null safe 연산자를 사용할 수 있다. 연산자를 사용했을 때 객체가 null이었으면 실행되지 않는다. with은 확장함수가 아니기 때문에 null safe 연산자를 사용할 구석이 없다.
null.let {
println("null is executed")
}
null?.let {
println("null with null-safe operator is not executed")
}
위는 실행되고 아래는 실행되지 않는다.
var rightUser: User? = User("John Doe");
rightUser.let {
println("fine user throw error")
it.name
}
rightUser?.let {
println("fine user with null-safe operator is executed")
it.name
}
nullable한 값에서 null-safe 연산자를 사용하지 않으면 오류를 던진다.
scope 함수에서 가져오는 객체를 사용하지 않으면 오류 없이 실행이 되긴 하는데 그럴거면 scope 함수 왜씀?
var nullUser: User? = null;
nullUser.let {
println("null user throw error")
it.name
}
nullUser?.let {
println("null user with null-safe operator is not executed")
it.name
}
객체가 null이면 실행되지 않는다.
당연.
스코프 함수별로 객체를 가리키는 키워드가 다른데, it과 this가 있다.
this는 class에서의 그 this와 같아서 스코프 안에 같은 이름의 변수가 없다면 생략할 수 있다.
it이든 this든 하여간 원래 변수 이름보다는 짧을 것이기 때문에 (파이썬에서 range-based loop를 처음 봤을 때를 생각하자) 코드 길이를 줄이는 데 도움이 될 것이다.
scope 안에서 함수를 하나만 사용하고, 그 함수가 인수를 하나만 받고, 그 인수에 객체를 넣을 거면 사용할 수 있는 형태가 있다.
var rightUser: User? = User("John doe")
var q = rightUser.let(::println)
let
var a = User("a").let {
println(it.name)
it.name = "v"
println(it.name)
}
println(a)
a
v
kotlin.Unit
- 확장함수
- scope 안에서 it으로 사용
- 마지막 식의 결과를 반환함 (여기서는 println의 결과인 Unit을 반환함)
apply
var b = User("b").apply {
println(this.name)
name = "w"
println(name)
}
println(b)
b
w
User(name=w)
- 확장함수
- scope 안에서 this로 사용
- 객체를 다시 반환함 (원본을 수정하는 거임)
처음 본 인상으로는 원본에서 뭔가 바꾼 새 객체를 만드는 줄 알았는데 그거는 아니다.
var testUser = User("John doe")
var testUser2 = testUser.apply {
this.name += " the disester"
}
println(testUser)
println(testUser2)
User(name=John doe the disester)
User(name=John doe the disester)
also
var c = User("c").also {
println(it.name)
it.name = "x"
println(it.name)
}
println(c)
c
x
User(name=x)
- 확장함수
- scope 안에서 it으로 사용
- 객체를 다시 반환함 (얘도 원본 수정)
it을 쓴다는 걸 빼면 apply와 똑같아 보임.
run
var d = User("d").run {
println(this.name)
name = "y"
println(name)
}
println(d)
d
y
kotlin.Unit
- 확장함수
- scope 안에서 this로 사용
- 마지막 식의 결과를 반환함
let의 this 버전.
with
var e = with(User("e")) {
println(this.name)
name = "z"
println(name)
}
println(e)
e
z
kotlin.Unit
- 확장함수가 아님
- scope 안에서 this로 사용
- 마지막 식의 결과를 반환함
var rightUser: User? = User("w?o?man")
var p = with(rightUser) {
print(name)
}
var q = with(rightUser) {
print(this?.name)
}
확장함수가 아니기 때문에 with 안의 this는 nullable이다. this를 생략하고 사용하면 오류를 던진다.
this?.name으로만 써야 한다.
뭔가 동작이 전부 같아 보이지만 내가 감히 추측해보기론, 현업에서는 전부 역할을 구분해두고 쓸 것이다.
(어떤 동작에서는 let, 어떤 동작에서는 run 하는 식으로)
Reference
https://blog.yena.io/studynote/2020/04/15/Kotlin-Scope-Functions.html
https://kotlinlang.org/docs/scope-functions.html
유광무 팀장님의 'Hello, Android!' 설명.