CODING/강의노트-부스트코스-코틀린

[부스트코스]📱코틀린 2-2 나를 괴롭히는 널(Null)!

codingTrip 2021. 1. 27. 21:00

안녕하세요? codingTrip입니다.

오늘은 "코틀린 프로그래밍  1/2(함수편)"

1단원 코틀린의 기본을 익혀요!

2장. 변수와 자료형, 연산자

2-2 나를 괴롭히는 널(Null)!

시작하겠습니다!

 

🔎 null을 허용한 변수 검사

▷ 코틀린의 변수 선언은 기본적으로 null을 허용하지 않는다.

- val a : Int = 30
- var b : String = "Hello"

null은 상당히 우리가 많이 만나요 ㅠㅠ
어떤 특정 변수를 선언한 다음에 그 해당되는 값을 할당해줘야 해요.
예를 들어 val 즉, 불변값 a를 선언하고, int 자료형에 값 30을 할당해보겠습니다.

 

만약, 선언만 하고서 값 할당을 생략했다고 한다면,
아무런 값이 들어가 있지 않는 상태가 되죠.
이것을 null이라고 합니다.
프로그래밍에서 null인 상태를 access(접근)하면 문제가 발생하게 됩니다.
이를 예방하기 위해 코틀린은 값을 할당하지 않으면 컴파일 단계에서 허용하지 않습니다.
기본적으로 null이 허용되지 않게 되는 것이죠.

 

null 가능한 선언 

- val a : Int? = null
- var b: String? = null

null 값을 할당 가능한 변수를 만들고 싶다면, 선언할 때 사용했던 타입 뒤?를 붙이면 됩니다.
헷갈리지 말아야 할 점은
Int형의 a와 Int?형의 a는 다르다는 것입니다.
또한 이렇게 null을 허용한 변수의 경우에는
널 가능성을 검사해 주는 것이 필요합니다.

 

▷ NPE(NullPointerException)

- 사용할 수 없는 null인 변수에 접근하면서 발생하는 예외

단순 출력은 상관 없으나 널인 상태에서 연산되는 멤버에 접근할 때
NPE는 꽤 악명이 높죠.
초창기에 밀리어달러 버그라고도 하는데요.

언제 어디서나 발생 가능합니다.
자바 스크립트 등... 거의 모든 언어null인 상태와 null이 아닌 상태를 고려하지 않기 때문
대부분의 변수가 널을 허용하고 있습니다.
하지만 코틀린은 기본적으로 null을 허용하지 않기 때문에 안전한 편입니다.

 

💻 코딩해 보세요!

▷ NullTest.kt

src폴더를 클릭하고 Alt+Insert를 눌러서 Package를 만듭니다.

package 이름은 chap02.section03라고 짓습니다.

section03폴더를 클릭하고 Alt+Insert를 눌러서 Kotlin File을 만듭니다.

파일 이름NullTest로 지어줍니다.

 

package chap02.section03

fun main() {
    var str1: String
    println(str1)
}

위와 같이 입력합니다.

아? 변수를 선언만 하고는 값을 할당하지 않았네요??

 

빨간줄 보이시죠?

코틀린은 아예 컴파일 단계에서 
Variable 'str1' must be initialized
str1은 반드시 초기화 해야 한다는 것을 보여주고 있습니다.

실행(설정) 단축키인 Alt+Shift+F10를 누르고 NullTest.kt를 선택합니다.

결과창에도 변수를 초기화 해야 한다고 써있네요.

 

package chap02.section03

fun main() {
    var str1: String
    str1 = null
    println(str1)
}

str1 변수에 null 값을 할당해보겠습니다.

 

이렇게 null로 값을 넣는 것도 허용하지 않습니다.
기본 선언은 널 불가능이므로 꼭 기억하세요!
널 가능한 형식은 [데이터형?]와 같이 선언합니다.

물음표를 붙여야 가능해요.


▷ String을 String?으로 변경해 본다.

package chap02.section03

fun main() {
    var str1: String?
    str1 = null
    println(str1)
}

