API를 사용하면서 API에 기능이나 프로퍼티을 추가하고 싶은 경우가 있었나요?
이 문제를 해결하기 위해 클래스의 인스턴스를 받아들이는 함수를 클래스에서 상속하거나 새로 만들 수 있을 것입니다. Java 프로그래밍 언어에서는 보통 Utils 클래스로 이 문제를 해결하지만, 자동 완성으로 나타나지 않아 찾기 어렵고 사용하기에 덜 직관적입니다. 위의 두 가지 방법 모두 해결책은 되겠지만, 코드가 쉽지도 않고 가독성도 떨어집니다.
그런데 고맙게도, Kotlin이 확장 함수와 프로퍼티으로 구조의 손길을 내밉니다. 확장 함수와 프로퍼티을 사용하면 클래스를 받아들이는 함수를 상속하거나 새로 만들 필요 없이 클래스에 기능을 추가할 수 있습니다.
Java 프로그래밍 언어류와 달리 확장 함수는 Android Studio의 자동 완성에서 보입니다. 확장 함수를 타사 라이브러리, Android SDK 또는 사용자 정의 클래스와 함께 사용할 수 있습니다.
확장 함수로 코드 가독성을 향상하는 방법을 알아보려면 이 글을 계속 읽어보세요!
확장 함수 — 사용법
이름, 품종, 나이가 있는 Dog이라 불리는 클래스가 있다고 가정 해봅시다.
유기견 입양 기관에서 입양에 관심을 보이는 사람이 있을 때 개의 정보를 인쇄하는 기능을 갖도록 Dog 클래스를 확장하고 싶을 수 있을 것입니다. 이를 위해 일반 함수처럼 설정되는 extension function를 구현합니다.
그런데 딱 한 가지 예외가 있는데요, 바로 함수 이름과 중간에 있는 점 앞에 확장하려는 클래스를 포함하는 경우입니다. 함수 구현에서 이 확장 함수가 적용되는 객체인 리시버(receiver) 객체를 참조할 수 있고 함수 블록 내에서 리시버 클래스의 모든 멤버에 액세스할 수 있습니다.
Dog 클래스에서 다른 함수를 호출하는 것과 똑같이 printDogInformation()을 호출할 수 있습니다.
Java 코드에서 확장 함수 호출하기
확장 함수는 확장하려는 클래스에 속하지 않으므로, Java 프로그래밍 언어에서 확장 함수를 호출하려고 할 때 그 클래스의 다른 메서드 중에서 함수를 찾지는 않을 것입니다. 이후에 살펴보겠지만, 확장 함수는 정의한 파일의 정적 메서드로 디컴파일하고 확장하려는 클래스의 인스턴스를 매개변수로 받습니다. 이는 Java에서 printDogInformation() 확장 함수를 호출하는 것과 같은 형태입니다.
Nullable 형식의 확장 함수
Nullable 형식에서도 확장 함수를 사용할 수 있습니다. 확장 함수를 호출하기 전에 null 확인을 수행하는 대신, nullable 형식에 대해 확장 함수를 만들어 null 확인을 함수 구현에 포함할 수 있습니다. 이는 printInformation()이 nullable 형식을 사용하는 것처럼 보입니다.
여기서 알 수 있듯이, printInformation()을 호출하기 전에 null 확인을 할 필요가 없습니다.
확장 프로퍼티 — 사용법
입양 기관에서는 개가 입양하기에 충분할 만큼 나이를 먹었는지 알고 싶어할 수도 있을 것입니다. 이를 위해, 개의 나이가 한 살 이상인지 확인하는 isReadyToAdopt라는 확장 프로퍼티을 구현합니다.
Dog 클래스에서 다른 프로퍼티을 호출하는 것과 같은 방식으로 이 확장 프로퍼티을 호출할 수 있습니다.
확장 함수 재정의
확장 함수로는 기존 멤버 함수를 재정의할 수 없습니다. 기존 멤버 함수와 동일한 서명을 가진 확장 함수를 정의할 경우, 호출되는 함수가 변수에 저장된 값의 런타임 형식이 아니라 그 변수의 선언된 정적 형식에 따라 결정되므로 그 멤버 함수가 항상 호출됩니다. 예를 들어, String 상에서 toUppercase()를 확장할 수는 없지만 convertToUppercase()를 확장할 수는 있습니다.
유사하게, 자신이 소유하고 있지 않은 형식을 확장하고 라이브러리 소유자가 자신의 확장 함수와 동일한 서명을 가진 라이브러리에 메서드를 추가하는 경우 자신이 정의한 확장 함수 대신 라이브러리 확장 함수가 호출됩니다. 여기서 얻게 될 유일한 정보는 자신이 정의한 확장 함수가 사용되지 않는 메서드로 처리된다는 점입니다.
내부적인 작동 원리
Android Studio에서 printDogInformation()을 받아서 Tools/Kotlin/Show Kotlin Bytecode로 이동한 후 Decompile 버튼을 누르면 디컴파일할 수 있습니다. printDogInformation() 메서드를 디컴파일된 코드로 볼 경우 다음 메시지가 표시됩니다.
내부적으로, 확장 함수는 리시버 클래스의 인스턴스를 받는 일반적인 정적 함수일 뿐입니다. 이런 함수는 리시버 클래스에 대한 다른 연결은 없습니다. 그래서 지원 필드 (backing fields)가 없는거죠. 즉, 실제로 클래스에 멤버를 삽입하지 않습니다.
결론
신중하게 사용하는 경우, 확장 함수는 유용한 도구로 활용할 수 있습니다. 코드를 더 직관적이고 읽기 쉽게 만들기 위해 확장 함수를 사용할 때 아래의 팁을 기억하세요.
명심할 점:
확장 함수는 정적으로 처리됩니다.
멤버 함수가 항상 이깁니다.
개는 사지 말고 입양하세요! :)
즐거운 코딩 되세요!