널 안정성이란?

널(null)이란 객체가 선언되었지만 초기화되지 않은 상태를 의미합니다. 객체는 데이터가 저장된 주소를 참조하므로 흔히 참조 변수라고 합니다. 데이터가 메모리에 저장되면 어디에 저장됐는지 알아야 이용할 수 있는데 이때 해당 메모리 위치를 식별하는 것이 주소입니다. 객체에는 주소가 저장되며 이 주소로 메모리에 접근해서 데이터를 이용합니다. 그런데 객체가 주소를 가지지 못한 상태를 나타냅니다.

 

널인 상태의 객체를 이용하면 널 포인트 예외(NullPointException)가 발생합니다. 널인 상태의 객체를 이용할 수 없다는 에러입니다. 이때 널 안정성이란 널 포인트 예외가 발생하지 않도록 코드를 작성하는 것을 말합니다. 당연히 널 포인트 예외가 발생하지 않도록 작성해야 하지만 중요한 것은 이런 노력을 누가 하는가입니다.

 

// 널 안정성을 개발자가 고려한 코드

fun main() {
	var data: String? = null
    val length = if (data == null) {
    	0
    } else {
    	data.length
    }
    println("data length : $length")
}
// 실행 결과

data length : 0

 

위 코드에서 String 타입의 data 변수를 null로 선언했습니다. data 변수에 대입한 문자열의 개수를 얻고자 length를 이용했는데 data가 null인 상태에서 data.length 코드를 실행하면 널 포인트 예외가 발생합니다.

 

따라서 if 문으로 data가 null인지 확인한 뒤 nulll이면 0을 반환하고 null이 아니면 data.length를 반환하게 작성했습니다. 이렇게 하면 data가 null이어도 널 포인트 예외가 발생하지 않습니다. 그런데 이렇게 널 안정성을 개발자의 몫으로 남겨두면 객체가 null인지 일일이 점검하는 코드를 작성해야 합니다. 하지만 위 소스를 다음처럼 작성할 수도 있습니다.

 

// 코틀린이 제공하는 널 안정성 연산자를 이용한 코드

fun main() {
    var data: String? = null
    println("data length : ${data?.length ?: 0}")
}
// 실행 결과

data length : 0

 

data가 null이면 0을 반환하고 null이 아니면 length를 이용해 문자열의 개수를 얻습니다. 앞선 코드와 똑같이 동작하지만 코드에서 직접적으로 null을 점검하지는 않습니다.

 

 

널 안정성 연산자

널 허용 연산자 ?

코틀린에서는 변수 타입을 널 허용(nullable)과 널 불허(not null)로 구분합니다. 변수에 null을 대입할 수 있는지를 선언할 때 타입으로 구분합니다. 아래 코드에서 data1 변수는 String 타입으로 선언했습니다. 이렇게 하면 널 불허로 선언하므로 data1 변수에 null을 대입하면 오류가 발생합니다. 그런데 data2 변수는 String 타입으로 선언했지만 타입 뒤에 ? 연산자를 추가했으므로 널 허용 변수가 되어 nulll을 대입할 수 있습니다.

// 널 허용과 널 불허

var data1: String = "ohsopp"
data1 = null	// 오류

var data2: String? = "ohsopp"
data2 = null	// 성공

 

널 안전성 호출 연산자 ?.

널 불허로 선언한 변수에는 null을 대입할 수 없으므로 널 포인트 예외를 신경 쓸 필요는 없지만, 널 허용으로 선언한 변수는 null을 대입할 경우 얼마든지 널 포인트 예외가 발생할 수 있습니다. 따라서 변수가 널인 상황을 고려해 프로그램을 작성해야 합니다.

 

다음 코드는 오류가 발생합니다. data 변수에 null이 아닌 데이터를 저장했더라도 변수를 널 허용으로 선언했으므로 언제든지 null이 대입될 수 있습니다. 이처럼 null이 대입될 수 있는 변수를 널 안정성을 고려하지 않고 작성하면 오류가 발생합니다.

// 널 포인트 예외 오류

var data: String? = "ohsopp"
var length = data.length	// 오류

 

