확장 (Extension)


 코틀린은 클래스 상속이나 데코레이터 패턴과 같은 디자인 패턴을 사용하지 않고도 새로운 기능을 가지는 클래스로 확장할 수 있습니다. 그것이 바로 extension(이하 확장)입니다. 코틀린은 extension function과 extension property를 지원합니다.


확장 함수 (Extension Function)


 확장 함수는 [확장을 하려는 대상.함수]로 쉽게 생성이 가능합니다.

 일단 확장 함수의 예를 살펴보겠습니다. 아래 코드는 Car 타입에 getBrandName()이라는 확장 함수를 만든 것입니다.

fun main(args: Array<String>) {

    val car = Car()

    println(car.getBrandName())

}


class Car {

    fun getPrice() : Int {

        return 10000

    }

}


fun Car.getBrandName() : String {

    return "BMW"

}

BMW 


 Car 클래스에는 존재하지 않는 getBrandName()이라는 함수가 아주 쉽게 만들어졌습니다. 심지어 Car 클래스에 멤버로 추가되거나 기존 함수를 수정하지도 않았습니다. 이런 것이 바로 확장입니다.


 그런데 확장 함수의 Car에 커서를 올리면 다음과 같이 나옵니다.



receiver 파라미터가 사용되지 않아서 발생한 경고입니다. receiver? 그게 뭘까요?


 위 코드에서 보듯이 '확장을 하려는 대상'입니다. 현재 Car 타입을 확장하고 있다는 것을 기억하셔야 합니다. 그럼 이번에는 해당 확장 함수에 receiver type과 동일한 return type을 선언하여 실제 receiver를 사용해보겠습니다.


fun main(args: Array<String>) {

    val car = Car()

    println(car.getBrandName().getPrice())

}


class Car {

    fun getPrice() : Int {

        return 10000

    }

}


fun Car.getBrandName() : Car {

    println("BMW")

    return this

}

BMW

10000 


 car.getBrandName()를 통해서 println("BMW")가 실행되고 return type이 receiver type과 동일한 Car이므로 멤버 함수인 getPrice()까지 호출이 됩니다.



Extensions are resolved statically


 위에서 살펴봤듯이 확장(Extension)은 실제로 확장(Extension)으로 확장한 클래스를 수정하는 것은 아닙니다. 클래스에 새로운 멤버를 추가하지 않고 확장을 정의만 하면 그(=receiver) 타입의 변수에 .(dot)로 호출할 수 있는 새 함수를 만듭니다.


 코틀린 레퍼런스에서는 확장 함수가 정적으로 전달된다는 것을 강조하고 있습니다. 즉, receiver type에 의해서 가상화 되지 않습니다. 호출되는 확장 함수는 런타임에 해당 표현식(expression)을 평가한 결과의 type에 의한게 아니라 함수가 호출되는 표현식의 type에 의해 결정됩니다. (원문을 직역했더니 표현이 참 매끄럽지 못하네요. 그래서 이 말의 의미를 이해하기가 아주 힘들었습니다.)


 한국어인데도 무슨 말인지 선뜻 이해가 되지 않습니다. 이럴 때는 코드를 봅시다.


fun main(args: Array<String>) {

    printFuel(Bus())

}


open class Car

class Bus: Car()


fun Car.getFuel() = "gasoline"

fun Bus.getFuel() = "lpg"


fun printFuel(fuel: Car) {

    println(fuel.getFuel())

}

  gasoline


 먼저 확장 함수가 정적으로 전달된다고 한 것은 printFuel 함수에 파라미터 fuel은 Car 타입으로 정적인 상태로 있다는 것을 의미합니다. 따라서 Bus 타입으로 호출하여도 Car 타입의 확장 함수가 실행이 되어 gasoline이 출력된 것입니다.


 receiver type에 의해서 가상화가 되지 않는다는 것은 receiver로 Car를 사용하여 Bus를 만들어내는 작업이 되지 않았다는 것입니다. 예를들면 다음과 같이 Int를 Double로 바꾸는 작업같은 개념입니다. 이건 지금 잘 이해가 안되더라도 추후 다시 공부할 시간이 있을 것 같습니다. (제발 누가 알려주세요!)


 val intToDouble: Int.() -> Double = { toDouble() } 


 printFuel 함수 호출부와 선언부의 Java 변환 코드를 보면 파라미터 부분이 명확하여 이해하는데 도움이 됩니다.


 printFuel((Car)(new Bus()));

 ...

 ...

    public static final void printFuel(@NotNull Car fuel) {

      Intrinsics.checkParameterIsNotNull(fuel, "fuel");

      String var1 = getFuel(fuel);

      System.out.println(var1);

   }



