본문 바로가기

프로그래밍 언어 기초/KOTLIN

[Kotlin] Null-Safety와 형 변환(Type Cast), 타입 체크(Type Check), 예외(Exception) 처리

개요

오늘은 코틀린의 Null-Safety와 타입 체크(Type Check), 예외 처리(Exception)에 대해서 알아보도록 하겠습니다.


null이란?

프로그래밍에서 "null"은 값이 존재하지 않음을 나타내는 특별한 상태를 가리키는 데 사용됩니다.

이는 일반적으로 변수나 참조가 아직 초기화되지 않았거나, 값이 할당되지 않았을 때 발생합니다.

null 값은 다음과 같은 상황에서 사용될 수 있습니다.

① 초기화되지 않은 변수

변수가 선언되었지만, 아직 값을 가지고 있지 않을 때, 이 변수는 일반적으로 null로 설정됩니다.

② 객체의 빈 참조

객체를 가리키는 참조가 없을 때 해당 참조는 null로 설정됩니다.

③ 메모리 할당 실패

동적으로 메모리를 할당하려고 시도했지만 실패한 경우, 변수는 null을 가질 수 있습니다.

④ 함수나 메서드의 반환 값으로 사용

특정 조건에 맞는 값을 반환할 수 없는 경우 함수나 메서드는 종종 null을 반환합니다.

이처럼 특수한 상황에서 null은 프로그램이 처리하기 위한 특수한 값으로 다뤄지게 됩니다.

하지만, 대부분의 프로그래밍 언어에서 null 값은 그 자체로는 문제가 없을 수 있지만,

 

변수 등에 저장되었던 값이 null로 바뀐 후 이에 접근하거나 활용하려고 하면 예외(Exception)가 발생합니다.

그리고 이 null 값을 참조해서 발생한 예외를 NullPointerException라고 합니다.

null은 일반적인 상황에서 어떤 프로그램이 계산하거나 다뤄야 할 데이터에 해당하지 않습니다.

그러므로, null을 사용해서 어떤 계산을 수행하거나 작업을 처리하려고 한다면 할 수 없으므로

예외를 발생시켜 프로그램의 비정상적인 동작을 강제로 멈춰서 시스템을 보호하게 됩니다.


코틀린의 null 허용 변수

지금까지 다뤘던 변수는 대부분 특정한 자료형(Type)을 갖는 데이터를 담았습니다.

그리고 기본적으로 코틀린에서 변수는 별도로 명시하지 아니하면 변수에 null을 허용하지 않습니다.

아래의 코드를 한 번 살펴보도록 하겠습니다.

var number:Int = 0;
var nullableNumber:Int? = 0;

number = 1;
// number = null; //  error: null can not be a value of a non-null type Int
println(number + 1);

nullableNumber = 1;
println(nullableNumber); // 1
nullableNumber = null;
println(nullableNumber); // null
// nullableNumber += 1; // error: type mismatch: inferred type is String but Int? was expected
println(nullableNumber + 1); // null1


 

코틀린에서 변수는 별도로 명시하지 않으면, null을 허용하지 않습니다.

따라서, 변수 number에는 null을 할당할 수 없고 null 값을 넣으려고 하면 오류를 발생시킵니다.

 

하지만, 변수에 null 값을 허용해야 한다면 자료형 뒤에 ? 를 붙여서 null을 허용할 수 있습니다.

코틀린에서 자료형 뒤에 붙는 ? 기호를 Nullable Operator 혹은 Nullability라고 부릅니다.

또한, 위의 코드처럼 null 허용 변수는 앞선 포스팅에서 살펴보았던 사칙 연산 등의 작업이 정상적으로 이루어지지 않습니다.

 

이처럼 null 참조로 인해 프로그램이 비정상적으로 동작하거나 충돌이 발생하는 이상 현상을 Billion Dollar Mistake라고 부릅니다.

이는 null 값을 발명한 컴퓨터 과학자 Tony Hoare에 의해 언급된 문제 현상이고,

