(우아한테크코스 미션 피드백을 듣다가 한 가지 새로운 사실을 알게 되어 정리합니다.)
Kotlin은 정수를 나타내는 타입으로 Int와 Integer가 있습니다.
이 둘의 차이는 Int는 primitive type이지만 Integer는 reference type이라는 점입니다.
이 둘의 존재로 인해, 동등성과 동일성을 비교해야 하는 상황에서 어떠한 차이가 발생하는지 알아보겠습니다.
isEqualTo() 메서드는 동등성을 비교합니다. (객체의 내용이 같음)
isSameAs() 메서드는 동일성을 비교합니다. (메모리 주소가 같음)
@Test
fun test1() {
val actual: Int = 1
val expected: Int = 1
assertThat(actual).isEqualTo(expected)
assertThat(actual).isSameAs(expected)
}
해당 테스트 코드를 실행하면 통과할 것입니다.
왜냐하면 Int는 primitive type이므로 동일성, 동등성을 비교해도 같기 때문입니다.
@Test
fun test2() {
val actual: Int = 1000
val expected: Int = 1000
assertThat(actual).isEqualTo(expected)
assertThat(actual).isSameAs(expected)
}
그렇다면 이 테스트 코드는 통과할까요?
test1과 숫자만 달라졌기 때문에 통과할 것이라고 생각이 듭니다.
하지만, 결과를 확인해보니 예상과 달리 테스트 코드는 실패합니다.
test1과 동일하게 primitive type을 비교한 것인데 왜 해당 테스트 코드는 실패하는 것일까요?
그 이유를 알아보기 위해, Kotlin 코드를 Java 코드로 Decompile 해보겠습니다.
Decompile 결과 int 타입을 Integer 타입으로 변환하여 인자를 전달하고 있습니다.
그 이유는 isSameAs() 메서드는 인자로 Object(원시값이 아닌 객체)를 받기 때문입니다.
그래서 JVM은 컴파일 타임에 Int를 Integer.valueOf() 메서드를 통해 Integer로 변환합니다.
그럼 여기서 드는 의문은 1이나 1000이나 Integer로 변환하고 나면 reference type이니 결국 서로 다른 메모리(heap) 공간에 할당되어 있을 텐데, 왜 1은 통과하고 1000은 실패하는 것일까요?
그 이유는 valueOf() 메서드의 특징때문입니다.
위에 조그맣게 작성되어 있는 설명을 읽어보니 valueOf() 메서드는 -128에서 127 범위의 정수는 캐시를 통해 항상 같은 객체를 반환한다고 합니다.
그렇기 때문에 1은 Integer.valueOf()로 전달했을 때 같은 객체를 반환받았기에, 동일성 비교에서 통과할 수 있었던 것입니다.
반면, 1000은 캐시 해놓은 범위를 넘어서기 때문에, 매번 새로운 객체를 만들어 동일성 비교에서 실패할 수밖에 없습니다.
Integer 클래스는 객체를 생성하기도 하고, 관리하는 '객체의 능동적인 관리자' 역할도 함을 알 수 있습니다.
@Test
fun test3() {
val actual: Int = 1000
val expected: Int = 1000
assertThat(actual == expected).isTrue
assertThat(actual === expected).isTrue
}
이 테스트 코드는 Kotlin을 조금 공부해 봤다면, 통과할 것이라고 예상할 수 있습니다.
== 연산자는 동등성을 비교하고, === 연산자는 동일성을 비교합니다.
여기에서는 따로 isSameAs()와 같은 메서드를 호출하지 않기 때문에 primitive type 그대로 비교하여, actual과 expected는 동일하다고 할 수 있습니다.
@Test
fun test4() {
val actual: Int? = 1
val expected: Int? = 1
assertThat(actual == expected).isTrue
assertThat(actual === expected).isTrue
}
Kotlin에는 타입 뒤에 '?'를 붙여줌으로써 nullable 하게 선언할 수 있습니다.
그럼 nullable 한 타입에 대해서는 동일성 비교에서 어떠한 결과를 보여줄지 생각해 볼 필요가 있습니다.
위 코드는 값이 1인 Int? 타입에 대해 동등성, 동일성 비교를 하고 있습니다.
test4를 Java 코드로 변환해 보니, Int? 타입이 Integer로 변환되어 있음을 확인할 수 있습니다.
위에서 살펴봤듯이, Integer는 -128부터 127까지는 같은 객체를 공유하기 때문에 동등성, 동일성 테스트에서 모두 True를 반환합니다.
'===' 연산자 대신 isSameAs() 메서드를 사용해도 동일한 결과가 도출됩니다.
@Test
fun test5() {
val actual: Int? = 1000
val expected: Int? = 1000
assertThat(actual == expected).isTrue
assertThat(actual === expected).isFalse
}
위 글을 모두 이해했다면, 이 테스트 코드 또한 바로 이해할 수 있을 것입니다.
캐싱 범위(-128에서 127)를 넘어선 값이기 때문에 객체를 새롭게 생성하여, actual과 expected는 서로 다른 객체라고 할 수 있습니다.
따라서 동일성 비교 연산자인 '==='에서 False를 반환합니다.
Kotlin은 Java에 비해 훨씬 간결하고 편리하다는 특징을 가지고 있지만, 한 단계 더 깊이 알아야 한다는 점을 알 수 있었습니다.
해당 포스팅 내용에 오류가 있다면 댓글로 남겨주세요 :)
github : https://github.com/tmdgh1592
'Programming > Kotlin' 카테고리의 다른 글
[Kotlin] Jvm Prefix Annotation 5가지 파헤치기 (1) | 2023.04.05 |
---|---|
[Kotlin] Value Class (inline class deprecated) (2) | 2023.02.21 |
[Kotlin] const val vs val - 둘의 차이점은 무엇일까? (0) | 2023.02.11 |