보시는 것처럼 빨간 줄도 안쳐있고, 오류도 안나는 군요.
이렇게 물음표를 붙이면 null인 상태의 출력은 가능합니다.
하지만 만약 null인 값을 가지고 계산을 수행하려고 하게되면 오류를 맞이하게 되므로 주의해야 한답니다.

 

package chap02.section03

fun main() {
    var str1: String?
    str1 = null
    println("str1: $str1, length: ${str1.length}")
}

str1 변수의 길이를 구하는 length를 사용해보겠습니다.

 

str1.length에 빨간 줄이 있네요?? 
Only safe(?.) or non-null asserted(!!.) calls are allowed on a nullable receiver of type String?
String?형식에는 세이프콜(?.)이나 넌널 단정 기호(!!.)가 허용됩니다.

이게 도대체 무슨 뜻일까요?
이렇게 물음표가 붙은 변수는 null이 가능해서 널 가능성이 존재하기 때문에
str1에 곧바로 멤버 length를 접근할 수 없습니다.
str1null 가능성인 것을 염두에 두고
이를 검사하는 코드를 넣거나 세이프 콜을 넣어야 합니다.

 

package chap02.section03

fun main() {
    var str1: String?
    str1 = null
    println("str1: $str1, length: ${str1?.length}")
}

str1.length에 ?를 붙여서 세이프 콜을 넣어봅시다.

세이프콜 기호 ?를 꼭 알아두세요.
str1이 혹시라도 null이면 뒷 부분인 length을 실행하지 않습니다. 
그래서 결과값에도 length를 실행하지 않고 그대로 null로 출력합니다.

 

package chap02.section03

fun main() {
    var str1: String?
    str1 = "Hello"
    println("str1: $str1, length: ${str1?.length}")
}

length이 실행되려면 null이 아닌 값을 넣어줘야 합니다.
그러면 위에 보시는 것처럼 문자열 Hello의 길이를 계산 해줍니다.
?. 세이프콜은 null이면 NPE가 발생할 가능성이 있으므로
length를 실행하지 않고 바로 null을 보내주는 역할을 하는 것이죠.

넌널 단정 기호 !!.널이 아니라고 단정 짓기 때문에
컴파일러가 오류를 무시하게 합니다.

package chap02.section03

fun main() {
    var str1: String?
    str1 = "Hello"
    println("str1: $str1, length: ${str1!!.length}")
}

물론 이 때는 변수에 null이 아닌 값을 입력했기 때문에
문제가 발생하지 않습니다.

package chap02.section03

fun main() {
    var str1: String?
    str1 = null
    println("str1: $str1, length: ${str1!!.length}")
}

[출처] 부스트코스 코틀린 강의 화면

이런 경우에는 NPE이 발생합니다.
컴파일러에서 알 수 있는 수준은 친절하게 노란색으로 형광펜이 되어서 무엇이 문제인지 알려줍니다.

 

사실... 분명 똑같이 입력했는데요... 오류가 다르게 나오네요.

아무튼 오류가 납니다. ㅋㅋㅋ 저도 같이 배우는 학생이잖아요?

검색을 해도 잘 모르겠더라고요. ㅠㅠ

그래도 끈질기게 오류를 해결하고자 했답니다.

저도 이렇게 하고 나니 NPE 오류가 나왔네요. 

여러분은 결과가 어떻게 나오셨나요? 궁금하네요.

 

package chap02.section03

fun main() {
    var str1: String?
    str1 = "Hello"
    println("str1: $str1, length: ${str1!!.length}")
}

NPE가 발생할 수도 있기 때문에 넌널 단정 기호최대한 쓰지 않는 것이 좋습니다.

아직 판단문을 배우지 않았지만
length를 판단문을 통해 거를 수도 있답니다.

package chap02.section03

fun main() {
    var str1: String?
    str1 = null
    var len = if(str1 != null) str1.length else -1
    println("str1: $str1, length: $len")
}

==는 값을 비교해 동일하면 true를 
!=는 다르면 true를 반환합니다.
코틀린if 판단문을 한 줄에 구성가능합니다.

val len = if(str != null) str1.length else -1
이 부분이 노랗게 변했는데
빠른 수정 : Alt+Enter하면 코드의 힌트가 나옵니다.

