•
리플렉션은 프로그램의 구조를 런타임에 반영할 수 있는 언어 및 라이브러리 기능의 집합이다.
•
함수와 속성은 Kotlin에서 일급 객체이며 런타임에 속성이나 함수의 이름 또는 타입을 알 수 있는 기능은 함수형 또는 반응형 스타일을 사용할 때 필수적이다.
Kotlin/JS는 리플렉션 기능에 대한 제한된 지원을 제공한다.
JVM 종속성
•
JVM 플랫폼에서 코틀린 컴파일러 배포판에는 리플렉션 기능을 사용하기 위해 필요한 런타임 구성 요소인 kotlin-relfect.jar 가 별도로 포함되어 있다.
•
이는 리플렉션 기능을 사용하지 않는 어플리케이션의 런타임 라이브러리 크기를 줄이기 위해서이다.
//Gradle
dependencies {
implementation(kotlin("reflect"))
}
//Maven
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
</dependencies>
Kotlin
복사
•
Gradle 또는 Maven 프로젝트에서 리플렉션을 사용하려면 kotlin-reflect에 대한 종속성을 추가하라.
•
Gradle 또는 Maven 프로젝트를 사용하지 않는 경우 프로젝트의 클래스 경로에 kotlin-reflect.jar가 포함되어 있는지 확인하라.
클래스 참조
•
가장 기본적인 리플렉션 기능은 코틀린 클래스에 대한 런타임 참조를 얻는 것이다.
•
정적으로 알려진 코틀린 클래스에 대한 참조를 얻으려면 클래스 리터럴 구문을 사용할 수 있다.
val c = MyClass::class
Kotlin
복사
•
이 참조는 KClass 타입 값이다.
•
JVM에서는 코틀린 클래스 참조가 자바 클래스 참조와 다르다.
•
자바 클래스 참조를 얻으려면 KClass 인스턴스에서 .java 속성을 사용하라.
바운드 클래스 참조
•
특정 객체의 클래스 참조를 얻으려면 동일한 ::class 구문을 사용하여 객체를 수신자로 사용할 수 있다.
val widget: Widget = ...
assert(widget is GoodWidget) { "Bad widget: ${widget::class.qualifiedName}" }
Kotlin
복사
•
위 코드는 GoodWidget 또는 BadWidget 과 같은 객체의 정확한 클래스 참조를 얻는다.
호출 가능한 참조
•
함수, 속성 및 생성자에 대한 참조는 함수 타입의 인스턴스로 호출하거나 사용할 수 있다.
•
모든 호출 가능한 참조의 공통 슈퍼타입은 KCallable<out R> 이며 여기서 R은 반환 값 타입이다.
•
이는 속성 타입이자 생성자에 대한 생성 타입이다.
함수 참조
fun isOdd(x: Int) = x % 2 != 0
Kotlin
복사
•
위와 같이 선언된 명명된 함수를 가진 경우 직접 호출할 수 있다.
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd))
Kotlin
복사
•
:: 연산자를 사용하면 함수 타입 값으로 사용하여 다른 함수에 전달할 수 있다.
•
::isOdd 는 함수 타입 (Int) → Boolean 의 값이다.
•
함수 참조는 매개변수 수에 따라 KFunction<out R> 의 하위 타입 중 하나에 속한다.
fun isOdd(x: Int) = x % 2 != 0
fun isOdd(s: String) = s == "brillig" || s == "slithy" || s == "tove"
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // refers to isOdd(x: Int)
Kotlin
복사
•
오브로드된 함수와 함께 :: 를 사용할 수 있으며 문맥에서 타입이 추론 가능해야 한다.
val predicate: (String) -> Boolean = ::isOdd // refers to isOdd(x: String)
Kotlin
복사
•
필요한 문맥을 제공하여 변수를 명시적으로 지정할 수도 있다.
val isEmptyStringList: List<String>.() -> Boolean = List<String>::isEmpty
Kotlin
복사
•
클래스의 멤버나 확장 함수에 대해 사용하려면 String::toCharArray 처럼 한정해야 한다.
•
확장 함수 타입을 가진 변수를 초기화할 때 추론된 함수 타입에는 수신자가 없지만 수신자 객체를 받는 추가 매개변수가 포함된다.
•
수신자를 가진 함수 타입을 얻으려면 타입을 명시적으로 지정하라.
함수 조합
fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
return { x -> f(g(x)) }
}
fun length(s: String) = s.length
val oddLength = compose(::isOdd, ::length)
val strings = listOf("a", "ab", "abc")
println(strings.filter(oddLength))
Kotlin
복사
•
compose 함수는 전달된 두 함수의 조합을 반환한다.
속성 참조
val x = 1
fun main() {
println(::x.get())
println(::x.name)
}
Kotlin
복사
•
Kotlin에서 속성을 일급 객체로 접근하려면 :: 연산자를 사용하라.
•
::x 표현식은 KProperty0<Int> 타입 속성 객체로 평가된다.
•
get() 메서드를 사용하여 값을 읽거나 name 속성을 사용하여 속성 이름을 검색할 수 있다.
var y = 1
fun main() {
::y.set(2)
println(y)
}
Kotlin
복사
•
::y 표현식은 set() 메서드를 가진 KMutableProperty0<Int> 타입을 반환한다.
val strs = listOf("a", "bc", "def")
println(strs.map(String::length))
Kotlin
복사
•
속성 참조는 단일 제네릭 매개변수가 필요한 함수로 사용될 수 있다.
class A(val p: Int)
val prop = A::p
println(prop.get(A(1)))
Kotlin
복사
•
클래스의 멤버인 속성에 접근하려면 위처럼 한정하라.
val String.lastChar: Char
get() = this[length - 1]
fun main() {
println(String::lastChar.get("abc"))
}
Kotlin
복사
•
확장 속성에 대한 예시는 위와 같다.
자바 리플렉션과 상호 운용성
•
JVM 플랫폼에서 표준 라이브러리는 자바 리플렉션 객체와의 매핑을 제공하는 리플렉션 클래스의 확장을 포함한다.
import kotlin.reflect.jvm.*
class A(val p: Int)
fun main() {
println(A::p.javaGetter) // prints "public final int A.getP()"
println(A::p.javaField) // prints "private final int A.p"
}
Kotlin
복사
•
코틀린 속성의 게터로 작동하는 자바 메서드 또는 백킹 필드를 찾으려면 .javaGetter 와 .javaField 확장 속성을 사용하라.
fun getKClass(o: Any): KClass<Any> = o.javaClass.kotlin
Kotlin
복사
•
자바 클래스에 해당하는 코틀린 클래스를 얻으려면 .kotlin 확장 속성을 사용하라.
생성자 참조
•
생성자는 메서드와 속성과 마찬가지로 참조할 수 있다.
•
생성자와 같은 매개변수를 받는 함수 타입 객체가 필요할 때 사용할 수 있다.
class Foo
fun function(factory: () -> Foo) {
val x: Foo = factory()
}
function(::Foo)
Kotlin
복사
•
생성자는 :: 연산자를 사용하여 클래스 이름을 추가하여 참조한다.
•
생성자에 대한 호출 참조는 매개변수 수에 따라 KFunction<out R> 의 하위 타입으로 분류된다.
바운드 함수 및 속성 참조
•
특정 객체의 인스턴스 메서드를 참조할 수 있다.
val numberRegex = "\\d+".toRegex()
println(numberRegex.matches("29"))
val isNumber = numberRegex::matches
println(isNumber("29"))
val strings = listOf("abc", "124", "a70")
println(strings.filter(numberRegex::matches))
Kotlin
복사
•
matches 메서드를 직접 호출하는 대신 그 참조를 사용한다.
•
이러한 참조는 수신자에 바인딩된다.
•
이 참조는 위 예시처럼 직접 호출할 수 있거나 함수 타입 표현식이 필요한 곳에서 사용할 수 있다.
val isNumber: (CharSequence) -> Boolean = numberRegex::matches
val matches: (Regex, CharSequence) -> Boolean = Regex::matches
Kotlin
복사
•
바운드 참조와 언바운드 참조의 타입을 비교하면 바운드 호출 참조는 수신자가 부착되어 있기 때문에 수신자의 타입은 더 이상 매개변수가 아니다.
val prop = "abc"::length
println(prop.get())
Kotlin
복사
•
속성 참조도 바인딩 할 수 있다.
•
이 경우 수신자를 명시할 필요는 없다. (this::foo 와 ::foo 는 동일하게 동작)
바운드 생성자 참조
class Outer {
inner class Inner
}
val o = Outer()
val boundInnerCtor = o::Inner
Kotlin
복사
•
내부 클래스의 생성자에 대한 바운드 호출 참조는 외부 클래스의 인스턴스를 제공함으로써 얻을 수 있다.