코틀린에는 null 값으로 인한 위험으로부터 안전하게 하기 위한 노력이 적용되었습니다.

 

 

https://en.wikipedia.org/wiki/Null_pointer#History

 

Null pointer - Wikipedia

From Wikipedia, the free encyclopedia Value indicating that a referenced dataset is invalid or doesn't exist In computing, a null pointer or null reference is a value saved for indicating that the pointer or reference does not refer to a valid object. Prog

en.wikipedia.org

 

 


null 값 체크

프로그래밍 과정에서 불가피하게 변수에 null이 오는 것을 허용해야 하는 때도 있을 수 있습니다.

위에서 언급한 특수한 상황에서는 불가피하게 변수 등에 null이 오는 것을 허용해야 할 수도 있는데,

이 경우에 개발자는 반드시 null 값에 대한 체크나 처리를 해두어야 합니다.

왜냐하면, 프로그램 대부분은 실행 중에 null이 올 수 있는 값에 대한 참조가 일어날 때,

예기치 못한 문제가 발생할 수 있기 때문입니다.

이는 개발자가 프로그래밍 시 신경 써야 할 부분 중 하나이며, 코틀린에서는 아래와 같이 null 타입을 점검할 수 있습니다.

var unKnownValue:Int? = null;
unKnownValue = 123;

if(unKnownValue == null){
    println("number is unKnown...!");

}else {
    println("number is $unKnownValue")
}

// result : number is 123

 


또한, 코틀린에서는 null을 허용한 변수에 대해 안전하고 편리하게 접근할 수 있도록 ? 연산자를 제공합니다.

val unInitList:MutableList<Int>? = null;

// val listLength = unInitList.size; // Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type MutableList<Int>?
val listLength = unInitList?.size;
println(listLength); // null

 

 

위 코드는 Kotlin의 Safe calls(?.)의 예시를 나타낸 코드입니다.

변수 unInitList에는 코틀린의 MutableList가 담길 수 있지만, 동시에 null 값도 담길 수 있습니다.

unInitList에 담길 수 있는 값의 종류 MutableList 값이나, null 값 두 가지가 올 수 있습니다.

이러한 경우에 코틀린은 ? 연산자를 통해 간편하게 null에 대응할 수 있도록 해줍니다.

만약 unInitList가 null이 아니라면, 크기(size) 값을 불러올 수 있으므로 size 값을 반환합니다.

unInitList가 null이면 size를 호출하지 않고 null을 반환합니다.

그런데, 위 상황을 살펴보면 한 가지 의문이 드실 수 있습니다.

코틀린에서 Safe Call로 null 값을 편하게 다룰 수 있다는 점은 이해하겠지만,

결국에 null 값을 반환하는 것이라면, 결과는 같은데 무슨 차이점이 있느냐는 점입니다.

이러한 의문점은 아래의 자바 코드와 비교하여 확인해보도록 하겠습니다.

 

List<Integer> unInitList = null;
System.out.println(unInitList);
int size = unInitList.size(); // Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.List.size()"
System.out.println(size);

 


위 자바 코드에서도 같이, 리스트를 생성하고 null을 할당해주었습니다.

하지만, 이를 바로 실행하면, NullPointerException이 발생합니다.

 

바로 이 부분에서 Safe Call과의 차이점 및 이점을 확인할 수 있습니다.

자바에서는 위처럼 코드를 작성하면, null에 대한 대응이 안 되었기 때문에

오류를 발생시키게 되고, 프로그램의 비정상적인 종료 같은 예기치 못한 문제를 발생시킵니다.

하지만, 코틀린에서는 null을 허용하는 변수와 그렇지 않은 변수를 구분하고

null을 허용한 변수에 Safe Call의 사용을 강제하여,

프로그램이 NullPointerException으로 인해 예기치 못하게 동작하는 것을 방지해줍니다.

 


컬렉션의 null 값 요소 제거

코틀린의 null 값은 리스트(List)와 같은 컬렉션의 요소 중 하나가 될 수도 있습니다.