확장(Extension) #1을 마치며


 확장 함수를 보기 전에 Extensions are resolved statically 레퍼런스의 설명이 이해되지 않아 같은 문장을 새벽까지 수십번 읽은 것 같습니다. 이게 확장 함수를 이해하는데 한결 도움이 되었네요. 그래서 그 후 확장 함수 (Extension Function)를 작성했습니다.

IDE 설치하기


목표 : IntelliJ IDEA Community Edition 2017.3.1 설치 후 'Hello Kotlin' 출력하기


 저는 지금까지 안드로이드 스튜디오 3.0.1로 업데이트 후 안드로이드 스튜디오에서 코틀린 테스트 코드를 실행했었습니다. 아직 코틀린으로 안드로이드 개발을 시작하기까지 갈 길이 멀기도하고 새로운 IDE를 사용해보고 싶어서 코틀린 학습용 환경을 구축을 이제서야 해봤습니다.


https://try.kotlinlang.org/

간단한 예제는 위 사이트에서도 가능합니다.





안드로이드 스튜디오 3.0.1에서 코틀린 코드 테스트


 그럼 코틀린 학습 환경을 구축해보겠습니다. 먼저 IDE인 IntelliJ IDEA를 설치합니다.

IDE 다운로드 : http://www.jetbrains.com 


Tools > IntelliJ IDEA



상업용과 무료버전이 있습니다. 학습용이니 무료버전인 Community를 다운로드 합니다.



다운로드 후 설치를 진행합니다.

본인 OS에 맞게 32 / 64 bit 중 선택합니다.

Create Associations은 해당 파일을 실행할 때 바로 IntelliJ가 켜지도록 연결할지를 선택합니다.

NotePad를 사용중이라 소스파일을 바로 IDE로 켤 일이 없어서 전 uncheck 하고 넘어갔습니다.




설치중...




설치가 완료되면 바로 실행해봅시다.




기존에 사용하던 설정값을 불러오는 부분입니다. 처음 설치할 때는 아무것도 없겠죠?




테마도 고를 수 있습니다.

저는 안드로이드 스튜디오는 밝은색을 사용하니 IntelliJ는 어두운색으로 선택해봤습니다.




코틀린 학습용이라 그냥 바로 Next 합니다.




여기서도 별다른 설정은 필요없고 Start 합니다.




드디어 시작할 수 있을 것 같습니다.

새 프로젝트를 만들어봅시다!




아직 끝이 아닙니다.

JVM에서 Kotlin 학습을 위한 환경을 세팅해야 합니다.

기존 안드로이드나 자발 개발환경이 있었다면 jdk도 이미 있을겁니다.

보통 다음 경로에 있습니다.

C:\Program Files\Java\jdk버전

jdk버전 폴더를 선택해주시면 됩니다.



안드로이드 스튜디오와 유사하지 않나요?

초기에는 Alt + 1로 새 프로젝트 생성이 됩니다.

혹은 File > New > Project




 이제 다음과 같이 코드를 입력하고 실행을 하면 'Hello Kotlin'이 정상적으로 출력됩니다. 코틀린의 main 함수의 형태도 눈여겨 볼만하네요. (Hello World는 식상하여 Hello Kotlin)


fun main(args: Array<String>) {

    println("Hello Kotlin")

}



함수(function)


기본 형태 (Basic syntax)


 Java 함수의 기본 형태

 void 함수명(변수) {

 }

 or

 리턴타입 함수명(변수) {

  return 값;

 }


 코틀린 함수의 기본 형태

 fun 함수명(변수): Unit {

 }

 or

 fun 함수명(변수): 리턴타입 {

  return 값

 }


 코틀린에서는 return 하지 않는 함수(void 함수)에 선언한 리턴타입 Unit은 생략이 가능합니다.


 리턴타입을 가지는 기본 형태를 그대로 활용하여 두 수의 합을 리턴하는 간단한 예제를 하나 만들 수 있습니다.


 Java

 int sum(int a, int b) {

  return a + b;

 }


 코틀린

fun sum(a: Int, b: Int): Int {

  return a + b

 }


 이때 코틀린의 코드는 좀 더 생략이 가능합니다. 


 먼저 리턴값이 a + b 인데 이때 함수에서 return을 생략하고 다음과 같이 사용할 수 있습니다.


fun sum(a: Int, b: Int): Int = a + b;


 이때 a + b의 타입이 Int인 것을 유추할 수 있기 때문에 리턴타입도 생략이 가능합니다.

fun sum(a: Int, b: Int) = a + b;



멤버 함수 (Member function)


 멤버 함수는 클래스(or object) 내에 정의된 함수입니다.


 class Date() {

   fun getDate() {

    println("2017/12/12")

  }

 }


 getDate()가 Date 클래스 내에 정의된 멤버 함수입니다. 멤버 함수는 다음과 같이 .(dot) 을 이용하여 호출할 수 있습니다.


 Date().getDate()



+ Recent posts