Dev/Kotlin

Kotlin (지연 초기화, lateinit, lateinit 특징, lazy, lazy 특징, 람다식, 람다 표현 다양한 방법, SAM 변환)

Walker_ 2024. 4. 24. 15:21
지연 초기화
코틀린은 지연 초기화를 사용하는데 이는 클래스의 코드에 Nullable 처리가 남용되는 것을 방지해 줌.

1. lateinit

개발을 하다 보면 클래스 안에서 변수(프로퍼티)만 Nullable로 미리 선언하고 초기화(생성자 호출)를 나중에 해야 할 경우가 있는데,
이럴 경우 lateinit 키워드를 사용할 수 있음.

1) Nullable로 선언하는 일반적인 방법
일바적인 선언 방식으로 처음에 null 값을 입력해두고, 클래스의 다른 메서드 영역에서 값을 입력함.

 

class Person {
    var name: String? = null
    init {
        name = "Jane"
    }
    fun process() {
        name?.plus("Messi")
        println("이름의 길이 = ${name?.length}")
        println("이름의 첫글자 = ${name?.substring(0,1)}")
    }
}

 

이 방식은 변수에 입력된 값의 메서드나 프로퍼티를 사용할 때 Safe Call(?.)이 남용되어 가독성을 떨어트리는 문제가 있음.

2) lateinit을 사용하는 방법
lateinit을 사용하면 Safe Call을 쓰지 않을 수 있기 때문에 코드에서 발생할 수 있는 수많은 ?를 방지할 수 있음.
 

 

class Person2 {
    lateinit var name: String
    init {
        name = "Jane"
    }
    fun process() {
        name.plus("Messi")
        println("이름의 길이 = ${name?.length}")
        println("이름의 첫글자 = ${name?.substring(0,1)}")
    }
}

 

lateinit의 특징은 다음 세 가지를 들 수 있음.
* var로 선언된 클래스의 프로퍼티에서만 사용할 수 있음
* null은 허용되지 않음
* 기본 자료형 Int, Long, Double, Float 등은 사용할 수 없음

lateinit을 사용할 때는 주의할 점이 있음.
lateinit은 변수를 미리 선언만 해놓은 방식이기 때문에 초기화되지 않은 상태에서 메서드나 프로퍼티를 참조하면 null 예외가 발생해서
앱이 종료.
따라서 변수가 초기화되지 않은 상황이 발생할 수 있다면 Nullable이나 빈 값으로 초기화하는 것이 좋음.

 

2. lazy
lazy는 읽기 전용 변수인 val을 사용하는 지연 초기화.
lateinit이 입력된 값을 변경할 수 있는 반면, lazy는 입력된 값을 변경할 수 없음. 그리고 사용법도 다름.
val로 변수를 먼저 선언한 후 코드의 뒤쪽에 by lazy 키워드를 사용하면 됨.
그리고 by lazy 다음에 나오는 중괄호{}에 초기화할 값을 써주면 됨.

 

class Company {
    // by lazy를 사용하면 변환되는 값의 타입을 추론할 수 있기 때문에 변수의 타입을 생략할 수 있음.
    val person: Person by lazy { Person() }
    init {
        // lazy는 선언 시에 초기화 하기 때문에 초기화 과정이 필요없음.
    }
    fun process() {
        println("person은 이름은 ${person.name}") // 최초 호출하는 시점에 초기화
    }
}

 

lazy의 특징
* 선언 시에 초기화 코드를 함께 작성하기 때문에, 따로 초기화 할 필요가 없음.
* lazy로 선언된 변수가 최초 호출되는 시점에 by lazy{} 안에 넣은 값으로 초기화.
    코드에서 Company 클래스가 초기화 되더라도 person에 바로 Person()으로 초기화 되지 않고,
    process() 메서드에서 person.name이 호출되는 순간 초기화.

lazy는 주의해서 사용. 지연 초기화는 말 그대로 최초 호출되는 시점에 초기화 작업이 일어나기 때문에
초기화하는데 사용하는 리소스가 너무 크면
(메모리를 많이 쓰거나 코드가 복잡한 경우) 전체 처리 속도에 나쁜 영향을 미칠 수 있음.

예를 들어 Company 클래스에서 처리하는 Person 클래스의 코드가 복잡하면 단순히 person.name을 호출하는 데 수 초가 걸릴 수 도 있음
따라서 복잡한 코드를 가지고 있는 클래스라면 미리 초기화 해 놓고 사용하는 것이 좋음

 