또한, 코틀린에서 null 값은 객체(Object)뿐만 아니라 원시 타입(Primitive Type)과도 함께 다뤄질 수 있습니다.


코틀린에서는 아래와 같이 코틀린 컬렉션에 대해 필터링된 함수를 제공합니다.

val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()
println(intList); // [1, 2, 4]

 


코틀린에서 null 값 다루기

코틀린은 개발자가 null 값에 대해서 다양하게 대응할 수 있도록 여러 가지 기능을 제공합니다.

지금부터는 이러한 Null 값과 관련한 연산자를 통해 코틀린에서 Null에 대응하는 방법을 소개하도록 하겠습니다.

 

엘비스 연산자(Elivs Operator, ?:)

엘비스 연산자(?:)는 Kotlin의 널 병합(Null Coalescing) 연산자입니다.

이 연산자는 변수가 null일 때 대체 값을 지정하는 데 사용됩니다.

 

변수가 null이 아닌 경우에는 해당 변수의 값이 그대로 사용되고,

변수가 null이면 우측에 지정된 대체 값이 사용됩니다.

var unKnownData:Long? = null;
val myData:Long = unKnownData ?: 0L;
println(myData); // 0


위 코드에서 ?: 연산자는 unKnownData 변수가 null이면 0L을 대체 값으로 사용하고,

그렇지 않으면 unKnownData의 값을 그대로 사용합니다.

결과적으로 myData변수에는 0L이 할당되어 출력됩니다.


강제 형변환(!! operator)

!! 연산자는 Kotlin에서 사용되는 널 표명(Null Assertion) 연산자입니다.

이 연산자는 변수가 null이 아님을 표명하고, 컴파일러에 해당 변수를 null로 처리하지 말라고 알려줍니다.

!! 연산자는 아래와 같은 특징을 가집니다.

  • 변수가 null이 아님을 표명하고, 프로그래머가 명시적으로 null 체크를 수행하지 않아도 됩니다.
  • 만약 변수가 null이면 NullPointerException이 발생하며, 프로그램이 비정상적으로 종료될 수 있습니다.
  • 주로 널이 될 수 없는 변수나 반환 값에 사용되며, 널이 될 가능성이 있는 변수에 사용할 때 주의가 필요합니다.

*표명(Assertion)이란?
컴퓨터 프로그래밍에서 프로그램 안에 추가하는 참·거짓을 미리 가정하는 문

 

var unKnownData2:Long? = 134L;
println(unKnownData2!!); // 134


타입 체크(Type Check)와 타입 변환(Type Cast)

앞선 포스팅에서 다뤘듯이, 코틀린에는 다양한 종류의 데이터가 있습니다.

데이터의 종류는 숫자를 나타내는 정수형 데이터부터,

문자열을 나타내는 데이터, 그리고 객체까지 셀 수도 없이 많은 종류가 있습니다.

프로그래밍 언어에서 다양한 자료형은 개발자에게 다채로운 프로그램을 만들기 좋은 환경이 되어줍니다.

하지만, 이토록 다양한 자료형을 적절히 관리하지 않으면 오히려 개발의 난도를 높이고,

프로그램의 안정성을 떨어뜨리는 위험 요인이 될 수 있습니다.

코틀린에서는 이에 대응하기 위하여 다양한 자료의 타입을 확인하는 타입 체크(Type Check)를 지원합니다.
타입 체크(Type Check)란, 주어진 변수 등에 담긴 데이터의 유형을 미리 확인하는 것을 말합니다.

코틀린에서 타입 체크는 is 연산자를 통해 할 수 있으며,

as 연산자나 when 표현식을 활용하여 타입을 점검할 수도 있습니다.


is 연산자

코틀린에서 객체의 타입을 확인하는 데 사용되는 대표적인 연산자입니다.

이 연산자는 객체가 특정 클래스나 인터페이스의 인스턴스인지 확인합니다.

val obj: Any = "Hello"

if (obj is String) {
    println("Object is a String")
} else {
    println("Object is not a String")
}
// result -> Object is a String

 


as 연산자

