본문 바로가기

Kotlin/Basic

Kotlin - Null 처리와 동일성 확인 (Null Safety and Equality)

내가 코틀린을 배우면서 코틀린에 있어 특별하다고 생각되는 부분들 또는 메모해두어야 할 점들을 여기에 적어놓으려고 한다.

Null 처리

Nullable 변수에 null 값이 대입되어 있을 때, 만약 해당 변수의 클래스 메서드 또는 속성을 불러오려고 하면 Null Pointer Exception 예외가 발생한다. 따라서 nullable 변수를 사용할 때에는 null인지 아닌지 먼저 확인을 해주어야 한다. 예를 들어 아래와 같이 코드를 작성할 수 있을 것이다.

 

예시

 

fun main() {
    var a: String? = null;
    if (a!=null) {
        println(a.uppercase())
    }
}

 

위 예시는 아무것도 출력하지 않는다. a가 null이기 때문이다. 하지만 이러한 null 체크를 하기 위해 if문으로 일일이 조건을 체크하는 것은 코드의 양이 늘어나는 결과를 초래할 수 있다. 따라서 이러한 경우에 쓸 수 있는 연산자가 존재한다.

 

Null safe operator

Null safe operator는 참조연산자 (.) 앞에 물음표 (?) 가 들어간 형태로 사용할 수 있다 (?.) . Null safe 연산자는 참조연산자를 실행하기 전에 먼저 객체가 null인지 확인한 후, null 일 시에는 뒤에 오는 구문을 실행하지 않는 특징을 갖고 있다.

 

예시

 

fun main() {
    var a: String? = null
    var b: String? = "Bee"
    
    println(a?.uppercase())
    println(b?.uppercase())
}

 

출력

 

null
BEE

 

이 방식은 run과 함께 사용하기 좋다. 먼저 null인지 확인한 후에 실행시키고 싶은 구문이 있다면 run에 넣어 실행시키면 되기 때문이다. 예를 들어 아래와 같이 사용할 수 있다.

 

예시

 

fun main() {
    var a: String? = null
    a?.run {
        println(uppercase())
        println(lowercase())
    }
    var b: String? = "Hello"
    b?.run {
        println(uppercase())
        println(lowercase())
    }
}

 

출력

 

HELLO
hello

 

아래의 Elvis operator도 이런 식으로 응용하여 사용할 수 있다.

 

Elvis operator

Elvis operator는 콜론 (:) 앞에 물음표가 들어간 형태로 사용할 수 있다 (?:) . 엘비스 오퍼레이터라고 불리는 이유는 해당 연산자를 90도 돌려보면 엘비스 프레슬리의 머리와 눈 두개 처럼 보이기 때문이다. 엘비스 연산자는 객체가 null이 아니라면 그대로 사용하지만, null이라면 연산자 우측의 객체로 대체되는 연산자이다.

 

예시

 

fun main() {
    var a: String? = null
    println(a?:"default".uppercase())
}

 

출력

 

DEFAULT

 

a가 null이기 때문에 우측에 있던 "default"라는 객체에 대입되어 해당 구문이 실행되었다.

 

Non-null assertion operator

Non-null assertion operator는 참조연산자 앞에 느낌표 (!) 두 개가 들어간 형태로 사용할 수 있다 (!!.) . Non-null assertion operator는 참조연산자를 사용할 때 null여부를 컴파일 시 확인하지 않도록 하여, 런타임 시 null pointer exception이 나도록 의도적으로 방치하는 연산자이다.

 

예시

 

fun main() {
    var a: String? = null
    println("hello")
    println(a!!.uppercase())
}

 

출력

 

hello
Exception in thread "main" java.lang.NullPointerException
 at FileKt.main (File.kt:9) 
 at FileKt.main (File.kt:-1) 
 at sun.reflect.NativeMethodAccessorImpl.invoke0 (NativeMethodAccessorImpl.java:-2) 

 

컴파일 시 오류가 나지 않고, 그대로 실행되어 런타임 시에 예외가 나는 것을 볼 수 있다.

 

동일성과 객체 참조

동일성에는 두 가지가 있다. 첫 번째는 내용의 동일성이며 두 번째는 객체의 동일성이다. 내용의 동일성이란 두 변수가 같은 값 (내용) 을 가지는 지에 대한 동일성이며, 객체의 동일성이란 두 개의 변수가 서로 같은 객체를 가리키고 있는 지 (참조하고 있는 지) 에 관한 동일성이다.

 

두 객체가 값이 같은지를 확인하고 싶다면 이중 등호 (==) 를 사용하면 된다. 두 변수가 같은 값을 담고 있더라도, 가리키고 있는 객체 또는 참조하고 있는 객체는 서로 다를 수 있다. 이 경우 삼중 등호 (===) 를 사용하여 객체의 동일성을 확인할 수 있다.

 

예시

 

fun main() {
    var a = Dummy("beom")
    var b = Dummy("beom")
    var c = a
    
    println(a == b)
    println(a === b)
    println(a === c)
}

class Dummy(val name: String) {
    override fun equals(other: Any?): Boolean {
        if (other is Dummy) {
            return other.name == name
        } else {
            return false
        }
    }
}

 

출력

 

true
false
true

 

c는 그대로 a의 참조를 받아왔기 때문에, 같은 참조를 가져 a와 객체의 동일성을 지니게 된다. 커스텀 클래스를 구현할 때에는 equals 함수를 커스텀으로 구현해주어야 값의 동일성을 판단할 수 있다. 만약 위에 경우에서 equals 함수를 따로 구현해주지 않았다면 첫 번째 값의 동일성에 대한 비교에서 false가 반환될 것이다.

 

Any 자료형

위에서 본 equals 함수는 Any라는 자료형에서의 equals를 오버라이드하여 구현한 커스텀 equals 함수이다. 이는 코틀린에서의 이중 등호는 Any 클래스의 equals 에서의 참/거짓 여부를 통해서 참/거짓을 반환하기 때문인데, 기본 클래스들에는 equals 함수가 모두 구현이 되어있지만, 커스텀으로 클래스를 만들 때에는 이것을 따로 구현을 해주어야 후에 이중 등호를 사용하여 값의 동일성 여부를 확인할 때 사용할 수 있다.

 

Any 자료형은 모든 자료형들의 슈퍼 클래스이다. 즉, 코틀린의 모든 클래스는 Any 자료형을 슈퍼클래스로 가지며, 따라서 Any 자료형은 어떠한 자료형이라도 모두 될 수 있고, 필요한 자료형으로 언제나 변환이 가능하다. 이를 묵시적 변환이라고 한다.

 

예시

 

fun main() {
    var a: Any = "string"
    println(a)
    a = true
    println(a)
    a = 123
    println(a)
    a as Int
    println(a is String)
    println(a is Int)
    println(a is Any)
}

 

출력

 

string
true
123
false
true
true

 

출처

https://juyeop.tistory.com/4

https://www.youtube.com/playlist?list=PLQdnHjXZyYadiw5aV3p6DwUdXV2bZuhlN