Replace 'if' expression with elvis expression
엘비스(elvis) 표현식으로 바꾸기를 클릭해봅니다.

 

package chap02.section03

fun main() {
    var str1: String?
    str1 = "Hello"
    var len = str1?.length ?: -1
    println("str1: $str1, length: $len")
}

val len = str1?.length ?: -1
이렇게 식이 많이 단축되었죠?

 

package chap02.section03

fun main() {
    var str1: String?
    str1 = null
    var len = str1?.length ?: -1
    println("str1: $str1, length: $len")
}

만일 이 왼쪽 부분이 null이면
elvis연산자에 의해서 -1가 수행됩니다.
null이 아니면 length가 수행됩니다.

이렇게 다양한 방법으로 null인 상태를 처리할 수 있습니다.

 

📐 null 처리 방법

▷ Kotlin의 처리방법 비교

  - Kotlin에서는 기본적으로 NotNull이고 Nullable 표현에만 '?'가 사용된다.

[출처] 부스트코스 코틀린 강의자료

이 함수의 경우에도 함수의 인자에 자료형이 적용가능 합니다.

예제에서는 String형과 String?형으로 적용했습니다.

변수에 String?형을 넣기 때문에 값에 null을 넣을 수 있습니다.
size는 정수형으로 추론됩니다. ?로 선언하지 않았으므로
null을 값으로 허용하지 않습니다.

null이 아닌 경우에만 length를 가져오도록 합니다.
if 판단문은 중괄호를 없애고 한 줄에 써도 됩니다.


📸 세이프 콜과 non-null 단정 기호 활용

▷ 세이프 콜(Safe-call)

str1?.length

세이프 콜은 안전 호출 방법입니다.

▷ non-null 단정 기호

str1!!.length

null일리 없다고 단정했기 때문에
null이 발생하면 NPE가 발생하게 됩니다.
최대한 사용을 지양해야 합니다.

 

🧪 조건문을 활용해 null을 허용한 변수 검사하기

▷if 와 else의 활용

[출처] 부스트코스 코틀린 강의자료

if 판단문은 중괄호를 없애고 한 줄에 써도 됩니다.

혹시 if문이 생소하신 분들을 위해 간략하게 설명하겠습니다.

if(str != null)

만약 str1변수가 null이 아니라면

str1.length

str1의 길이를 구하고

else -1

아니라면(null인 경우에) -1를 반환합니다.

 

📚 세이프 콜과 엘비스 연산자를 활용해 null을 허용

▷ 더 안전하게 사용하는 방법

[출처] 부스트코스 코틀린 강의자료

이 부분은 간략하게 엘비스 연산자로 바꿀 수 있습니다.
네모로 그려서 표현하면 간단합니다.
null이면 -1
null이 아니면 length가 반환됩니다.

 

 

자, 이렇게 오늘도 마무리가 되었네요.

선생님께서 실습으로 하나씩 차근차근 설명해주신 점이 좋았습니다.
어떻게 코틀린이 컴파일 단계에서 null을 허용하지 않는 것인지
직접 눈으로 확인할 수 있어서 좋았어요.
이렇게.. NPE가 예방되는 거구나 ㅠㅠㅠ

코틀린은 나름의 많은 방법으로

어떻게든 NPE를  발생하지 않기 위해

 노력하는 언어인것 같아서 맘에 듭니다.
얼마나 많은 오류가 이 NPE에서 발생하는지 몰라요. ㅠㅠ
그리고, 코틀린은 if 판단문을 한줄에 구성 가능한 점도 신기했습니다.

중간중간에 자막으로 헷갈리는 부분을 부연 설명해주신 점도 좋았어요.
실습 후 마지막에 강의자료로 다시 정리해주시는 점이 전 가장 좋았어요.
거듭 말씀드렸지만 정말 실습에서의 장점과 이론 강의의 장점을
지루하지 않게 배분하신 것 같아서 좋았답니다.

 

역시 강의를 쓱 듣는 것보다 직접 코드를 입력하면서

공부하는 게 확실히 성장하는 것 같아요.

여러분도 실습하시면서 같이 진도 나가봐요!

 

그럼 다음 시간에 만나요.

 

 

부스트코스 서포터즈 3기 codingTrip