[TIL] 2023. 12. 09

Gahyeon Lee·2023년 12월 9일
0

TIL

목록 보기
21/47
post-thumbnail

💃 지연 초기화

1. 지연 초기화란?

  • 변수나 상수의 값을 나중에 초기화할 수 있다.

  • 코틀린은 클래스를 설계할 때 안정성을 위해 반드시 변수의 값을 초기화할것을 권장한다. (클래스 만들 때 = ""나, = 0과 같이 공백이라도 넣어준 것이 바로 초기화했던 것!)

  • 클래스를 설계할 때 초기의 값을 정의하기 난처해서 나중에 대입하기 위한 문법이다. (클래스 만들 때 바로 초기화할 수 있는 경우는 드물다.)

  • 저사양으로 제한되어있는 환경에서는 메모리를 더욱 효율적으로 사용할 수 있다. (항상 처음부터 값이 들어있는 것 보다는 실제로 메모리를 효율적으로 관리하면서 필요할 때 값을 초기화해주는 게 더 좋다.)

  • 코틀린은 지연초기화 또는 늦은초기화를 위해 변수는 lateinit, 상수는 lazy 키워드를 활용한다.

2. 예시 코드

▶️ 변수의 지연초기화 : lateinit

  • 기본 사용
    • name변수 값을 초기에 정의하기 어렵기 때문에 lateinit을 사용하자.
    • 물론 ""와 같이 공백으로 처리할 수 있지만, 가독성 측면에서 좋지 않다.
fun main() {
    var s1 = Student()
    s1.name = "참새"
    s1.displayInfo()

    s1.age = 10
    s1.displayInfo()
}

class Student {
    lateinit var name: String    // var name: String = "" 으로 해도 되지만 가독성이 좋지 않다.
    var age: Int = 0

    fun displayInfo() {
        println("이름은: ${name}입니다.")
        println("나이는: ${age}입니다.")
    }
}

/* 결과
이름은: 참새입니다.
나이는: 0입니다.
이름은: 참새입니다.
나이는: 10입니다.
*/
  • 고급 사용
    • 변수를 사용하기 전에 초기화되었는지 확인해야 안정성을 높일 수 있다.
    • isInitialized를 활용해서 값이 초기화되었는지 확인할 수 있다.(결과 : true/false)
    • 사용할 때는 값이 아니라 참조 형태로 사용해야 하기 때문에 this:: 또는 ::를 붙인다.
fun main() {
    var s1 = Student()
    s1.name = "참새"
    s1.displayInfo()

    s1.age = 10
    s1.displayInfo()
}

class Student {
    lateinit var name: String
    var age: Int = 0

    fun displayInfo() {
        if (this::name.isInitialized) {      // name이 lateinit으로 잡혀있는데 사용하기 전에 정말 초기화 됐는지 확인하기 위해 isInitialized를 부르고 그게 true면(초기화됨) if를 실행하고, false(초기화 안 됨) else를 실행한다.(this::name으로 접근)
            println("이름은: ${name}입니다.")
            println("나이는: ${age}입니다.")
        } else {
            println("name변수를 초기화해주세요.")  // 이렇게 해야 더 안정성이 있게 된다. 만약 초기화가 안 됐는데 코드를 실행한다면, 예외가 발생하고, 예외 처리를 위한 try-catch로 묶어야 해서 코드가 길어지고 성능이 저하됨(isInitialized 한 줄로 훨씬 간결하게 작성)
        }
    }
}

/* 결과
이름은: 참새입니다.
나이는: 0입니다.
이름은: 참새입니다.
나이는: 10입니다.
*/

▶️ 상수의 지연초기화 : lazy

  • 상수를 사용하는 시점에 값을 대입하고 초기화가 수행된다.
  • 소스에서 변수가 최초로 이용되는 순간 중괄호로 묶은 부분이 자동으로 실행되어 그 결괏값이 변수의 초깃값으로 할당된다. lazy문의 중괄호 부분을 여러 줄로 작성한다면 마지막 줄의 실행 결과가 변수의 초깃값이 된다.
fun main() {
    var s1 = Student()
    s1.name = "참새"
    s1.displayInfo()

    s1.age = 10
    s1.displayInfo()
}

class Student {
    lateinit var name: String
    var age: Int = 0
    val address: String by lazy {    // 자료형이 String인 상수 adress를 lazy로 만들건데 초기화 되었을 떄 실행할 코드와 초기화 값(seoul)을 넣어 줌.
        println("address 초기화")
        "seoul"
    }

    fun displayInfo() {
        println("이름은: ${name} 입니다.")
        println("나이는: ${age} 입니다.")
        println("주소는: ${address} 입니다.")
    }
}

/* 결과
이름은: 참새 입니다.
나이는: 0 입니다.
address 초기화
주소는: seoul 입니다.
이름은: 참새 입니다.
나이는: 10 입니다.
주소는: seoul 입니다.
*/

💃 널 세이프티

