ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 코틀린 기초 문법 ②
    KOTLIN 2022. 9. 27. 19:54

     

     

    참고한 강의와 책은 이전 블로그 글에 올려두었다. 

     

    https://dodop-blog.tistory.com/391

     

    코틀린 기초 문법 ①

    요즘 코드가 Java -> Kotlin으로 넘어가고 있고, 사용이 많고 동기 분들을 보니 모두 코틀린 공부를 하고 계셔서 Kotlin 공부를 시작했다. 노션에 따로 적으면서 공부하긴 했지만, 블로그에도 함께 적

    dodop-blog.tistory.com

     

     

     

     

    컬렉션과 배열 

    array 와 list

    • arrayOf : 함수에 원소를 넘기면 배열 생성
    • arrayOfNulls : 원소타입이 nullable한 경우에 함수에 정수값을 인자로 넘기면 모든 원소가 null 이고 인자로 넘긴 값과 크기가 같은 배열을 만들 수 있음
    • Array 생성자 : 배열 크기와 람다를 인자로 받아서 람다를 호출해서 각 배열 원소를 초기화 (arrayOf를 쓰지 않고 각 원소가 널이 아닌 배열을 만들어야 하는 경우 사용)
    • forEachIndexed : 인덱스와 요소를 함께 순회
    val array =arrayOf(1, 2, 3, 4, 5)
    val list =listOf(1, 2, 3, 4, 5)
    
    val array2 =arrayOf("1", 2, 3.0f)
    val list2 =listOf("1", 2, 3.0f)
    
    array[0] = 3
    //    list[0] = 2 X 불가
    
    var result = list[0] // list.get(0)
    val arrayList =arrayListOf<Int>()
    arrayList.add(10)
    arrayList.add(20)
    
    // 크기를 정한 비어있는 함수를 정의 
    var nullArr = arrayOfNulls<Int>(5)
    
    // Array 람다 출력 
    val letters = Array(26) { i -> ('a' + i).toString() }
    println(letters.joinToString(""))
    // abcdefghijklmnopqrstuvwxyz
    
    // IntArray 람다 출력 
    val squares = IntArray(5) {i -> (i+1) * (i+1)}
    println(squares.joinToString())
    // 1, 4, 9, 16, 25
    
    // index와 요소를 함께 출력 
    squares.forEachIndexed { index, element ->
        println("Argument $index is: $element")
    }
    // Argument 0 is: 1
    // Argument 1 is: 4
    // Argument 2 is: 9
    // Argument 3 is: 16
    // Argument 4 is: 25
    

     

     

    읽기 전용과 변경 가능한 컬렉션

    • MutableCollection 인터페이스 : 컬렉션의 데이터를 변경 가능
    • Collection : 컬렉션을 변경하지 않고 읽기 전용으로 사용
    • 방어적 복사(defensive copy) : 어떤 컴포넌트의 내부 상태에 컬렉션이 포함될 때 그 컬렉션을 MutableCollection을 인자로 받는 함수에 전달하면서 원본의 변경을 막기 위해 사용
    • 읽기 전용 컬렉션이 항상 스레드 안전 하지는 않음
    • setOf, mapOf는 자바 표준 라이브러리에 속한 클래스의 인스턴스를 반환

    컬렉션 타입 읽기 전용 타입 변경 가능 타입 method

    컬렉션 타입  읽기 전용 타입  변경 가능 타입  method
    List listOf mutableListOf, arrayListOf add, remove
    Set setOf mutableSetOf, hashSetOf, linkedSetOf, sortedSetOf add, remove
    Map mapOf mutableMapOf, hashMapOf, linkedMapOf, sortedMapOf, mutableEntry (key, value) → key를 이용해 탐색 put, remove

     

     

    vararg 키워드

    • 파라미터 개수가 달라질 수 있는 함수를 정의
    • 스프레드 연산자(*) : 배열을 명시적으로 풀어 배열의 각 원소를 인자로 전달
    fun sum(vararg numbers: Int) = numbers.sum()
    sum(1, 2, 3, 4, 5)
    
    val numbers = intArrayOf(1, 2, 3, 4, 5)
    sum(*numbers)
    

     

     

     

    infix (중위)함수 호출

    • 인자가 하나뿐인 메서드를 간편하게 호출 가능
    • 단, class 안에 infix 함수 적용시 적용할 클래스 이름이 자기 자신이므로 이름은 쓰지 않음
    infix fun Int.multiply(x: Int): Int = this * x
    
    // 다음과 같은 식으로 함수를 호출할 수 있다 
    // 6 -> infix대상(자기 자신), 4 -> 파라미터 x 
    6 multiply 4 
    // == 6.multiply(4)
    

     

     

     

    구조 분해 선언 (destructuring declaration)

    • 복합적인 값을 분해해서 여러 변수에 나눠 담음
    • Pair, Map 등에서 사용 가능
    // 여기서는 컬렉션 원소와 인덱스 값을 따로 변수에 담음 -> 구조 분해 선언 
    for ((index, name) in students.withIndex()) {
          println("${index + 1}번째 학생은 ${name}입니다")
      }
    

     

     

     

    컬렉션의 Null 가능성

    • 변수 타입 뒤에 ?을 붙여 그 변수에 null을 저장할 수 있다는 뜻을 표시 가능
    • 컬렉션중에서 null 값을 제외시키기 위해서 filterNotNull의 함수를 사용 가능
    var arr = arrayListOf<Int?>()
    var str = listOf("1", "2", "3", "4", "o", "s")
    str.forEach { arr.add(it.toIntOrNull()) }
    print(arr)
    // [1, 2, 3, 4, null, null]
    println(arr.filterNotNull())
    // [1, 2, 3, 4]
    

     

     

     

     

     

     

     

     

    Null 다루기 

    ?. null safe operator

    • 널과 문자열을 인자로 받도록 허용
    • null 검사와 메서드 호출을 한번의 연산으로 수행
    • 호출하려는 값이 null이 아니라면 일반 함수처럼 작용하고 null 이라면 호출이 무시되고 null을 반환
    • scope 함수와 자주 사용됨
    • null 이 될 수 있는 값을 null이 될 수 없는 타입의 변수에 대입할 수 없음
    var name = "joyce"
    //    var nullName : String = null X null타입이면 안되는 곳에 null을 넣었다.
    var nameInUpperCase = name.toUpperCase()
    
    //    ? -> nullable
    var nullName: String? = null
    //    null이 아니면 toUpperCase null이면 전체가 null 반환
    var nullNameInUpperCase = nullName?.toUpperCase()
    

     

     

     

    ?: elvis operator

    • null 대신 사용할 디폴트 값을 지정할 때 사용
    //    Default 값을 주고 싶으면 ?:
    val lastName: String? = null
    val fullName = name + (lastName ?: " with no LastName")
    println(fullName)
    

     

     

     

    !!. : non-null assertion operator

    • 값이 null 이 아니어야 함을 의미
    • 어떤 값이든 null이 아닌 타입으로 바꾸어 컴파일 때는 검사하지 않고 null인 경우 런타임시에 Null Pointer Exception 발생하도록 설정
    • null 이 아닌 값을 받는 다는 사실이 분명하여 null 검사를 다시 수행하고 싶지 않을 때 사용
    fun ignoreNull(str: String?) {
    //    !!은 null 일리가 없다라는 표시를 의미
    //    만약 null이 들어오게 되면 NPE가 발생하게 된다.
    //    null 이 아님을 의미하기 때문에 : String이나 toUpperCase를 사용할 수 있다.
        val mNotNull: String = str!!
        val upperCase = mNotNull.toUpperCase()
    
        val email: String? = "sdfsdfs@gmail.com"
    
    //    let은 null이 아니면 수행하라는 의미
        email?.let{
    				println("Email is $email")
    		}
    }
    

     

     

     

    안전한 캐스트 as?

    • 어떤 값을 지정한 타입으로 캐스트
    • 대상 값을 as로 지정한 타입으로 바꿀 수 없으면 ClassCastException이 발생 하기 때문에 as변환 전에 is로 타입 체크를 할 수도 있음
    • 매번 타입 체크를 하기 보다 as?을 사용하여 지정한 타입으로 변환할 수 없는 경우 null을 반환하도록 할 수 있음
    class Book(val name: String) {
      override fun equals(o: Any?): Boolean {
        val otherBook = o as? Book ?: return false // 타입이 맞지 않으면 false 반환
        return otherBook.name == name // Book으로 안전한 캐스트 이후 otherBook이 Book타입으로 스마트 캐스팅 
    }
    
    val book1 = Book("One")
    val book2 = Book("Two")
    println(book1 == book2)
    // true
    
    println(book1 == 1) // 타입 불일치 
    // false
    

     

     

     

    let 함수

    • 안전한 호출 연산자와 함께 사용시 결과가 null 인지 검사한 후 결과를 변수에 넣는 작업을 간단한 식으로 한번에 처리
    • null 이 될 수 있는 값을 null 이 아닌 값만 인자로 받는 함수에 넘기는 경우 많이 사용
    • 자신의 수신 객체를 인자로 전달받은 람다에게 넘김
    • null 이 될 수 있는 값에 대해 안전한 호출 구문을 통해 let을 호출하되 null 이 될 수 없는 타입을 인자로 받는 람다를 let에게 전달 → null 이 될 수 있는 타입의 값을 null 이 될 수 없는 타입의 값으로 바꿔서 람다에 전달
    // let을 사용하지 않는 식 
    val person: Person ?= getTheBestPersonInTheWorld()
    if (person != null) sendEmailTo(person.email)
    
    // let을 사용한 식 
    getTheBestPersonInTheWorl()?.let { sendEmailTo(it.email) }
    

    참고) null 로 지정한 프로퍼티를 나중에 수정해주어 단순한 Null Pointer Exception이 발생할 가능성이 존재하는 것 보다 초기화를 미루어 (late-initialized) 초기화 이전 사용시 ‘lateinit property — has not been initialized’의 예외로 정보를 확실히 하는 것이 좋다.

     

     

     

    타입 파라미터의 Null 가능성

    • 코틀린에서는 함수나 클래스의 모든 타입 파라미터가 nullable
    • 타입 파라미터 T는 물음표가 없어도 nullable을 의미 → null 이 될 수 없는 파라미터에 T를 넘길 수 없음
    • 타입 파라미터가 null 이 아님을 확실히 하기 위해서는 타입 상한을 지정하여 null 이 아님을 명시

     

     

     

     

     

     

     

     

     

    가시성 변경자 
    • 클래스의 기반 타입 목록에 들어있는 타입 혹은 제네릭 클래스의 타입 파라미터에 들어있는 타입의 가시성은 그 클래스 자신의 가시성과 같거나 더 높아야 함
    • 메서드의 시그니처에 사용된 모든 타입의 가시성은 그 메서드의 가시성과 같거나 더 높아야 함
    • 단, 코틀린 코드의 internal은 자바에 딱 맞는 가시성이 없으므로 컴파일된 자바 바이트 코드 안에서 public으로 작동

    변경자 클래스 멤버 최상위 선언 package class

    변경자  클래스 멤버  최상위 선언  package class
    public (기본 가시성) 모든 곳에서 보기 O 모든 곳에서 보기 O 어떤 패키지에서도 O 클래스 외부 접근 O
    internal 같은 모듈안에서 보기 O 같은 모듈 안에서만 보기 O 같은 모듈 내의 패키지 O  
    protected 하위 클래스 안에서만 보기 O 적용 불가   상속받은 클래스 O
    private 같은 클래스 안에서만 보기 O 같은 파일 안에서만 보기 O 같은 파일 내 O 클래스 내부에서만 O

     

     

     

     

     

     

     

     

     

     

    동일성과 동등성 
    • 동등성 (===)
      • 원시타입의 경우 두 피연산자의 값이 같은지 비교
      • 참조 타입의 경우 두 피연산자의 주소가 같은지 비교
    • 동일성 (equals, ==)
      • 참조타입의 값이 같은지 비교
      • 모든 클래스가 상속받는 Any 클래스의 equals() 함수가 반환하는 Boolean 값 (우리가 클래스 만들 때에는 Any를 상속받아 내용의 동일성을 보장할 수 있다)
    • 코틀린에서는 == 가 두 객체를 비교하는 가장 기본적인 방법으로 내부에서 equals를 호출하여 객체 비교
    • 참조비교를 위해서는 === 연산자를 사용

     

     

     

     

     

     

     

     

     

    Object
    • 생성자 없이 객체를 직접 만들어내는 키워드
    • 클래스를 정의 하면서 동시에 인스턴스(객체)를 생성
    • 객체 선언, 동반 객체, 객체 식등의 특징으로 사용

     

    객체 선언 (싱글턴)

    • 싱글턴을 정의하는 방법 중 하나
    • 객체 선언 기능을 통해 싱글턴을 언어에서 기본 지원
    • 인스턴스와 달리 싱글턴 객체는 객체 선언문이 있는 위치에서 생성자 호출 없이 즉시 만들어지고 공용으로 사용
    object CarFactory {
        val cars = mutableListOf<Car>()
    
        fun makeCar(power: Int): Car {
            val car = Car(power)
            cars.add(car)
            return car
        }
    }
    
    data class Car(val power: Int)
    
    fun main() {
        val car1 = CarFactory.makeCar(10)
        val car2 = CarFactory.makeCar(200)
        for (car in CarFactory.cars) {
    				println(car.power)
    				// 10 200 
        }
    }
    

     

     

    동반 객체 (companion object)

    • 어떤 클래스와 관련있는 메서드와 팩토리 메서드를 담을 때 쓰임
    • 동반 객체 메서드에 접근 시에는 동반 객체가 포함된 클래스의 이름 사용
    • 코틀린 언어는 static 키워드를 지원하지 않음 (companion object와 유사)
    • 클래스 안에 정의된 객체 중 하나에 companion을 붙이면 클래스의 동반 객체로 만들 수 있음
    • 자신을 둘러싼 클래스의 모든 private 멤버에 접근 가능하므로 private 생성자를 호출하기 좋은 위치
    • 동반객체에 이름을 붙여 사용할 수 있음
    //  private constructor를 사용해서 외부에서 만들지 못하도록 한다.
    //  이때 companion object를 이용하여 외부에서 사용하지 못하는 private property, method 등을 읽어올 수 있도록 해준다.
    class Book private constructor(val id: Int, val name: String) {
        companion object BookFactory : IdProvider {
            private const val myBook = "animal farm"
            fun create(): Book = Book(getId(), myBook)
            override fun getId(): Int {
                return 555
            }
        }
    
    //    companion object {
    //        val myBook = "animal farm"
    //        fun create(): Book = Book(0, myBook)
    //    }
    }
    
    interface IdProvider {
        fun getId(): Int
    }
    
    fun main() {
        val book = Book.create()
        val bookId = Book.getId()
        println("${book.id} + ${book.name}")
    // 555 + animal farm
        println(bookId)
    // 555
    }
    
    • 동반객체가 인터페이스를 구현할 수도 있음
    interface JSONFactory<T> {
        fun fromJSON(jsonText: String): T
    }
    
    class Person(val name: String) {
        companion object : JSONFactory<Person> {
            override fun fromJSON(jsonText: String): Person {
                // ...
            }
        }
    }
    

     

     

    • 동반객체로 싱글톤 구현하기 ( 반복적 작업이 될 수 있기 때문에 object가 싱글톤을 구현함 )
    • 쓰레드 세이프한 구현을 하기 위해 synchronized를 사용한 것을 확인할 수 있음
    • 참고영상 → https://www.youtube.com/watch?v=Vuwa21Xy-dc
    class Manager private constructor() {
        companion object {
            private var instance: Manager? = null
            fun getInstance() = synchronized(this) {
                if (instance == null) {
                    instance = Manager()
                }
                instance
            }
        }
    }
    
    // 생성된 하나의 객체를 사용하는 것을 확인 가능 
    fun main() {
        println(Manager.getInstance())
    // Manager@506e6d5e
        println(Manager.getInstance())
    // Manager@506e6d5e
        println(Manager.getInstance())
    // Manager@506e6d5e
        println(Manager.getInstance())
    // Manager@506e6d5e
        println(Manager.getInstance())
    // Manager@506e6d5e
    }
    
    
    
    // operator invoke를 사용할 수도 있음 
    class Manager private constructor() {
        companion object {
            private var instance: Manager? = null
            operator fun invoke() = synchronized(this) {
                if (instance == null) {
                    instance = Manager()
                }
                instance
            }
        }
    }
    
    // 같은 객체를 반환 
    fun main() {
        println(Manager())
    // Manager@506e6d5e
        println(Manager.invoke())
    // Manager@506e6d5e
    }
    
    
    // object를 사용하면 다음과 같이 단순하게 싱글톤이 구현됨 
    object Manager {
        init {
            println("Manager Object Initialized")
        }
    }
    
    fun main() {
        println(Manager)
    // Manager Object Initialized
    // Manager@2acf57e3
        println(Manager)
    // Manager@2acf57e3
    }

     

     

     

     

     

    객체 식

    • 무명 내부 클래스(anonymous inner class)대신 쓰임
    • ex) EventListner / cf) 옵저버 패턴 → 이벤트 수신 클래스와 이벤트 발생 및 전달 클래스 사이에서 interface인 리스너(observer)가 존재
    // 익명객체를 사용하지 않은 예제
    interface EventListener {
        fun onEvent(count: Int)
    }
    
    class Counter(var listener: EventListener) {
        fun count() {
            for (i in 1..100) {
                if (i % 5 == 0) listener.onEvent(i)
            }
        }
    }
    
    class EventPrinter : EventListener {
        override fun onEvent(count: Int) {
    				print("${count}- ")
        }
    
        fun start(){
            var counter = Counter(this)
            counter.count()
        }
    }
    
    fun main() {
        EventPrinter().start()
    }
    
    // 5- 10- 15- 20- 25- 30- 35- 40- 45- 50- 55- 60- 65- 70- 75- 80- 85- 90- 95- 100-
    
    // 익명객체를 사용한 예제 
    interface EventListener {
        fun onEvent(count: Int)
    }
    
    class Counter(var listener: EventListener) {
        fun count() {
            for (i in 1..100) {
                if (i % 5 == 0) listener.onEvent(i)
            }
        }
    }
    
    class EventPrinter {
        fun start() {
            var counter = Counter(object :EventListener {
                    override fun onEvent(count: Int) {
                        print("${count}- ")
                    }
                })
            counter.count()
        }
    }
    
    fun main() {
        EventPrinter().start()
    }
    
    // 5- 10- 15- 20- 25- 30- 35- 40- 45- 50- 55- 60- 65- 70- 75- 80- 85- 90- 95- 100-
    

     

     

     

     

     

     

     

     

     

    람다 
    • val lambdaName : Type = {argumentList -> codeBody}의 구조
    • 항상 중괄호로 둘러쌓여 있으며 → 가 인자목록과 람다본문을 구분
    • 람다식을 변수에 저장할 수 있음
    • 람다식이 여러줄인 경우 마지막 수행문의 결과 값이 반환
    • 파라미터가 없는 경우 실행할 구문만 나열
    • 파라미터가 하나 뿐이면 it을 사용
    val square: (Int) -> (Int) = { num -> num * num }
    val square2 = { num: Int -> num * num }
    val nameAge = { name: String, age: Int -> "My name is $name. I'm $age." }
    
    fun main() {
        println(square(12))
    // 144
        println(nameAge("joyce", 29))
    // My name is joyce. I'm 29.
        println(pizzaIsGreat("joyce said "))
    // joyce said Pizza is the best!
        println(extendString("joyce", 28))
    // I'm joyce and 28 years old.
        println(calculateGrade(87))
    // perfect
        println(invokeLambda(lambda))
    // false
        println(invokeLambda { true })
    // true
        println(invokeLambda { it < 6.44 })
    // true
    }
    
    //  확장함수
    val pizzaIsGreat: String.() -> String = { this + "Pizza is the best!" }
    
    fun extendString(name: String, age: Int): String {
        val introduceMyself: String.(Int) -> String = { "I'm ${this} and ${it} years old." }
        return name.introduceMyself(age)
    }
    
    //  람다의 return
    val calculateGrade: (Int) -> String = {
        when (it) {
            in 0..40 -> "fail"
            in 41..70 -> "pass"
            in 71..100 -> "perfect"
            else -> "Invalid grade"
        }
    }
    
    //  람다를 표현하는 여러가지 방법
    fun invokeLambda(lambda: (Double) -> Boolean): Boolean {
        return lambda(5.2342)
    }
    
    val lambda: (Double) -> Boolean = { it < 2.55 }
    

     

     

    람다의 컬렉션 함수형 API

    -.first, .last, .firstOrNull, .lastOrNull

    • .first와 .last는 찾는 객체가 없으면 NoSuchElement Exception 발생
    • .firstOrNull, .lastOrNull의 경우 찾는 객체가 없으면 null을 반환
    val list = listOf(1, 2, 3, 4, 5, 6, 7)
    println(list.first { it > 5 })
    // 6
    println(list.last { it > 5 })
    // 7
    println(list.firstOrNull { it > 7 })
    // null
    println(list.lastOrNull { it > 7 })
    // null
    

    -.find, .findLast

    • .find : 조건을 만족하는 첫번째 원소를 반환, 조건을 만족하는 원소가 없으면 null을 반환
    • .findLast : 조건을 만족하는 마지막 원소를 반환
    val list = listOf(1, 2, 3, 4, 5, 6, 7)
    println(list.find { it > 5 })
    // 6
    println(list.findLast { it > 5 })
    // 7
    

    -.filter, map

    • .filter : 컬렌션을 이터레이션하면서 주어진 람다에 각 원소를 넘겨서 람다가 true를 반환하는 원소만 모아 컬렉션에서 원치않는 원소를 제거할 때 사용
    • .map : 주어진 람다를 컬렉션의 각 원소에 적용한 결과를 모아 새 컬렉션을 반환
    val list = listOf(1, 2, 3, 4, 5, 6, 7)
    println(list.filter { it > 3 })
    // [4, 5, 6, 7]
    
    println(list.map { it * it })
    // [1, 4, 9, 16, 25, 36, 49]
    

    -.forEach

    • 모든 원소에 대해 다음의 식을 호출
    val list = listOf(1, 2, 3, 4, 5, 6, 7)
    list.forEach(::print)
    // 1234567
    

    -.any, .all, .none, .count

    • 컬렉션의 원소가 어떤 조건을 만족하는지 판단
    • .any : 조건을 만족하는 원소가 하나라도 있는지 확인
    • .all : 모든 원소가 조건을 만족하는 지 확인
    • .none : 조건을 만족하는 원소가 하나도 없는지 확인
    • .count : 조건을 만족하는 원소의 갯수를 반환
    val list = listOf(1, 2, 3, 4, 5, 6, 7)
    println(list.count { it > 5 })
    // 2
    println(list.any { it > 5 })
    // true
    println(list.all { it > 5 })
    // false
    println(list.none { it < 0 })
    // true
    

    -.associatedBy, .groupBy, .partition

    • .associatedBy : 아이템에서 key를 추출하여 map으로 변환
    • .groupBy : 키를 통해 컬렉션의 원소를 구분하고 키 값에 따른 각 그룹이 값인 맵을 반환
    • .partition : 조건에 맞는지 여부에 따라 나눔
    data class Person(val name: String, val age: Int)
    val list = listOf(Person("a", 1), Person("b", 2), Person("c", 3))
    println(list.associateBy { it.name })
    // {a=Person(name=a, age=1), b=Person(name=b, age=2), c=Person(name=c, age=3)}
    println(list.groupBy { it.name })
    // {a=[Person(name=a, age=1)], b=[Person(name=b, age=2)], c=[Person(name=c, age=3)]}
    println(list.partition { it.age >= 2 })
    // ([Person(name=b, age=2), Person(name=c, age=3)], [Person(name=a, age=1)])
    

    -.flatMap, .flatten

    • .flatMap : 인자로 주어진 람다를 컬렉션의 모든 객체에 적용한 결과 얻어지는 여러 리스트를 한데 모아 반환
    • .flatten : 여러 리스트 (list of list)를 풀어서 하나의 리스트로 반환
    data class Person(val name: List<String>, val age: Int)
    val list = listOf(Person(listOf("a", "b"), 1), Person(listOf("b", "c"), 2), Person(listOf("c", "d"), 3))
    println(list.flatMap { it.name }.toList())
    // [a, b, b, c, c, d]
    println(list.flatMap { it.name }.toSet())
    // [a, b, c, d]
    val numbers = listOf(listOf(1, 2, 3), listOf(5, 6, 7), listOf(8, 9, 0))
    println(numbers.flatten())
    // [1, 2, 3, 5, 6, 7, 8, 9, 0]
    

     

     

     

    scope 함수

    • 속성, 함수를 더 깔끔하게 사용할 수 있음
    • 수신객체 지정 람다
    • apply, also → 처리가 끝나면 인스턴스 반환 (수신객체 자체)
    • run, let → 처리가 끝나면 최종값을 반환
    • apply, run → 참조연산자 없이 인스턴스의 변수 함수 사용
    • also, let → 같은 이름의 변수나 함수가 ‘scope’ 바깥에 중복된 경우를 방지하기 위해 파라미터로 인스턴스 넘긴것처럼 it을 통해 인스턴스 사용 가능
    • 예시 출처 → https://blog.yena.io/studynote/2020/04/15/Kotlin-Scope-Functions.html
     

    [Kotlin] 코틀린 let, with, run, apply, also 차이 비교 정리

    let, with, run, apply, also 코틀린에는 이렇게 생긴 확장함수들이 있다. 객체를 사용할 때 명령문들을 블럭{} 으로 묶어서 간결하게 사용할 수 있게 해주는 함수들이다. 문제는 서로 비슷비슷해서 헷

    blog.yena.io

    -apply()

    • 인스턴스 생성 후 변수 담기 전에 초기화 과정에 많이 사용
    • 항상 자신에게 전달된 객체를 반환 (수신객체)
    • 수신 객체 내부 프로퍼티를 변경 후 수신 객체 자체를 반환하기 위해 사용
    • run과 비슷하지만 블럭에서 return 값을 받지 않으며 자기 자신인 T를 반환
    fun <T> T.apply(block: T.() -> Unit): T
    
    data class Person(var name: String, var age: Int)
    val person = Person("", 0)
    val result = person.apply {
        name = "James"
        age = 56
    }
    
    println("$person")
    //Person(name=James, age=56)
    

    -also()

    • apply와 마찬가지로 T를 반환
    fun <T> T.also(block: (T) -> Unit): T
    
    val person = Person("", 0)
    val result = person.also {
        it.name = "James"
        it.age = 56
    }
    
    println("$person")
    //Person(name=James, age=56)
    

    -run()

    • 일반 람다함수처럼 인스턴스 대신 마지막 실행문의 결과값 반환
    fun <T, R> T.run(block: T.() -> R): R
    
    val person = Person("James", 56)
    val ageNextYear = person.run {
        ++age
    }
    
    println("$ageNextYear")
    // 57
    

    -with()

    • 자기 자신을 반환해야 하는 경우 this를 사용
    fun <T, R> with(receiver: T, block: T.() -> R): R
    
    val person = Person("James", 56)
    with(person) {
        println(name)
    		// James
        println(age)
    		// 56
    
    }
    

    -let()

    fun <T, R> T.let(block: (T) -> R): R
    
    val person = Person("", 0)
    val resultIt = person.let {
        it.name = "James"
        it.age = 56
        it // (T)->R 부분에서의 R에 해당하는 반환값.
    }
    
    val resultStr = person.let {
        it.name = "Steve"
        it.age = 59
        "{$name is $age}" // (T)->R 부분에서의 R에 해당하는 반환값.
    }
    
    val resultUnit = person.let {
        it.name = "Joe"
        it.age = 63
        // (T)->R 부분에서의 R에 해당하는 반환값 없음
    }
    
    println("$resultIt")
    // Person(name=James, age=56)
    println("$resultStr")
    // Steve is 59
    println("$resultUnit")
    // kotlin.Unit
    

     

     

     

     

     

    Function Context Object Return Value
    let it lambda result
    run this lambda result
    with this lambda result
    apply this context object
    also it context object

     

     

     

Designed by Tistory.