코틀린에서 형변환은 as
를 사용한 불안 형변환과 as?
를 사용한 안전 형변환이 있다.
불안 형변환 (unsafe cast)
형변환이 불가능하면 ClassCastException이 발생한다.
val fragment: Fragment = ProductFragment()
val productFragment: ProductFragment = fragment as ProductFragment
ProductFragment의 인스턴스를 Fragment 데이터 형식을 저장하는 변수 fragment에 할당했다. ProductFragment 데이터 형식만 저장할 수 있는 productFragment 변수에 Fragment 데이터를 저장하려면 형변환을 해야한다. 그래서 as
를 사용하여 fragment를 ProductFragment의 인스턴스로 바꾼 것이다.
위 코드는 ProductFragment가 Fragment의 하위 형식이기 때문에 잘 작동된다. 하지만 상위 형식이나 호환되지 않는 형식으로 형변환을 하려 하면 ClassCastException이 발생한다. 이러한 문제를 막기 위해 as?
를 사용해야 한다.
안전 형변환 (safe cast)
as?
연산자를 사용하며, 값을 지정된 형식으로 형변환하려 시도하고 형변환이 되지 않으면 null을 리턴한다. null 허용 연산자라고도 한다.
val fragment: String = "ProductFragment"
val productFragment: ProductFragment = fragment as ProductFragment // 예외 발생
String은 ProductFragment로 변환되지 않기 때문에 위 코드는 ClassCastException이 발생한다. 이를 해결하기 위해 as
나 as?
를 사용해보자.
val productFragment: ProductFragment? = fragment as? ProductFragment
val productFragment: ProductFragment? = fragment as ProductFragment?
첫번째 예시는 as?
를 사용하기 위해 변수에 null을 허용했다. 이유는 as?
는 형변환이 되지 않으면 null을 리턴하기 때문이다.
두번째 예시는 as
를 사용하는 대신 변수와 ProductFragment 모두 null을 허용했다.
productFragment를 null 불허 변수로 만들고 싶다면 아래와 같이 쓰면 된다.
val productFragment: ProductFragment? = fragment as? ProductFragment ?: ProductFragment()
형변환이 됬다면 값이 productFragment변수에 할당되고, 형변환이 되지 않았다면 새로운 ProductFragment 인스턴스가 변수에 할당된다.
다음은 프래그먼트 관리자로부터 안전하게 프래그먼트를 얻을 수 있는 코드이다. (프로젝트 하면서 정말 많이 쓰인다)
val productFragment: ProductFragment? = supportFragmentManager.findViewById(R.id.fragment_product) as? ProductFragment
스마트 형변환
개발자가 연산자를 사용하지 않아도 컴파일러가 형변환을 해주는 기능이다. 변수가 변하지 않는다는 것을 컴파일러가 확신할 때만 작동한다. null 가능성 (!
연산자를 사용한 변수) 스마트 형변환은 null 허용 참조를 null 불허 참조로 변환한다.
if (starfish is Fish) {
starfish.eatSeaweed() // 컴파일 O
}
starfish.eatSeaweed() // 오류
Starfish라는 클래스 안에는 eatSeaweed() 메소드가 있고, Fish 클래스의 자식이라고 가정하자. if문을 통해 starfish가 Fish의 인스턴스인 것이 확인되면 컴파일러가 starfish가 Fish로 형변환을 해주기 때문에 바로 eatSeaweed() 메소드를 부를 수 있다. if문 밖에서는 starfish가 Fish의 인스턴스가 아니기 때문에 형변환이 되지 않아 메소드를 부를 수 없다.
if (starfish !is Fish)
return
starfish.eatSeaweed()
이 코드에서는 starfish.eatSeaweed()가 정상적으로 호출되는데, starfish가 Fish의 인스턴스가 아닌 경우 메소드 호출 전 리턴이 되기 때문이다.
if (starfish is Fish && starfish.eatSeaweed()) {
println("starfish is happy")
}
&&
과 ||
연산자에서 첫번째 조건이 false일 경우 다음 조건들은 읽히지 않는다는 점을 이용해 위처럼 코드를 작성하면 더 효율적으로 코드를 작성할 수 있다. 메소드가 불린다는 것은 starfish가 Fish의 인스턴스인 경우이므로 스마트 형변환이 일어난다.
스마트 형변환을 null 검사에 사용하는 경우이다.
val view: View?
if (view != null) {
view.isShown() // null 불허
}
view.isShown() // null 허용
view는 null허용으로 정의되었고, if문에서는 view가 null이 아니므로 view는 null 불허로 변환된다. if문 밖에서는 view가 null이므로 그대로 null 허용이다.
엘비스 연산자를 이용해 코드를 간단히 작성해보자. 아래 코드는 view가 null이 아닌 경우 isShown() 메소드를 호출한다.
fun verifyView(view: View?) {
view ?: return
view.isShown()
}