따라서 널 허용으로 선언한 변수의 멤버에 접근할 때는 반드시 ?. 연산자를 이용해야 합니다. ?. 연산자는 변수가 null이 아니면 멤버에 접근하지만 null이면 멤버에 접근하지 않고 null을 반환합니다. ?. 연산자 덕분에 널 허용 변수에 null이 대입되더라도 멤버에 접근하지 않고 null을 반환합니다. 따라서 널 포인트 예외는 발생하지 않습니다.

// 널 안정성 호출 연산자 사용

var data: String? = "ohsopp"
var length = data?.length	// 성공

 

엘비스 연산자 ?:

엘비스 연산자는 ?: 기호를 말합니다. 이 연산자는 변수가 널이면 널을 반환합니다. 그런데 어떤 경우에는 변수가 널일 때 대입해야 하는 값이나 실행해야 하는 구문이 있을 수도 있습니다. 이때 엘비스 연산자를 이용합니다.

// 엘비스 연산자 사용

fun main() {
    var data: String? = "ohsopp"
    println("data = $data : ${data?.length ?: -1}")
    
    data = null
    println("data = $data : ${data?.length ?: -1}")
}
// 실행 결과

data = ohsopp : 6
data = null : -1

 

예외 발생 연산자 !!

!!는 객체가 널일 때 예외를 일으키는 연산자입니다. 객체가 널일 때 ?. 또는 ?: 연산자를 이용해 널 포인트 예외가 발생하지 않게 작성할 수도 있지만, 또 어떤 경우에는 널 포인트 예외를 발생시켜야 할 때도 있습니다. 이때 !! 연산자를 이용합니다.

// 예외 발생 연산자

fun some(data: String?): Int {
    return data!!.length
}

fun main() {
    println(some("ohsopp"))
    println(some(null))
}
// 실행 결과

6
Exception in thread "main" java.lang.NullPointerException

 

위 소스는 some() 함수에 문자열을 전달하면 오류 없이 정상적으로 실행되고, null을 전달하면 data!!.length 코드로 예외 메시지를 출력하는 것을 보여줍니다.

'📌 Kotlin' 카테고리의 다른 글

[Kotlin] List / MutableList 추가, 삭제  (0) 2022.02.19

 

 

 

 

List와 MutableList

코틀린에서는 컬렉션(Collection)을 크게 두 가지로 구분하여 정의하는데, 바로 immutable(불변)과 mutable(가변)입니다. List는 불변 컬렉션이고 MutableList는 가변 컬렉션입니다.

 

사전적 의미처럼 immutable은 'read only'을 의미하지만, mutable은 'read & write'을 의미합니다. 따라서 List형은 리스트의 변화가 없어야 할 때 주로 사용하고, MutableList형은 원소의 추가/삭제 등 리스트의 변경이 필요한 경우에 사용합니다. 그래서 List에는 MutableList의 add()나 remove()/removeAt()과 같은 함수가 정의되어있지 않습니다.

 

 

 

활용 코드

fun main(){

    val list = listOf(1, 2, 3, 4, 5)
    // list.add(10)             // 오류. add() 함수 없음.
    println("\n====================[list]====================")
    println(list)

    
    val mList = mutableListOf(1, 2, 3, 4, 5)
    println("\n====================[mList]====================")
    println(mList)

    
    mList.addAll(listOf(10, 20, 30))
    mList.add(99)
    mList.add(3, -10)
    println("\n====================[mList]====================")
    println(mList)
    

    if (mList.remove(100)) {
        println("\n100이 삭제되었습니다.")
    } else {
        println("\n100이 존재하지 않습니다.")
    }
}

 

 

실행 결과

 

 

 

 

코틀린 컬렉션 내부에는 indices라는 프로퍼티가 정의되어 있는데, 이는 인덱스 범위를 나타냅니다. 이 indices 속성은 반복문을 통한 컬렉션 처리 시에 조금 더 편리하게 만들어줍니다. indices 속성은 immutable과 mutable에 구애받지 않습니다.

// indices 속성을 이용한 반복문

for (i in list.indices) {
    print("${list[i] + 100}\t")
}

// 출력 결과 : 101  102  103  104  105

'📌 Kotlin' 카테고리의 다른 글

[Kotlin] 널 안정성  (0) 2023.02.12

+ Recent posts