1. 널 세이프티란?

  • 코틀린의 Null 안정성을 향상시켜줄 수 있다.
  • Null예외는 프로그램의 가용성을 저하시키는 치명적인 오류이다.(NullPointerExcrption이 발생하면 프로그램이 꺼져버린다.)
  • 코틀린은 Null예외로부터 안전한 설계를 위해 자료형에 Null 여부를 명시할 수 있다.(자료형이 Null을 가질 수 있다, 없다를 명시하기 때문에 java보다 더욱 안전하다.)

2. Kotlin의 널 세이프티 방법

  • Null 예외로부터 안전한 설계를 위한 다양한 키워드들을 지원한다.
  • 코틀린은 null을 가질 수 있게 하는 ?, null이 아님을 강제로 보장하는 !!, null이 아닐 때만 실행할 수 있고 null이라면 null이라는 문자열을 그대로 출력하는 safe call 연산자 ?., null일 때와 null이 아닐 때를 구분할 수 있고 null이라는 문자열 대신에 다른 문자열로 대체해 주는 엘비스 연산자 ?: 으로 Null예외로부터 살아남으려고 한다.
  • 하지만 강제로 null이 아니라고하는 !!는 최대한 사용을 지양하자!

3. 예시 코드

▶️ 변수를 null을 저장할 수 있다는 것은 String?으로 선언한다.

  • null을 저장하지 않고 설계하려면 lateinit var로 대체할 수 있다.
// 주소를 저장하는 address를 String?으로 선언해 보자.

fun main() {
    var s = Student()    // s라는 이름으로 인스턴스 만듦
    s.name = "참새"
    s.address = "서울"
    s.displayInfo()
}

class Student {
    lateinit var name: String
    var address: String? = null

    fun displayInfo() {
        println("이름은: ${name} 입니다")
        println("주소는: ${address} 입니다")    // address는 처음에 Null을 가졌지만, 서울이라는 값을 넣어줬음.
    }
}

/* 결과
이름은: 참새 입니다
주소는: 서울 입니다
*/

▶️ 메소드를 호출하고 전달받은 리턴값(변수, 상수 값)이 null이 아님을 !!키워드로 보증한다.

  • readLine()의 결과값을 !!로 Null이 아님을 보장해 보자.
fun main(){
//  var data = readLine()!!.toInt()   // 이 한 줄을 밑의 두 줄로 쪼개 볼 수 있다.(Null이 아니니 바로 toInt() 부름)

    var  inputData = readLine()!!     // readLine이라는 메소드를 호출하면 리턴되는 값이 반드시 Null이 아니라는 것을 !! 붙여서 보장하는 것
    var data = inputData.toInt()      // 위에서 inputData에 넣어준 값은 당연히 Null이 아니니까 Null 체크할 필요 없이 바로 .toInt() 실행.
    println("Null아닌 값: ${data}")
}

▶️ ?.키워드로 Null인지 확인하고 Null이 아닐 때만 참조하는 메소드를 실행하도록 작성해야 한다. (Null을 가진 애가 있지만 Null이 아닐 때만 실행하는 똑똑한 코드)

  • 주소를 저장하는 address는 초기값이 null이기때문에 null위협에 놓여있다.
  • Null이 아님을 보장해야하는데 강제로 !!를 사용하는것은 현상황에 바람직하지 않다.
  • ?.안전 호출연산자(safe-calls)라고 한다.
fun main(){
    var s = Student()
    s.name = "참새"
    s.displayAddressLength()

    s.address = "서울"
    s.displayInfo()
}

class Student {
    lateinit var name:String
    var address:String? = null    // address는 null을 저장할 수 있다.

    fun displayInfo() {
        println("이름은: ${name} 입니다")
        println("주소는: ${address} 입니다")
    }

    fun displayAddressLength() {
        println("주소의 길이는: ${address?.length} 입니다")    // 메소드는 null이면 호출이 안 된다. ?.을 붙이면 address가 null인지 확인해서 null이 아니면 메소드 실행하고, null 발생했을 때 프로그램이 꺼지지 않고 null이라고 호출한다.
    }                                                    
}

/* 결과
주소의 길이는: null 입니다
이름은: 참새 입니다
주소는: 서울 입니다
*/

▶️ ?. 키워드로 안전하게 실행했지만 null이 출력되는것을 막고싶을 때 ?:(엘비스 연산자) 키워드를 함께 사용해서 null 대신에 다른 문자열을 출력할 수 있다.

fun main(){
    var s = Student()
    s.name = "참새"
    s.displayAddressLength()

    s.address = "서울"
    s.displayInfo()
}

class Student {
    lateinit var name:String
    var address:String? = null

    fun displayInfo() {
        println("이름은: ${name} 입니다")
        println("주소는: ${address} 입니다")
    }

    fun displayAddressLength() {
        println("주소의 길이는: ${address?.length ?: "초기화하세요"} 입니다")    // null 대신 "초기화하세요"가 출력
    }
}

/* 결과
주소의 길이는: 초기화하세요 입니다
이름은: 참새 입니다
주소는: 서울 입니다
*/
profile
코린이 강아지 집사🐶

0개의 댓글