본문 바로가기

[Kotlin] Scope 함수 - 'run, with, apply, let, also'

Kotlin - Scope(스코프)함수 정리

 

 

코틀린에서 제공하는 여러 함수 중에서 기본적인 Scope(스코프) 함수 5종에 대한 정리

 

종합 정리 먼저보기

apply / also 

      두 함수 모두, 인스턴스(객체)를 리시버 객체로 전달, 반환 값으로 인스턴스(객체)를 반환
      apply()참조연산자(it) 사용x, 직접적인 접근 name="", age=1 등
      also()참조연산자(it) 사용, 사용하지 않으면 접근불가 it.name"", it.age=1 등 

run / let 

      두 함수 모두, 인스턴스(객체)를 리시버 객체로 전달, 반환 값으로 결과 값(마지막 라인) 반환
      run() 참조연산자(it) 사용x, 직접적인 접근 name="", age=1 등
      let() 참조연산자(it) 사용, 사용하지 않으면 접근불가 it.name"", it.age=1 등 

with

      매개변수(인자)를 받아 리시버 객체로 전달, 반환 값으로 인스턴스(객체) 반환
      run() 참조연산자(it) 사용x, 직접적인 접근 name="", age=1 등
      let() 참조연산자(it) 사용, 사용하지 않으면 접근불가 it.name"", it.age=1 등

 

Scope Functions

run

run() 함수는 (1) 익명 함수처럼 사용하는 방법(2) 객체에서 호출하는 방법모두 제공하는 함수

반환값이 인스턴스(객체)가 아닌 마지막 값을 Retrun 한다는 점이 중요



1) 익명함수처럼 사용하는 방법

fun <T, R> T.run(block: T.() -> R): R

 

    : 블록의 결과를 반환 (결과 - 블록내 마지막라인)
      블록 내 선언된 변수는 모두 임시 변수(로컬 변수)로 사용
      복잡한 계산에 임시변수가 많이 필요할 때 유용하게 사용하는 함수

val result = run {	// run 함수 스코프 블록 시작 - {}
    val item = 20       // 물건 개수, 블록 내 임시변수(로컬=지역변수)
    val price = 4000    // 개당 가격, 블록 내 임시변수(로컬=지역변수)
    val discountPercent = 25.0  // 25% 할인, 블록 내 임시변수(로컬=지역변수)
    ((item * price) * ((100-discountPercent) * 0.01)).toInt()	// result변수에 저장될 결과값
}
// run()함수의 스코프 블록 내부에서 마지막 값이 result에 저장되는 함수

println ("할인된 가격 : ${result}원 ")		// run()함수의 마지막 값만 저장됨


ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// 출력
할인된 가격 : 60000원

 

    : 블록 내 마지막 라인이 아닌 값은 사라짐
      복잡한 계산에 임시변수가 많이 필요할 때 유용하게 사용하는 함수

val result = run {
    val item = 20       // 물건 개수
    val price = 4000    // 개당 가격
    val discountPercent = 25.0  // 25% 할인

    ((item * price) * ((100-discountPercent) * 0.01)).toInt() // 이 계산값은 사라짐 (의미없는 라인)

    "마지막 결과값 테스트"	// 마지막라인이 실질적으로 result에 저장될 값
}

println ("할인된 가격 : ${result}원 ")

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// 출력
할인된 가격 : 마지막 결과값 테스트원

 

2) 객체에서 호출하는 방법

fun <R> run(block: () -> R): R

 

    : 객체를 블록의 리시버 객체(Receiver Object)로 전달하고 블록의 결과를 반환 (블록의 결과 - 마지막 라인)

      객체의 메서드처럼 호출하기 때문에 안전한 호출(?.) 연산자로 null이 아닐 경우에만 run함수를 실행할 수 있는 점
      
      객체를 리시버 객체로 전달받기 때문에 this를 통해 자신참조 가능하며, this를 생략해서 참조도 가능한 직관적인 점

      반환값 - 인스턴스(객체)가 아닌 마지막 값을 return

var str :String = "TSET Code"

// 객체에서 호출하기 때문에 안전한 호출(?.)로 안전하게 코드 실행이 가능, Null이 아니면 run함수 수행
str = str?.run {
    println(this.toUpperCase()) // 리시버 객체로 받기에 this로 자신 참조가능
    println(toLowerCase())      // 리시버 객체로 유일하기 때문에 this 생략 가능
    "result Code"   // 마지막 결과를 반환
}

// TEST Code -> result Code로 변환된 결과 출력
println(str)

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// 출력
TSET CODE
tset code

result Code


 

apply

fun <T> T.apply(block: T.() -> Unit): T

스코프 블럭에 객체 자신리시버 객체로 전달되고, 이 리시버 객체가 반환되는 함수 (인스턴스 반환)

주로, 객체의 상태를 변화시키고 그 객체를 다시 반환하는 용도로 자주 사용하는 함수

var user1 = User()  // 기본값으로 객체 생성, name - "아무개" age - 0
println("${user1}")

