코틀린 언어 정리 2-14

class 확장(Extensions)

특징(제한) 및 사례 분석

(계속)...

Nullable Receiver

확장 함수의 Receiver type을 Nullable로 선언할 수도 있습니다.(아래 예제에서 fun A?.func1() ) 이렇게 선언된 확장 함수는 리시버 객체가 null일 경우 함수 외부에서 null 안전 처리를 하지 않아도 컴파일 에러가 발생하지 않습니다. 즉 리시버 객체가 null인 상태에서도 확장 함수를 호출할 수 있습니다. 하지만 함수 내부에서 this에 대해 반드시 null체크를 하도록 강제합니다.


예제

class A {

   var value: Int = 10

   fun func2() {

       println("A::func2()")

   }

}


fun A?.func1() {

   println("A?::func1()")

   //println(this.value) // ?., !! null 안전 연산자 사용 권고 컴파일 에러. 즉 이 함수 내부에서는 this에 대한 null체크를 한 후에 this를 사용할 수 있습니다.

}


fun main(args: Array<String>) {

   var oA: A? = null

   oA.func1() // func1확장 함수 내부에서 null 안전처리를 하도록 강요하므로 이 코드는 컴파일 에러가 발생하지 않습니다.

}

위 예제에서 func1이 nullable receiver A?의 확장 함수로 선언 되었습니다. nullable receiver가 아닐 경우 main함수의 oA.func1() 부분은 원래 null 처리 관련 컴파일 에러가 발생해야 하는데 nullable로 선언되었으므로 에러가 발생하지 않습니다.


예제

class A {

   var value: Int = 10

}


/*

fun A.func1() {

println("A::func1()") }

*/


fun A?.func1() {

   println("A?::func1()")

   println(this.value) // null체크 없이 사용하면 ?., !! null 안전 연산자 사용 권고 컴파일 에러

}


fun main(args: Array<String>) {}

위의 코드를 컴파일 하면 nullable receiver에 "?.", "!!" 연산자를 사용하지 않았다는 컴파일 에러가 발생합니다. func1의 구현 중 this를 사용하기 전 부분에 if문으로 this에 대한 null check를 하거나 아래 코드 처럼 this 참조 시 ?. 연산자 등을 사용하면 에러가 발생하지 않습니다.


예제

fun A?.func1() {

   println("A?::func1()")

   println(this?.value) // ?. 연산자를 사용하여 null safe하므로 컴파일 에러 발생 안함.

}

확장함수의 리시버가 null인 상황이 의도된 것이고 확장 함수 내에서 this를 참조한다면 this에 대해 null check를 하여 null참조 에러 및 null 안전과 관련된 컴파일 에러를 제거할 수 있습니다.


예제

class A {}


fun A.func1() {

   println("A::func1()")

}


fun A?.func1() {

   println("A?::func1()")

}


fun main(args: Array<String>) {}

Receiver Type이 nullable 이거나 nullable이 아닌 경우의 각각의 확장 함수 선언은 컴파일 시 동일한 클래스의 함수 선언으로 처리 되므로 둘 중 하나의 선언만 사용할 수 있습니다. 따라서 위의 예제를 컴파일 시키려면 A.func, A?.func 둘 중 하나의 함수를 제거해야 합니다.


전체 예제

class A {

   var value: Int = 10

   fun func2() {

       println("A::func2()")

   }

}


/* // func1 함수 중복 선언으로 컴파일 에러

fun A.func1() {

println("A::func1()") }

*/


fun A?.func1() {

   println("A?::func1()")

   if (this != null)

       println(this.value)

   println(this?.value) // null체크 없이 사용하면 ?., !! null 안전 연산자 사용 권고 컴파일 에러 발생

}


fun main(args: Array<String>) {

   var oA: A? = null

   oA.func1() // 이 코드는 컴파일 에러가 발생하지 않습니다.


   //oA.func2() // null 가능 객체에서 멤버 함수 호출 시 null 안전 처리를 하지 않으면 컴파일 에러 발생

   oA?.func2()

}


정리

  • 리시버가 nullable 또는 nullable이 아닌 확장 함수 선언은 동일한 선언으로 처리됩니다. 따라서 둘 중 하나의 선언만 사용해야 합니다.

  • nullable type 확장 함수에서 this를 사용하려면 null 안전 처리를 반드시 해야 합니다.

  • nullable로 선언된 리시버 객체 변수에 대해서는 null 안전 처리를 하지 않아도 컴파일 에러가 발생하지 않습니다.


+ Recent posts