본문 바로가기
깡샘 코틀린

05-2 널 안정성

by 농농씨 2023. 6. 15.

티스토리 단축키 확인 ctrl + '/' 키

코드블록 단축키 alt+ctrl+','

 

널(Null) 안정성

널(Null)이란? 객체가 선언되었지만 초기화되지 않은 상태

객체는 데이터가 저장된 주소를 참조하므로 흔히 '참조 변수'라고 함. 데이터가 메모리에 저장되면 어디에 저장됐는지 알아야 이용할 수 있는데, 이때 해당 메모리 위치를 식별하는 것이 '주소'.

정리하면, 객체가 메모리에 담긴 데이터를 이용하기 위해, 객체에는 데이터가 직접 저장되는 것이 아닌 데이터가 저장된 메모리의 '주소'가 저장된다.  그 주소로 메모리에 접근해서 데이터를 이용한다. 그런데 객체가 주소를 가지지 못한 상태를 나타낸다.

 

다음 코드에서는 data1 변수에 "hello"라는 데이터를 저장했다. 하지만 실제로는 "hello"라는 문자열 데이터가 저장된 '주소'가 대입되며, 그 주소로 문자열 데이터를 이용한다. 

data2 변수에는 null을 대입했다. 이렇게 하면 주솟값을 가지지 못한다. 즉, 변수가 선언되었지만 이용할 수 없는 상태이다.

// 객체에 널 대입
val data1: String = "hello"
val data2: String? = null // ?는 null도 들어갈 수 있다는 뜻으로 붙임

이처럼 널인 상태의 객체를 이용하면 널 포인트 예외(NullPointException)가 발생한다. 널 포인트 예외는 널인 상태의 객체를 이용할 수 없다는 오류이다.

이때 널 안정성이란 널 포인트 예외가 발생하지 않도록 코드를 작성하는 것을 말한다. 널 포인트 예외는 오류이므로 당연히 발생하지 않게 작성해야 하지만 중요한 건 이런 노력을 누가 하는가이다.

// 널 안정성을 개발자가 고려한 코드 예시
fun main() {
	var data: String? = null // ?를 붙여서 변수가 null을 포함할 수 있음
	val length = if (data == null) { // if문을 이용해서 데이터가 널이라면 
 		0 //을 반환하고 
 	} else { // 아니라면
    	data.length // 데이터의 길이를 반환하고 이를 변수에 저장해라(자동 Int타입의 변수가 되는건가?)
    }
    pritnln("data length : $length")
}

위 소스를 보면 data가 null일 때 data.length 코드를 실행하면 널포인트예외가 발생한다. 그런데 이런 노력을 개발자가 이렇게 일일이 하지 않을 수 있도록 코틀린이 널안정성을 위한 도구를 제공한다.

// 코틀린이 제공하는 널 안정성 연산자를 이용한 코드
fun main() {
	var data: String? = null
    println("data length : ${data?.length ?: 0}") // null에 .length를 어케쓰지... 그래서 leng?th인건가.
    					//	물음표를 이중으로 붙여야하면 이건 개발자의 몫인가
    // data가 null이면 0을 반환하고, null이 아니면 length를 이용해 문자열의 개수를 얻는다.
}
실행결과:
data length : 0

 위의 코드는 앞선 코드와 똑같이 동작하지만 코드에서 null을 점검하지는 않는다. 즉, null점검 코드를 작성하지 않았는데도 널 안정성을 확보했다.

이처럼 프로그래밍 언어가 널 안정성을 지원한다는 것은 객체가 널인 상황에서 널 포인트 예외가 발생하지 않도록 연산자를 비롯해 여러 기법을 제공한다는 의미이다.

 

 

널 안정성 연산자

널 허용 - ? 연산자

복습) 코틀린에서는 변수 타입을 널 허용(nullable)과 널 불허용(not null)로 구분한다. 즉, 변수에 null을 대입할 수 있는지를 '선언할 때 타입으로 구분'한다.

// 널 허용과 널 불허
var data1: String = "kkang"
data1 = null // 오류!(널 불허라서)

var data2: String? = "kkang" // ?연산자 활용하여 변수를 널허용으로 선언
data2 = null // 성공!

 

널 안정성 호출 - ?. 연산자

널 허용이 아닌 널 불허인 변수는 여전히 널 포인트 예외가 발생할 것을 신경써야 한다.

// 널 포인트 예외 오류
var data: String = "kkang"
var length = data.length // 오류!

이처럼 null이 대입될 수 있는 변수를 널 안정성을 고려하지 않고 작성하면 오류가 발생한다.

따라서 널 허용으로 선언한 변수의 멤버에 접근할 때는 반드시 ?.연산자를 사용해야 한다. 

?. 연산자는 변수가 null이 아니면 멤버에 접근하지만 null이면 멤버에 접근하지 않고 null을 반환한다.

// 널 안정성 호출 연산자 사용
var data: String? = "kkang"
var length = data?.length

?. 연산자 덕분에 널 허용 변수에 null이 대입되더라도 멤버에 접근하지 않고 null을 반환하므로, 널포인트예외는 발생하지 않습니다.

 

엘비스 - ?:연산자

엘비스 연산자 ➡️ ?: 기호

이 연산자는 변수가 널이면 널을 반환한다.

그런데 어떤 경우에는 변수가 널일 때, 대입해야 하는 값이나 실행해야 하는 구문이 있을 수 있다. 이때 엘비스 연산자를 사용한다.

// 엘비스 연산자 사용
fun main() {
	var data: String? = "kkang"
    println("data = $data : ${data?.length ?: -1}")
    					// ?: 좌측의 변수가 null이면 우측의 값을 반환하라
    data = null
    println("data = $data : ${data?.length ?: -1}")
}
실행결과:
data = kkang : 5
data = null : -1

 

예외 발생 - !! 연산자

!! ➡️ 객체가 널일 때 예외(exception)를 일으키는 연산자

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

// 예외 발생 연산자
fun some(data: String?): Int {
	return data!!.length
}
fun main() {
	println(some("kkang")) // 함수에 문자열 전달하면 오류없이 출력
    println(some(null)) // 함수에 null 전달하면 예외메시지 출력함
}
실행결과:
5
Exception in thread "main" java.lang.NullPointerException

 

'깡샘 코틀린' 카테고리의 다른 글

06-2 뷰 클래스  (0) 2023.06.15
06-1 화면을 구성하는 방법  (2) 2023.06.15
05-1 람다 함수와 고차 함수  (0) 2023.06.15
04-3 코틀린의 클래스 종류  (0) 2023.06.14
04-2 클래스를 재사용하는 상속  (0) 2023.06.14