공상하는 개발자

[코틀린] 코틀린 익숙해지기 4탄(제네릭, 고차함수, 애노테이션 등) 본문

개발/코틀린

[코틀린] 코틀린 익숙해지기 4탄(제네릭, 고차함수, 애노테이션 등)

공상과학소설 2020. 3. 22. 19:06
반응형

제네릭 (Generics)

※ 리스트를 다루는 함수를 작성한다면 어떤 특정 타입을 저장하는 리스트뿐 아니라 모든 리스트를 다룰 수 있는 함수를 원할 것이다.

 → 이럴 때 제네릭 함수를 작성해야 한다.

fun <T> List<T>.slice(indices: IntRange): List<T> {
    return this.subList(indices.first, indices.last)
}

제네릭 함수인 slice는 T를 타입 파라미터로 받는다. 타입 파라미터가 수신 객체와 반환 타입에 쓰인다.

 

제네릭 클래스 선언

interface List<T> {	// List 인터페이스에 T라는 타입 파라미터를 정의한다.
    operator fun get(index: Int): T	// 인터페이스 안에서 T를 일반 타입처럼 사용할 수 있다.
}

※ 제네릭 클래스를 확장하는 클래스를 정의하려면 기반 타입의 제네릭 파라미터에 대해 타입 인자를 지정해야 한다. 이때 구체적인 타입을 넘길 수도 있고, 타입 파라미터로 받은 타입을 넘길 수도 있다. 밑의 예제를 보자.

 

class StringList:List<String>{
    override fun get(index: Int): String {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
}
class ArrayList<T>:List<T>{
    override fun get(index: Int): T {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
}

 

-> StringList 라는 클래스는 String이라는 구체적인 타입을 넘겨주었고, ArrayList<T> 클래스는 타입 파라미터로 받은 타입을 넘겨주었다.

 

실체화한 타입 파라미터를 사용한 함수 선언

▶ 코틀린 제네릭 타입의 타입 인자 정보는 실행 시점에 지워진다. 따라서 제네릭 클래스의 인스턴스가 있어도 그 인스턴스를 만들 때 사용한 타입 인자를 알아낼 수 없다. 제네릭 함수의 타입 인자도 마찬가지다. 제네릭 함수가 호출돼도 그 함수의 본문에서는 호출 시 쓰인 타입 인자를 알 수 없다.

fun <T> isA(value: Any) {
    value is T
}

-> T를 알 수 없다는 컴파일 에러를 일으킨다.

 

● 이는 일반적으로 사실이다. 이런 제약을 피할 수 있는 경우가 하나 있다. 인라인 함수의 타입 파라미터는 실체화되므로 실행 시점에 인라인 함수의 타입 인자를 알 수 있다. 어떤 함수에 inline 키워드를 붙이면 컴파일러는 그 함수를 호출한 식을 모두 함수 본문으로 바꾼다.

 

 

※ 방금 살펴본 isA 함수를 인라인 함수로 만들고 타입 파라미터를 reified로 지정하면 value의 타입이 T의 인스턴스인지를 실행 시점에 검사할 수 있다.

inline fun <reified T> isA(value: Any) {
    value is T
}

 

실체화한 타입 파라미터로 클래스 참조 대신

★ 안드로이드의 startActivity 함수 간단하게 만들기

inline fun <reified T : Activity> Context.startActivity() {
    val intent = Intent(this, T::class.java)
    startActivity(intent)
}

startActivity<SignupActivity>()

 

이런식으로 간단하게 만들 수 있다.


고차 함수

→ 고차 함수란 함수의 인자나, 반환 값이 lambda인 경우를 말한다.

 

 형식 : (인자1:타입, 인자2:타입....) -> 반환타입

              인자는 괄호로 묶어야 한다.

 

Unit은 반환 값이 없음을 나타낸다.(자바의 Void와 같은 개념)

 

고차 함수의 선언

getMovieData(number: Int, success: (Int) -> Unit, fail: (String) -> Unit) {
    when (number) {
        1 -> {
            success(3)
        }
        else -> fail("ABC")
    }

함수 설명 : number가 1이라면 success(3)을 호출하고 1이 아니라면 fail("ABC")를 호출한다.

 

fun main() {

    getMovieData(number = 19, success = {
        println("숫자는 $it 입니다.")
    }, fail = {
        println(it)
    })
    // ABC 출력...
}

number 가 19 이므로 success는 호출되지 않고 fail이 호출된다. 그래서 "ABC"가 호출된다.

만약 number가 1이었다면 success(3)이 호출되어 "숫자는 3입니다."가 호출된다.

 


애노테이션과 리플렉션

※ 어떤 함수를 호출하려면 그 함수가 정의된 클래스의 이름과 함수 이름, 파라미터 이름 등을 알아야만 했다. 애노테이션 리플렉션을 사용하면 그런 제약을 벗어나서 미리 알지 못하는 임의의 클래스를 다룰 수 있다. 

 

 애노테이션 : 애노테이션을 사용하면 라이브러리가 요구하는 의미를 클래스에게 부여할 수 있다.

 리플렉션 : 리플렉션을 사용하면 실행 시점에 컴파일러 내부 구조를 분석할 수 있다.

 

애노테이션을 활용한 JSON 직렬화 제어

→ 애노테이션을 사용하는 고전적인 예제로 객체 직렬화 제어를 들 수 있다. 직렬화는 객체를 저장장치에 저장하거나 네트워크를 통해 전송하기 위해 텍스트나 이진 형식으로 변환하는 것이다. 반대 과정인 역직렬화는 텍스트나 이진 형식으로 저장된 데이터로부터 원래의 객체를 만들어낸다. 직렬화에 자주 쓰이는 형식에 JSON 이 있다. 자바와 JSON을 변환할 때 자주 쓰이는 라이브러리로는 잭슨과 지슨이 있다.

 

 

 

반응형
Comments