Null이란?
- var이나 val 변수의 값이 없다는 것을 나타내는 값이다.
- Java를 포함해 많은 언어에서 null은 치명적 에러를 유발하는 원인이 된다.
- Kotlin에서 null 값을 가지려면 null타입을 선언해야한다.
Kotlin에서의 Null을 선언?
- Kotlin에서 아래와 같은 구문을 작성하면 3번 line에서 컴파일 에러가 발생한다.
- String은 null 불가능(non-nullable) 타입 이기 때문이다.
fun main(args: Array<String>) {
var name: String = "Choi"
name = null
}
명시적 null 타입
- (타입)?은 해당 타입의 null 가능(nullable)을 뜻합니다.
- Kotlin에서의 null 선언은 아래와 같다.
fun main(args: Array<String>) {
var name:String? = "Choi"
name = null
pirntln(name)
}
에러 검출 시점(컴파일 vs 런타임)
- 컴파일 언어 특성상 컴파일러가 언어의 요구사항에 맞게 컴파일 에러를 발생시키는 지를 확인 후 처리한다.
- 컴파일 시점 에러는 프로그램이 실행 전 에러를 발견할 수 있어 큰 장점중 하나다
- 이와 달리 런타임 에러는 컴파일러가 발견할 수 없어 프로그램 실행 중 발생하는 오류이며
어느 시점에 발생하는지 확인이 에러 발생전 까지는 모른다
null 안전 처리
- 가장 안전한 방법은 null 불가능 타입을 사용하는 것이지만 그렇지 않은 경우를 대비해
3가지 방법이 있습니다.
첫번째 방법: 안전 호출 연산자
- 아래 코드를 실행하면 컴파일 도중 에러가 난다.
readLine()은 null을 허용하지만 capitalize()이 null을 허용하지 않기 때문이다
fun main(args: Array<String>) {
var beverage = readLine().capitalize()
println(beverage)
}
- 이런 경우 안전 호출 연산자인 '?'를 사용한다.
fun main(args: Array<String>) {
var beverage = readLine()?.capitalize()
println(beverage)
}
- 이렇게 하면 컴파일 런타임 에러 모두 생기지 않는다.
- 컴파일러가 안전 호출 연산자를 발견하면 null 값을 검사하는 코드를 자동으로 추가해 준다
따라서 런타임 시 readLine 함수의 반환 결과가 null이 아니면 capitalize 함수가 호출되어
첫 자가 대문자로 변환된 반환 문자열이 지정된다.
- 이처럼 안전 호출 연산자를 사용하면 함수 호출에 사용되는 변수나 다른함수의 반환값이 null이 아닐때만
다음 함수가 안전하게 호출되므로 NullPointerException을 방지할 수 있다.
안전 호출 연산자와 함께 let 함수 사용하기
- 안전 호출 연산자를 사용했을때 null에 따라 결과를 처리하고싶을때 사용하면 좋다
- 아래 코드는 readLine()한 결과가 null인 경우 "bear"를 null이 아닌 경우 it.capitalize()를 실행하는 코드이다.
fun main(args: Array<String>) {
var beverage = readLine()?.let {
if(it.isNotBlank()){
it.capitalize()
}else{
"bear"
}
}
println(beverage)
}
두 번째 방법 : non-null 단언 연산자
- non-null 단언 연산자는 '!!'으로 표시한다.
- 이 연산자는 null이 될 수 없다는 것을 단언하는 연산자다.
- 따라서 왼쪽 피연산자 값이 null이 아니면 정상적으로 코드를 수행하고 ,
null이면 런타임 시에 NullPointerException 예외를 발생시킨다.
- 단언 연산자는 컴파일러가 null 발생을 미리 알 수 없는 상황이 생길 수 있을 때 사용된다.
- 아래 코드를 해석하면 readLine의 반환값이 무엇이든 capitalize를 실행하라는 뜻이다.
단 반환값이 null이면 KotlinNullPointerException 예외가 발생된다.
fun main(args: Array<String>) {
var beverage = readLine()!!.capitalize()
println(beverage)
}
세 번째 방법 : 값이 null인지 if로 검사하기
- null 값을 if문으로 검사하여 처리하는 것이다.
null 복합 연산자 (null coalescing operator)
- null 복합 연산자는 '?:' 으로 표시한다.
- 엘비스 연산자라고도 불린다
- null 복합 연산자는 검사값이 null일 때 null이 아닌 기본값을 제공하여 결괏값이 null이 되지 않도록 하는
연산자라고 생각할수 있다.
[null 복합 연산자 예제]
fun main(args: Array<String>) {
var beverage = readLine()
beverage = null
val beverageServed:String = beverage?:"맥주" //beverage가 null이면 "맥주" ,null이 아니면 beverage를 반환
println(beverageServed)
}
[null 복합 연산자 예제 - let과 함께 사용]
fun main(args: Array<String>) {
var beverage = readLine()?.let{ //readLine()의 결과가 null인경우 let은 실행하지, 않고 "맥주"를 반환하고,
it.capitalize() //readLine()의 결과가 null이 아니면 let을 실행하여, 입력받은 문자열의 첫글자를 대문자로 변경해 반환한다.
}?:"맥주"
println(beverage)
}
예외(Exception)
- 대표적으로 KotlinNullPointerException이 있다.
- 사용자의 예상외의 값 입력, 개발자의 잘못된 코딩으로 인해 발생한다
- 일반 예외 : 컴파일 과정에서 체크되어 발생되는 에러
- 실행 예외 : 런타임 과정에서 발생되는 에러
- 미처 처리하지 못한 예외를 미처리 예외(unhandled exception),
프로그램 실행이 중단되는 것을 크래시(crash)라고 한다.
예외 던지기 (Exception Throw)
- 예외를 발생시키는 것을 예외를 던진다(Throw)고 표현한다.
- 예외를 던지는 이유는 코드가 잘못 작성되었으면 처리해야하는 문제임을 알려주기 위함이다
- 흔히 발생하는 예외는 IllegalStateException이 있다.
- 아래 코드는 count가 null일 경우 IllegalStateException를 던집니다
fun main(args: Array<String>) {
var count:Int? = null
val isThree = (1..3).shuffled().last() == 3
if(isThree){
count = 2
}
nullCheck(count)
println(count)
}
fun nullCheck(count:Int?){
count?: throw IllegalStateException("count is null!!")
}
- 이와 같이 예외를 던지면 어느 시점에 예외가 발생하였는지 어떤 에러인지 정확히 알 수 있다.
커스텀 예외(Custom Exception)
- 기존에 정의되어있던 KotlinNullpointException, IllegalstateException등이 아닌 직접 작성한 예외이다
- 아래 코드에 ChoiException이라는 커스텀 예외를 작성하였다.
fun main(args: Array<String>) {
val name:String = "Kim"
checkNameisChoi(name)
println("He name is $name.")
}
fun checkNameisChoi(name:String) {
if(name != "Choi") {
throw ChoiException()
}
}
//IllegalStateException 상속 및 재정의
class ChoiException(): IllegalStateException("이 분은 Choi가 아닙니다")
예외 처리 (Try/Catch/Finally)
- Try 블록에선 실행할 코드를 Catch 블록에선 실행도중 예외발생시 처리할 코드를 작성한다.
- Try 블록은 항상 실행하지만 Catch 블록은 예외 발생시만 실행한다.
- finally 블록은 예외가 발생해도 가장 마지막에 실행됩니다.
예외가 발생해도 처리해야할 일이 있다면 finally에 작성하는것이 좋습니다.
import java.lang.Exception
fun main(args: Array<String>) {
val count:Int? = null
try{
count!!.plus(1) //null + 1 을 하여 에러가 발생한다.
}catch (e: Exception){
e.printStackTrace() //에러 메시지를 출력한다.
}finally {
println("예외가 발생하던 안하던 전 실행됩니다.")
}
println("He name is $count.")
}
전제 조건 함수 (Precondition Function)
- Kotiln 표준 라이브러리의 일부로 편의 함수에 속한다.
- 이 함수들을 사용하면 커스텀 메시지와 함께 예외를 던질 수 있다.
- 아래 코드에 checkNotNull()이 전제 조건 함수이다.
import java.lang.Exception
fun main(args: Array<String>) {
val count: Int? = null
try {
checkNotNull(count,{ "해당 변수는 null 입니다." })
} catch (e: Exception) { //count가 null인경우 예외를 발생시키고 메세지를 실행합니다.
e.printStackTrace()
} finally {
println("예외가 발생하던 안하던 전 실행됩니다.")
}
println("He name is $count.")
}
[전제 조건 함수 종류]
함수 | 설명 |
checkNotNull | 첫번째 인자값이 null이면 IllegalStateException을 던지며, 그렇지 않으면 첫번째 인자 값을 반환한다. |
require | 첫번째 인자값이 false면 IllegalArgumentException을 던진다. |
requireNotNull | 첫번째 인자값이 null이면 IllegalArgumentException을 던지며, 그렇지 않으면 첫번째 인자값을 반환한다. |
error | 첫 번째 인자값이 null이면 제공된 메시지와 함께 IllegalStateException을 던지며, 그렇지 않으면 첫번째 인자값을 반환한다. |
assert | 인자값이 false이면 AssertionError를 던진다. 그리고 컴파일러의 assertion 플래그가 활성화 된다. |
null에 관하여
- 실제로 null은 다른언어에서 폭넓게 사용되고 있다, 아직 지정되지 않은 변수의 초깃값으로 말이다
예를 들어 userName이라는 사용자 이름을 입력하는 변수가 있다면
사용자가 입력하지 않으면 그의 이름을 null로 지정해놓는 방식으로 말이다.
- 이처럼 null이 기본값으로 지정되는 방식으로 인해 다른 언어에서는 종종 NullPointerException이 발생될 수 있다.
이것이 Kotlin이 null의 처리를 중요시하는 이유이다
checked 예외와 unchecked예외
- Kotiln에서는 모든 예외가 unchecked 예외이다, 즉 예외가 생길 수 있는 모든 코드를 우리가
try/catch 문으로 반드시 처리하도록 컴파일러가 강요하지 않는다는 뜻이다.
- 예를 들어 자바는 checked와 unchecked 예외 타입이 구분되어 있다.
checked 예외의 경우 try/catch문으로 처리하는지 컴파일러가 확인하고, 만일 처리하지 않으면 컴파일 에러를
발생시킨다. 이것은 프로그래머가 예외 처리를 정확하게 하도록 한 것이다.
- 하지만 대부분의 프로그래머들은 catch문에 에러를 처리하기 보단 e.printStackTrace()만 기재하여 에러를 넘겨
프로그램을 정상 실행하도록 하여 에러를 무시하므로, 추후에 더 큰 문제로 발전 할 수 있다.
- 위 이유들로 인해 checked예외는 문제 해결보다 더 많은 문제를 야기하므로 Kotlin은 unchecked 예외를 지원하고잇다.
[Kotlin] 함수(Function) (0) | 2020.12.04 |
---|---|
[Kotlin] 조건문과 조건식 (If, When, In) (0) | 2020.09.24 |
[Kotlin] 객체 타입 체크(is) (0) | 2020.09.24 |
[Kotlin] 반복문 예제 (For, While, do-While) (0) | 2020.09.24 |
[Kotlin] 생성자(Constructor) (0) | 2020.09.24 |