[Kotlin] 코틀린과 표준함수

5 minute read

클로저

: 람다식으로 표현된 내부 함수에서 외부 범위에 선언된 변수에 접근할 수 있는 개념

람다식 안에 있는 외부 변수는 값을 유지하기 위해 람다가 포획한 변수 !

fun main() {
	val calc = Calc()
	var result = 0 //외부변수 
	calc.addNum(2,3) { x, y -> result = x + y } //result의 결과가  함수 외부의 result에 반영됨 
	println(result) // 5
}

class Calc(){
	fun addNum(a: Int, b: Int, add: (Int, Int) -> Unit){ //람다식에서는 반환값 x
		add(a,b)
	}
}

코틀린의 표준 라이브러리 함수

  • let()
  • apply()
  • with()
  • also()
  • run()

let()

: T를 매개변수로 받아 block으로 넘기고 block의 결과값 R을 반환

//T : 사용요소, R : 반환요소 
public inline fun<T, R> T.let(block: (T) -> R): R{ ... return block(this)}
fun main() {
	val score : Int? = 32
	//var score = null

	fun checkScore(){
		if(score != null) {
			println("Score: $score")
		}
	}

	fun checkScoreLet(){
		score?.let{ println("Score: $it") } //score를 받아서 it으로 사용할 수 있음  
		val str = score.let { it.toString() }
		println(str)
	}

	checkScore()
	checkScoreLet()
}
  • Let함수의 체이닝
fun main() {
	var a = 1
	val b = 2

	a = a.let { it + 2 }.let { //a에다가 2 더한 결과값에 let을 바로 사용해서 {}안의 내용을 실행
		println("a = $a")//3 출력 후 
		val i = it + b // 2를 더함
		i  //결과값이 a에 반영됨 
	}
	println(a) //5 
}
  • 중첩 사용
var x = "Kotlin"
x.let { outer -> //it을 사용하지 않고 명시적 이름 사용 
		outer.let { inner ->
				print("Inner is $inner and outer is $outer")
		}
}
  • 반환값은 바깥쪽의 람다식에만 적용
var x = "Kotlin"
x = x.let { outer -> // x 에 let안의 내용을 할당  
		outer.let { inner ->
				print("Inner is $inner and outer is $outer")
				"Inner String"//할당 x 
		}
		"Outer String" //이것만 x에할당됨 
}
  • 안드로이드에서의 let 활용

let을 사용하지 않을 때

val padding = TypedValue.applyDimension(
			TypedValue.COMPLEX_UNIT_DIP, 16f, resources.displayMetrics).toInt()
setPadding(padding,0,padding, 0)

let을 사용

TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f, 
			resouces.displayMetrics).toInt().let{ //padding -> 해서 padding으로 사용할수도 있음 
				setPadding(it,0,it,0)//인자가 한개이기 때문에 it 사용 
}
  • null 검사

safeCall 과 같이 사용 !

val obj : String?

obj?.let { //null이 아닌 경우에만 수행 
	Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show()
}
  • else문이 포함된 문장 대체
val firstName : String? 
firstName?.let { print("$it") } ?: print("0") //null이 아닌경우만 let 블록을 수행하기 때문에, null이라면 0을 출력 

also()

: 함수를 호출하는 객체 T를 이어지는 block에 전달하고 객체 T 자체를 반환

public inline fun <T> T.also(block: (T) -> Unit) : T {block(this); return this }
var m = 1
m = m.also{ it + 3 } //블록 안의 코드 수행결과와 상관없이 원본 객체 m을 반환하기 때문에 1이 반환됨 
println(m) //1 
  • let과 also
fun main(){
    data class Person(var name: String, var skills : String){}
    var person = Person("kildong", "Kotlin")

    val a = person.let {
        it.skills = "Android" //person객체의 skills가 Android로 변경됨 
        "success" //a에 success가 반영 
    }
    println(person)// Person(name=kildong, skills=Java)
    println("a: $a") // a: success

    val b = person.also{
        it.skills = "Java" //person객체의 skills가 Java로 변경됨 
        "success" //객체 person 자체를 반환하기 때문에 b에 success가 반영되지 않음
    }
    println(person)// Person(name="kildong", skills="Java")
    println("b: $b") // Person(name="kildong", skills="Java")
}
  • 안드로이드에서의 let과 also 활용

let과 also를 사용하지 않을 때

fun makeDir(path: String) : File {
	val result = File(path)
	result.mkdirs()
	return result
}

let과 also를 사용할 때

//let은 File로 만들고 also를 통해 넘겨 디렉토리로 변경된다. 
//이때 also에서는 결과값을 반환하지 않기 때문에, 최종적으로 디렉토리 변경까지 마친 결과가 let에서 반환된다.  
fun makeDir(path : String) = path.let{File(it)}.also{it.mkdirs()}

👍 let()과 also()의 차이점 정리

⇒ let() : block 안의 내용을 적용시키고, 그 결과 반환

⇒ also() : block 안의 내용만 적용 시키고 결과 반환 x


apply()