as 연산자는 본래 타입의 변환(Type Cast)에 사용되는 연산자입니다.

하지만, 아래와 같이 타입 캐스팅을 시도한 후의 결과를 바탕으로 타입을 점검할 수 있습니다.

특히, as? 연산자를 사용하면 안전한 형 변환을 시도하고, 이때 형 변환에 실패하면 null을 반환합니다.

 

val obj2: Any = "Hello"
val str2 = obj2 as? String
if (str2 != null) {
    println("Object is a String: $str2")
} else {
    println("Object is not a String")
}
// result -> Object is a String: Hello

when 표현식과 타입 체크

when은 코틀린의 제어문 중 하나입니다.

하지만, 코틀린에서 when은 if처럼 활용하기 좋습니다.

 

특히 타입 체크 연산자 is와 함께 사용하면 아래와 같이 어떤 데이터가 어떤 타입에 속하는지 판별하고,

데이터의 타입에 따라 적절한 작업을 수행하도록 할 수 있습니다.

 

 val obj3: Any = "Hello"

when (obj3) {
    is String -> println("Object is a String")
    is Int -> println("Object is an Int")
    else -> println("Object is neither a String nor an Int")
}
// result -> Object is a String

 


코틀린의 형 변환(Type Cast)

코틀린은 객체지향 프로그래밍 언어입니다.

객체지향 프로그래밍 언어에서 객체는 상호 간에 `관계(Relationship)`에 따라 형 변환이 가능할 수 있습니다.

이와 관련한 더 자세한 설명은 다음 포스팅에서 다루도록 하겠습니다.

코틀린에서는 아래와 같이 as 키워드를 통해 형 변환(Type Cast)을 할 수 있습니다.

 

val obj4: Any = "Hello" // Any 타입의 변수에 문자열 할당
// 타입 캐스팅 시도
val str4: String = obj4 as String // obj를 String으로 캐스팅
// 캐스팅된 객체 사용
println("Length of the string: ${str4.length}") // Length of the string: 5


코틀린에서는 as 연산자를 사용하여 객체를 원하는 타입으로 변환할 수 있습니다.

이때, 변환이 성공하면 캐스팅된 객체를 사용할 수 있지만,

변환이 실패하면 ClassCastException이 발생할 수 있습니다.

위 코드에서 obj4는 Any 타입으로 선언되어 있습니다.

하지만 문자열로 초기화되었기 때문에 obj를 String 타입으로 형을 변환할 수 있습니다.

따라서 str4 변수에는 캐스팅된 문자열이 할당되어 있습니다.

이후에는 str4 변수를 사용하여 문자열의 길이를 출력하고 있습니다.

만약 캐스팅이 실패하면 ClassCastException이 발생합니다.

따라서 안전한 형 변환을 위해서는 as? 연산자를 사용하여 캐스팅을 시도할 수 있습니다.

 

val obj5: Any? = null // Any 타입의 변수에 문자열 할당
// 타입 캐스팅 시도
val str5: String? = obj5 as? String // obj5를 String으로 캐스팅
// 캐스팅된 객체 사용, Safe Call(?.)을 통해 안전하게 null 변수 탐색
println("Length of the string: ${str5?.length}")     // Length of the string: null


코틀린에서는 as? 연산자를 지원하여, 형 변환 실패에 대응할 수 있도록 하고,

이때, 형 변환이 실패하면 null을 반환받게 됩니다.


예외 처리(Exception Handling)

지금까지 코틀린의 Null Safety 문법과 형 변환(Type Cast), 그리고 타입 체크(Type Check)에 대해 알아보았습니다.

코틀린에서 null 타입에 대한 체크와 안정성을 보장하지 않으면, NullPointerException이 발생할 수 있습니다.

그리고 객체 간의 관계(Relationship)를 잘 확인하고 형 변환하지 않으면 ClassCastException이 발생할 수 있습니다.

그런데, 이외에도 다양한 상황에 따른 예외(Exception)가 발생할 수 있습니다.

