아시다시피 자바와 코틀린은 객체지향 언어(Object Oriented Programming Language)입니다.
그리고 객체 지향 프로그래밍 언어의 특징 중 하나인 다형성으로 인해 부모 객체에 자식 클래스의 인스턴스를 주입하는 등 조건에만 맞는다면 꼭 인스턴스와 같은 객체 타입이 아니더라도 대입할 수 있습니다.
그러다 보면 필요에 따라 Type Casting(형 변환)을 해야 하는 경우가 있는데요. 기존에 자바에서는 instanceof 키워드를 통해 안전하게 형 변환 가능한지 검사한 후에 변환하고자 하는 대상 앞에 () 괄호를 명시해주어 형 변환을 할 수 있었습니다.
그렇다면 코틀린에서는 어떻게 형 변환을 할 수 있을까요?
대표적으로 is 와 as 가 있습니다만, 이번 포스팅에서는 스마트 캐스팅 기능을 지원하는 is 에 대해 알아보도록 하겠습니다.
스마트 캐스팅 - is
우선 코틀린에는 자바의 instanceof 가 없습니다. 대신 is 라는 녀석이 있는데요. 독특하게도 이 녀석은 instanceof 이상의 역할을 해줍니다. 사용 방식은 다음과 같습니다.
[타입 체크할 변수] is [확인하고자 하는 타입]
코틀린의 is는 단순히 타입을 체크하는 역할을 넘어 결과로 true를 리턴할 경우 해당 타입으로 자동 캐스팅을 해줍니다. 즉, 캐스팅을 위한 변수를 하나 더 만들지 않아도 되는데요.
예제를 통해 자바의 instanceof 를 사용했을 때와 비교해보도록 하겠습니다.
java code
class JavaA {
int a = 5;
}
class JavaB extends JavaA {
int b = 10;
}
public class Main {
public static void main(String[] args) {
JavaA obj = new JavaB();
if (obj instanceof JavaB) {
JavaB obj2 = (JavaB) obj; // 타입 캐스팅을 위한 별도의 변수 생성
System.out.println(obj2.b); // 10
}
}
}
먼저 자바 코드입니다. JavaA 라는 클래스와 그를 상속하는 JavaB 클래스를 정의하여 JavaA 타입의 객체에 들어있는 JavaB 인스턴스를 타입 체크 후에 자식 클래스인 JavaB 타입으로 형 변환하는 예제입니다.
obj 변수는 JavaB 생성자를 통해 생성한 인스턴스이기 때문에 "obj instanceof JavaB" 의 결과로 true를 리턴하게 되고, 따라서 obj2 라는 JavaB 타입의 객체에 대입할 수 있게 됩니다.
이번에는 같은 예제를 코틀린의 is 를 사용해보도록 하겠습니다.
kotlin code
open class KotlinA {
val a = 5
}
class KotlinB : KotlinA() {
val b = 10
}
fun main() {
val obj: KotlinA = KotlinB()
if (obj is KotlinB) { // smart casting
println(obj.b) // 10
}
}
코틀린 코드입니다. 전반적으로 자바 코드와 유사해 보이는 듯 하지만 한 가지 특이한 부분이 있습니다.
바로 KotlinB 타입으로 타입 캐스팅을 해주는 코드도 없을뿐더러 그를 저장하기 위한 변수도 따로 만들지 않았음에도 KotlinA 타입으로 선언한 obj 변수에서 곧바로 KotlinB 클래스에 정의된 b 변수를 호출하는 부분입니다.
이러한 캐스팅을 스마트 캐스팅이라고 하는데요. 비록 obj 가 처음에 선언할 때는 KotlinA 타입으로 선언되었지만, is 키워드를 통해서 KotlinB 타입이라는 것이 확인된 시점부터는 코틀린이 자동으로 검사 대상이었던 obj 변수의 타입을 KotlinB 타입으로 캐스팅하게 됩니다.
보통 어떤 변수의 타입을 체크하는 목적에는 해당 타입으로의 캐스팅을 하기 위해서인 경우가 많기 때문에 이 스마트 캐스팅은 개발자가 좀 더 간결한 코드 작성을 할 수 있도록 도와줍니다.
여기서 하나 응용을 해보자면 타입이 아닌지 체크하는 문법은 is 앞에 느낌표를 붙여주면 됩니다. -> !is
그리고 재밌는 기능은 if 문 안에서 !is 를 사용하게 되면 else 문 안에서 스마트 캐스팅이 적용되게 됩니다.
if (obj !is String) { // same as !(obj is String)
print("Not a String")
} else {
print(obj.length) // obj is String
}
위 코드에서처럼 if 문에서 obj 의 타입이 String 이 아니라는 조건에서 false가 리턴된다면 String Type 이라는 결론이 도출되어 obj의 타입이 String으로 스마트 캐스팅 되게 됩니다.
한 가지 더 특징이 있는데요. && 와 || 등 논리 연산자를 통해 여러 조건이 함께 쓰일 경우에 왼쪽에서 오른쪽 순으로 검사가 일어나기 때문에 만약 is 나 !is 를 이용하여 왼쪽에 조건식을 걸어둘 경우 이어서 검사 되는 오른쪽 조건식에서 스마트 캐스팅이 적용된 채로 검사하게 됩니다.
// x is automatically cast to string on the right-hand side of `||`
if (x !is String || x.length == 0) return
// x is automatically cast to string on the right-hand side of `&&`
if (x is String && x.length > 0) {
print(x.length) // x is automatically cast to String
}
그러나 스마트 캐스팅은 항상 적용되는 것이 아닙니다. 컴파일러가 타입 체크와 사용에 대해 보장할 수 없는 상황에서는 적용되지 않는데요. 관련해서 몇 가지 규칙과 주의사항이 있습니다. val 과 var 에 따라 조금씩 다른데요.
- val local variable(지역 변수) - delegate properties(ex - by lazy 등) 의 경우를 제외하고 항상 적용됨
- var local variable - 체크와 사용 사이에 수정되지 않고, 람다에서 변수가 수정되지 않으며, delegate properties가 아닐 경우에만 적용됨
- val properties(멤버 변수) - 같은 모듈 내에서 선언됐을 경우에는 적용되지만, open properties나 custom getter의 경우에는 적용되지 않음
- var properties - 어떠한 경우에도 스마트 캐스팅이 적용되지 않음
제네릭(Generic)과 is
코틀린에서 제네릭을 사용하게 되면 컴파일 타임에서 각 연산에 대해 Type Safety 를 보장하게 됩니다만, 런타임에서는 제네릭 타입에 대한 정보를 갖고 있지 않습니다. 예를 들어 위에서 선언했던 KotlinA 타입을 갖는 List<KotlinA> 라는 컬렉션을 선언했을 때 이는 컴파일 단계에서는 리스트에 들어가고 나오는 타입에 대해 KotlinA 타입이 맞는지 검사해주지만, 런타임 단계에서는 KotlinA 타입에 대한 정보는 지우고 그저 List<*> 로 취급할 뿐입니다.
따라서 런타임 단계에서 obj is List<KotlinA>와 같이 특정 타입 파라미터에 대한 타입 검사하는 것이 불가능합니다만, obj is List<*> 와 같이 별표를 사용하거나 ArrayList나 LinkedList 등 구체적인 구현 객체에 대한 타입 검사는 가능합니다.
if (something is List<*>) {
something.forEach { println(it) } // The items are typed as `Any?`
}
fun handleStrings(list: List<String>) {
if (list is ArrayList) {
// `list` is smart-cast to `ArrayList<String>`
}
}
위 예제에서 작성된 코드는 Github 저장소에서 확인하실 수 있습니다.
'Kotlin' 카테고리의 다른 글
[Kotlin] 한 방에 정리하는 코틀린 제네릭(kotlin generic) - in, out, where, reified (4) | 2021.07.20 |
---|---|
[Kotlin] 의외로 놓치기 쉬운 when, 제대로 알아보기 (0) | 2021.07.17 |
[Kotlin] 헷갈리는 "Nothing" 확실하게 이해하기(feat. Any, Unit) (5) | 2020.01.30 |
코틀린 중첩 클래스(Kotlin Nested and Inner Classes) (0) | 2019.11.26 |
코틀린의 강력한 문법! 코틀린 익스텐션(Kotlin Extensions) (0) | 2019.11.21 |