: also()함수와 마찬가지로 호출하는 객체 T를 이어지는 block으로 전달하고 객체 자체인 this를 반환

확장 함수 형태로 처리됨 ! → it을 사용하지 않고 this로 사용 하지만 this는 생략 가능

public inline fun <T> T.apply(block: T.() -> Unit) : T { block(); return this }
data class Person(var name: String, var skills : String){}
    var person = Person("kildong", "Kotlin")

    //this는 person을 가리킴
    person.apply{ this.skills = "Swift" }
    println(person) //Person(name=kildong, skills=Kotlin)

    val returnObj = person.apply{
        name = "Sean" //this 없이 객체의 멤버에 여러번 접근
        skills = "Java"
    }

    println(person) //Person(name=Sean, skills=Java)
    println(returnObj) //Person(name=Sean, skills=Java)

👍 also()와 apply()의 차이점 정리

⇒ also() : it으로 받고 생략할 수 없음

⇒ apply() : this로 받고 생략 가능, 반복되는 객체의 변수를 다룰 때 주로 사용하는 듯 obj.name obj.grade 이런식으로 obj 하나하나 안쓰고 걍 name, grade로 바로 사용

  • 안드로이드에서의 apply

apply를 사용하지 않았을 때

val param = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT)
param.gravity = Gravity.CENTER_HORIZONTAL
param.weight = 1f
param.topMargin = 100
param.bottomMargin = 100

apply를 사용할 때

val param = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
    gravity = Gravity.CENTER_HORIZONTAL
    weight = 1f
    topMargin = 100
    bottomMargin = 100
}
//fun makeDir(path : String) = path.let{File(it)}.also{it.mkdirs()}
File(path).apply { mkdirs() } //이렇게 간단하게 사용할 수 있음 

⇒ let과 also를 사용했던 문장을 → apply하나만 사용해서 더 간단하게 만들 수 있음


run()

: 인자가 없는 익명함수처럼 동작하는 형태와 객체에서 호출하는 형태 두 가지로 사용

public inline fun <R> run(block: ()-> R) : R = return block()
public inline fun <T, R> T.run(block: T.() -> R): R = return block() 
fun main() {
	data class Person(var name: String, var skills : String)
    var person = Person("kildong", "Kotlin")

    val returnObj = person.apply{
        name = "Sean"
        skills = "Java"
        "success" //reutrn 반영 안됨 
    }
    
    println(person) //Person(name=Sean, skills=Java)
    println("returnObj: $returnObj") // returnObj: Person(name=Sean, skills=Java)

    val returnObj2 = person.run(){
        name = "Dooly"
        skills = "C#"
        "success" //return 반영됨 
    }

    println(person) //Person(name=Dooly, skills=C#)
    println("returnObj2: $returnObj2") // returnObj2: success

}

👍 apply()와 run()의 차이점 정리

⇒ apply() : 리턴값 반영 x

⇒ run() : 리턴값 반영 o


with()

: 인자로 받는 객체를 이어지는 block의 receiver로 전달하며 결과값 반환

run() 함수와 기능이 거의 동일하다. run의 경우 receiver가 없지만, with()에서는 receiver로 전달 할 객체를 처리

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

세이프 콜은 지원하지 않기 때문에 let과 같이 사용

supportActionBar?.let {
	with(it) { //it = supportActionBar 
			setDisplayHomeAsUpEnabled(true)
			setHomeAsUpIndicator(R.drawable.ic_clear_white)
	}
}

let과 with 표현 병합 = run과 동일

supportActionBar?.run {
		setDisplayHomeAsUpEnabled(true)
		setHomeAsUpIndicator(R.drawable.ic_clear_white)
}
fun main() {
	data class Person(var name: String, var skills : String)
    var person = Person("kildong", "Kotlin")

    val result = with (person){
        name = "Sean"
        skills = "Java"
			//"Hello"
    }
    
    println(person) //Person(name=Sean, skills=Java)
    println("returnObj: $result") // result: kotlin.Unit -> 반환값이 없다면 Unit 반환, 반환값이 있다면(예제에선 Hello) Hello반환 
}

use()

: 객체를 사용한 후 close() 등을 자동적으로 호출해서 닫아줌

public inline fun <T : Closeable?, R> T.use(block: (T) -> R) : R 
or
public inline fun <T : AutoCloseable?, R> T.use(block: (T) -> R) : R

ex)파일 사용

private fun readFirstLine() : String {
	BufferedReader(Filereader("test.file")).use { return it.readLine() }
}

takeIf()와 takeUnless()의 활용

  • takeIf() : 람다식이 true이면 객체 T를 반환하고 그렇지 않은 경우 null 반환
  • takeUnless() : 람다식이 false이면 객체 T를 반환하고 그렇지 않은 경우 null반환
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? 
	= if (predicate(this)) this else null
val input = "Kotlin"
val keyword = "in"

input.indexOf(keyword).takeIf { it >= 0 } ?: error("keyword not found")
input.indexOf(keyword).takeUnless { it < 0 } ?: error("keyword not found")

참고

https://www.boostcourse.org/mo132/joinLectures/28611

Comments