프로그래밍에서 Exception은 프로그램 실행 중에 발생할 수 있는 예외적인 상황을 처리하기 위한 메커니즘입니다.

예외는 예기치 않은 상황이 발생했을 때 프로그램의 정상적인 흐름을 중단시키고,

예외를 처리하거나 전파하여 프로그램이 안정적으로 실행될 수 있도록 돕습니다.

 

코틀린에서 예외 처리는 아래와 같이 처리할 수 있습니다.

 

try {
    // some code
} catch (e: Exception) {
    // handler
} finally {
    // optional finally block
}

 


Try 표현식 (Try expression)

코틀린에서 try-catch 구문은 예외 처리 뿐만 아니라 변수에 대한 표현식으로 사용할 수 있습니다.

이는 제어문에서 if-else if-else 구문을 사용해 변수에 값을 담는 것과 같은 방식으로 사용할 수 있습니다.

try-catch 구문을 통해 변수의 할당, Try 표현식(Try Expression)은 아래와 같이 사용할 수 있습니다.

 

var input = /* ... */
val parsedNumber: Int? = try { input.toInt() } catch (e: NumberFormatException) { null }

Nothing

코틀린에서 throw 구문은 Kotlin 표현식으로 취급되므로, Elvis 표현식의 일부로서 활용할 수 있습니다.

다시 말하자면, 코틀린에서 Exception은 변수에 담아 사용할 수 있다는 의미입니다.

val s = person.name ?: throw IllegalArgumentException("Name required")

 

 

위의 코드를 보면, 변수 s에는 엘비스 연산자(?:)를 통해 예외(Exception) 처리를 하고 있습니다.

변수 s에서 예외가 발생할 때 throw를 통해 Exception 클래스의 인스턴스가 담기게 되며,

이때 변수 s의 표현식의 타입은 Nothing입니다.

 

이 유형에는 값이 없으며 절대 접근할 수 없는 코드 위치를 표시하는 데 사용됩니다.

코틀린 코드에서 함수에 Nothing을 사용하여 절대 반환하지 않는 함수를 표시할 수도 있습니다.


예외 처리(Exception Handling)이 필요한 이유

프로그래밍에서 예외(Exception)를 잘 활용해야 할 이유는 다음과 같습니다.

오류 상황 처리
프로그램 실행 중에 예기치 않은 상황이 발생할 수 있습니다.
예를 들어, 파일을 읽을 때 파일이 존재하지 않거나, 네트워크 연결이 끊겼을 때 등이 있습니다.
이러한 상황에 대처하기 위해서는 예외를 사용하여 오류를 처리할 수 있습니다.

프로그램 안정성 보장

예외를 처리하면 프로그램이 예상대로 동작하지 않을 때도 프로그램이 중단되지 않고 계속 실행될 수 있습니다.

이는 사용자에게 더 나은 경험을 제공하고, 프로그램의 안정성을 높여줍니다.

오류 정보 제공

예외는 발생한 오류에 대한 정보를 제공합니다.

이 정보를 통해 오류를 디버깅하고, 문제를 식별하고, 적절한 조처를 할 수 있습니다.

이러한 이유로 예외 처리는 프로그래밍에서 필수적인 부분 중 하나입니다.

그렇기에 코틀린에서도 예외 처리(Exception Handling)를 위한 수단을 제공하고,

이는 프로그램의 안정성을 보장하고 오류를 처리하는 데 중요한 역할을 담당합니다.


참고자료

https://en.wikipedia.org/wiki/Null_pointer#History

 

Null pointer - Wikipedia

From Wikipedia, the free encyclopedia Value indicating that a referenced dataset is invalid or doesn't exist In computing, a null pointer or null reference is a value saved for indicating that the pointer or reference does not refer to a valid object. Prog

en.wikipedia.org

https://kotlinlang.org/docs/typecasts.html#is-and-is-operators

 

Type checks and casts | Kotlin

 

kotlinlang.org

https://kotlinlang.org/docs/exceptions.html

 

Exceptions | Kotlin

 

kotlinlang.org