// apply() 객체의 설정을 변경하고 그 객체를 리턴
var user2 = user1?.apply {   // user1을 리시버 객체로 받는 apply 함수 실행
	name = "김루피"    // this.name 동일, 리시버 객체 프로퍼티에 직접 접근 가능
	age = 200         // this.age 동일, 리시버 객체 프로퍼티에 직접 접근 가능
}

println("${user1} ${user1.hashCode()}") // user2의 프로퍼티 값과 hashCode 출력
println("${user2} ${user2.hashCode()}") // user2의 프로퍼티 값과 hashCode 출력
println("${user1===user2}")     // user1과 user2는 같은 객체인지? - true
}

// Data클래스 (VO 클래스), 기본값 설정 name "아무개", age 0
data class User(var name: String = "아무개", var age: Int = 0)

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// 결과
User(name=아무개, age=0)
User(name=김루피, age=200) 1376099252		// apply후 user2 프로퍼티와 hashCode
User(name=김루피, age=200) 1376099252		// apply후 user1 프로퍼티와 hashCode
true						// user1===user2, 동일 객체인지?


: apply() 함수는 객체 자신을 참조하고, 객체 자신을 반환(return)하기 때문에, 위 예시에서 user2와 user1은 동일 객체

run() - apply()는 유사하지만 반환하는 형태의 차이인 점

   : run() - 결과 값 (마지막 라인)

     apply() - 인스턴스 (객체 본인)

 

with

fun <T, R> with(receiver: T, block: T.() -> R): R


with()
함수는 run/apply와 다른점은 인수(매개변수)로 인스턴스(객체)를 받고 블록에 리시버 객체로 전달하는 함수

 : run / apply 함수는 인수(매개변수)가 없는 함수

run 함수 예시 - str객체를 대문자로 변경해서 다시 저장하는 방식

var str :String = "TEST Instance"

// run, 블록 내 마지막 라인을 결과 값으로 반환, 반환 값을 str에 저장하는 방식
str = str.run {
    toUpperCase()
}


 apply 함수 예시 - str객체에 대문자를 변경하는 방식

var str :String = "TEST Instance"

// apply 함수, str 객체의 값을 변경하고 str 객체를 반환
var test = str.apply {
	toUpperCase()
}


with() 함수 예시
 - str객체를 인수(매개변수)로 받아 블록 내 리시버 객체로 전달하는 방식

   with함수의 인수로 객체를 전달, 전달받은 객체를 반환 한다는 점
   with함수의 인자로 받기 때문에 안전한 호출(?.)이 불가능한 점

var str :String = "TEST Instance"

// with 함수는 인수(매개변수)를 받아서 그 인스턴스를 블록의 리시버 객체로 전달
var test = with(str) {
	// 'str.toUpperCase()' or 'this.toUpperCase()' 와 동일
	toUpperCase()	// 전달받은 리시버 객체(with함수 매개변수'str')를 참조
}

 

 

also

fun <T> T.also(block: (T) -> Unit): T

apply()와 기능은 동일한 함수, but 참조연산자 (it)을 사용한다는 점

apply와 기능은 동일한데 굳이 also 함수를 왜 사용할까? 라는 생각이 들었는데 사용하는 이유는 간단.
apply run함수는 리시버 객체로 전달받은 본인을 사용할때 참조연산자 없이 바로 사용이 가능

예시) User클래스 프로퍼티(멤버변수)와 현재 메서드 위치 로컬(지역)변수와 이름이 동일할 경우
  : 현재 메서드 처리위치의 로컬변수를 우선시 참조 (main함수 로컬변수 name)


  : apply로 해당객체의 프로퍼티(User클래스 name)을 참조하려면 this.name으로 해야 참조가 가능

 

also() 함수 사용 예시
  : also는 run/apply와 달리 참조연산자(it)을 사용해야만 프로퍼티 접근 가능, 생략 시 접근 불가


apply 처럼 반환은 인스턴스(객체)로 반환

var user = User()

user.also {
    it.name = "최상디"	// 전달된 리시버 객체를 참조연산자(it)를 사용해야만 참조가 가능
    it.age = 50
    name = "박조로" 	// Error, 참조연산자(it)없이는 접근불가
}

println("${user}")
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// 결과
User(name=최상디, age=50)

 

let

fun <T, R> T.let(block: (T) -> R): R

run() 함수와 기능은 동일, but 참조연산자 (it)을 사용한다는 점

let() 함수는 run()과 동일한 기능이며 also() 함수처럼 참조연산자(it) 없이 프로퍼티에 접근이 불가능하다는 점

let() 함수 사용 예시
  : let는 run/apply와 다르게 참조연산자(it)을 사용해야만 프로퍼티 접근이 가능, 생략하고는 접근이 불가


run처럼 반환은 결과 값으로 반환 (마지막 라인 값)

 var user = User("박조로", 20)

var oldYear = user.let {
	var agePlus =  it.age + 30		// user의 프로퍼티에 접근하려면 참조연산자(it) 필요
    println("${it.age} -> ${agePlus}")	// user의 age에 30살을 더한 변화 출력
    agePlus		// let의 반환값(마지막 라인)
}

println(oldYear)

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// 결과
20 -> 50		// let함수 블럭 내 println()
50				// oldYear 값