람다식
람다식 Lambda Expression 은 람다 표현식, 람다식이라고도 불림.
람다식을 한 줄로 표현하면 '람다식은 마치 값처럼 다룰 수 있는 익명함수다.'

일단 익명 함수는 이름 없이 정의되는 함수.

 

val sayHello = fun() {println("Hello World!")}
sayHello()

 

람다식을 값처럼 다룰 수 있다는 말은, 람다식 자체가 함수의 인수가 되거나 반환값이 될 수 있다는 의미.

1. 람다식 정의
람다를 이용하여, 인수 숫자의 제곱값을 반환.

 

val squareNum: (Int) -> Int = {number -> number * number}
println(squareNum(12))

 

squareNum : 람다식을 저장할 변수의 이름을 지정
(Int) : 람다식의 인수 자료형을 지정
Int : 람다식의 반환 자료형을 지정. 이 경우에는 정수를 넣고 정수를 반환.
number : 인수 목록을 나열. number의 자료형은 자료형에서 명시해주었으므로 형추론이 되어 number는 Int가 됨.
number * number : 람다식에서 실행할 코드를 지정
 */
/*
자료형은 인수목록에서 명시해주어도 됨
앞의 코드와 동일하게 작동하는 함수

 

val squareNum2 = {number:Int -> number * number}
println(squareNum2(12))

 

/*
또한 람다식의 인수가 한 개이면 인수를 생략하고 it으로 지정할 수 있음.
 */
 val squareNum3 : (Int) -> Int = {it * it}
println(squareNum3(12))

 

2. 람다를 표현하는 다양한 방법
람다는 '값처럼' 사용할 수 있는 익명 함수
값처럼 사용한다는 것은 함수의 인수로도 넣어줄 수 있다는 말.

 

fun invokeLambda(lambda: (Int) -> Boolean) :Boolean {// 람다식을 인수로 받음
    return lambda(5)
 }
// 이 함수는 다음과 같이 람다식을 인수로 넣어 사용할 수 있음
val paramLambda: (Int) -> Boolean = { num -> num == 10 }
println(invokeLambda(paramLambda)) // 람다식의 인수로 넣은 5 != 10 이므로 -> false

 

// 변수를 사용하지 않고도 바로 넣어줄 수도 있음.
// 다음의 람다식들은 모두 똑같이 작동
invokeLambda({ num -> num == 10})

val lambda: (Int) -> Boolean = {num -> num == 10}
println(invokeLambda(paramLambda)) // 람다식의 인수로 넣은 5 != 10 이므로 -> false

// 변수를 사용하지 않고도 바로 넣어줄 수도 있음
// 다음의 람다식들은 모두 똑같이 작동
invokeLambda({ num -> num == 10}) // 람다식 바로 넣어주기
invokeLambda({it == 10}) // 인수가 하나일 때 it으로 변경 가능
invokeLambda(){ it == 10 } // 만약 함수의 마지막 인수가 람다일 경우 밖으로 뺄 수 있음.
invokeLambda { it == 10 } // 그 외 인수가 없을 때 () 생략 가능

 

3. SAM(Single Abstract method) 변환

안드로이드를 개발하다 보면 당므과 같은 코드를 아주 많이 작성하게 됨.
button.setOnClickListener {
    // 버튼이 눌렀을 떄 작동할 코드
}
함수의 인수가 하나이고 람다식인 경우에 ()를 생략하고 {}에 코드를 작성할 수 있음.

OnClickListener는 다음과 같이 추상 메서드 하나가 있는 인터페이스
public interface OnClickListener {
    void onClick(View v);
}
    setOnClickListener는 이와 같이 람다식이 아님에도 마치 람다식처럼 취급되고 있음.
    이것이 가능한 이유가 바로 자바8에서 소개된 SAM 변환.
    SAM 변환는 두 가지 조건이 있음

    1. 코틀린 인터페이스가 아닌 자바 인터페이스 여야 할 것.
    2. 인터페이스 내에는 딱 한 개의 추상 메서드만 존재할 것.

    이 조건을 만족하는 경우 익명 인터페이스 객체 생성에 람다식을 사용할 수 있음.
    람다식을 사용하면 코드가 훨씬 간결해지고 가독성이 높아짐.

 

 


공부 과정을 정리한 것이라 내용이 부족할 수 있습니다.

부족한 내용은 추가 자료들로 보충해주시면 좋을 것 같습니다.

읽어주셔서 감사합니다 :)