2020. 9. 7. 09:33ㆍProgramming/Kotlin
반복 구문의 발생
Java, Kotlin에서 JSONObject을 사용하여 JSON으로 작성된 데이터 구조를 파싱할 때, getInt, getString 등을 사용하는 경우가 많았다. 이 메서드는 문자열 키를 받아서 매칭되는 값을 반환하는데, 매칭되는 값이 없을 경우 Exception을 던지게 되어있다. Exception이 발생한 경우에는 null을 반환하도록 코드를 작성하면, 아래와 같다.
try {
JSONObject.getInt("key")
} catch (JSONException e) {
e.printStackTrace()
null
}
try {
JSONObject.getString("key")
} catch (JSONException e) {
e.printStackTrace()
null
}
위에서 JSONObject.getString(), JSONObject.getInt()를 호출하는 구문 중 try, catch문은 타입을 제외하면 동일한 코드이다. 그렇다면 이를 어떻게 하나의 코드로 처리할 수 있을까? 동일한 함수에서 다양한 타입을 처리하기 위해 제네릭을 사용한다는 것은, 익히 알려진 사실이다. 제네릭을 사용하여 코드를 수정해보자.
제네릭의 사용
제네릭을 사용하는 방법은 간단하다. fun 키워드 뒤에 <T>
처럼, 타입으로 사용할 대문자를 붙여주기만 하면 된다.
클래스 리플렉션의 사용
The most basic reflection feature is getting the runtime reference to a Kotlin class. To obtain the reference to a statically known Kotlin class, you can use the class literal syntax:
val c = MyClass::class
The reference is a value of type KClass.
Note that a Kotlin class reference is not the same as a Java class reference. To obtain a Java class reference, use the .java property on a KClass instance.
공식 문서에 나와있는 클래스 리플렉션에 대한 정의이다. 간단하게 요약하면 코틀린 클래스의 런타임 레퍼런스를 값으로 가져온다는 얘긴데, 보다 자세한 내용을 확인하려면 공식문서를 참조하도록 하자.
private fun <T> JSONObject.tryGet(key: String, type: Class<T>): T? = try {
when (type) {
Int::class -> getInt(key) as T
Boolean::class -> getBoolean(key) as T
String::class -> getString(key) as T
JSONArray::class -> getJSONArray(key) as T
Double::class -> getDouble(key) as T
else -> null
}
} catch (e: Exception) {
null
}
여기서 JSONObject.tryGet에서는 함수의 바디에서 T의 타입을 사용하기 위해, 명시적으로 type:Class<T>
라는 파라메터를 전달하고 있다. 그 뒤, 리플렉션을 사용하여 Int
, Boolean
, String
, JSONArray
, Double
의 타입과 type:Class<T>
를 비교하여 분기처리를 하고 있다.
reified의 사용
reified에 대한 자세한 내용은 (reified type parameter)[https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters]을 참고하도록 하자.
위에서 제네릭 함수 tryGet을 작성할 때, 런타임에서는 T의 타입을 접근할 수 없기 때문에 type:Class<T>
와 같이 명시적으로 타입을 전달했다. (컴파일 타임에는 존재하지만, 런타임에는 Type erasure때문에 접근할 수 없기 때문이다. 이는 코틀린에서 reified는 왜 쓸까?
, Jeremy님의 블로그를 참고하도록 하자.)
reified 키워드를 사용하여 inline 함수를 만들면, type:Class<T>
를 넘겨주지 않고도 T의 타입에 접근이 가능하다. 코드를 재작성해보자.
private inline fun <reified T> JSONObject.tryGet(key: String): T? = try {
when (T::class) {
Int::class -> getInt(key) as T
Boolean::class -> getBoolean(key) as T
String::class -> getString(key) as T
JSONArray::class -> getJSONArray(key) as T
Double::class -> getDouble(key) as T
else -> null
}
} catch (e: Exception) {
null
}
이제 함수 내부에서 T의 타입에 접근할 수 있기 때문에, T로 타입캐스팅 할 때도 경고를 띄워주지 않으며 코드도 좀 더 간결해졌다.
결론
- 제네릭스와 리플렉션을 사용하면, 타입에 따른 분기처리를 함수 내에서 가능하다. 타입 상한을 지정해서 간단하게 처리할 수도 있지만, JSONObject의 get 메서드들을 하나로 묶고 싶을 때는 이 방법이 간단하다.
- reified 키워드를 사용하면 명시적으로 제네릭스의 타입을 넘겨주지 않아도, 함수 내부에서 제네릭스의 타입에 접근이 가능하다.
'Programming > Kotlin' 카테고리의 다른 글
코틀린으로 배우는 함수형 프로그래밍 연습문제 #3 [푸는 중] (1) | 2020.01.19 |
---|---|
Backing Field와 Backing Properties (0) | 2019.02.12 |
Null Safety와 엘비스(Elvis) 오퍼레이터 (0) | 2019.02.05 |
When Expression (0) | 2019.02.03 |