Developing Myself Everyday
article thumbnail

사진: UnsplashElisa Photography

 

 

현재 프로젝트를 진행하면서 함수 참조를 사용해야 할까??라는 궁금증이 생겼습니다.

 

리플렉션은 최대한 사용을 지양해야 한다는 생각을 가지고 있었는데, 참고하고 있는 다른 Github에서 함수를 함수 참조로 사용하는 것을 보고, 내가 가지고 있던 생각이 잘못되었고 생각했습니다.

 

그래서 이번 게시글에서 코틀린의 리플렉션에 대해 자세히 알아보고, 함수 참조와 무엇이 다른가에 대해 알아보려고 합니다.

 

 

참고: 이 게시글에서는 각각에 대한 사용법을 다루고 있지는 않습니다.


 

 

 

 

리플렉션(Reflection)이란?


리플렉션은 프로그램이 런타임에서 자체 소스 코드를 내부 검사해, 클래스의 정보를 조작할 수 있도록 하는 기능입니다.

 

리플렉션에 대해 이해하기 위해서는 JVM의 작동방식에 대해 이해할 필요가 있습니다. JVM은 클래스를 로드 시 클래스 로더를 사용해 클래스에 대한 정보를 즉 Metadata Metaspace 영역에 저장합니다.

 

리플렉션은 로딩된 클래스의 Metadata를 활용하여 클래스의 정보를 동적을 조작하는 기능입니다.

메타데이터란?
자바에서 메타데이터는 클래스, 메서드, 필드 등과 같은 프로그램 요소에 대한 정보를 포함하는 데이터를 의미합니다. 간단히 말하자면 데이터에 대한 데이터입니다.

 

 

 

코틀린의 리플렉션


코틀린에서 리플렉션은 `kotlin.reflect` 패키지에 정의되어 있습니다. 

 

아래의 그림은 이러한 클래스들의 계층을 나타냅니다.

 

 

위의 그림에서 알 수 있는 것은 모든 계층의 타입이 전부 `K` 로 시작한다는 것입니다. 이는 이러한 타입들이코틀린 리플렉션의 일부이며, 자바 리플렉션과 구분하는 역할을 합니다. 

 

 

 

애노테이션(Annotation)

응?? 애노테이션이 왜 나오지? 라고 생각하셨다면 저와 같이 리플렉션에 대해서 잘 모르셨던 것 같습니다.

 

사실 리플렉션으로 메타데이터를 가져오기 위해서는 애노테이션이 필요합니다.

 

 

그럼 애노테이션은 도대체 뭘까요??

 

애노테이션은 바로 메타데이터를 나타내는 태그입니다. 그렇기 때문에 애노테이션 혼자서는 코드의 동작에 직접적인 영향을 미칠 수가 없습니다.

 

애노테이션은 런타임에 JVM에 의해 유지되고, 이를 리플렉션을 통해 읽을 수 있게 합니다.

 

 

`KAnnotatedElement`

계층 구조의 맨 위에는 `KAnntatedElement` 인터페이스가 있습니다. Element는 클래스, 함수 및 프로퍼티를 말하는데, 이러한 Element들의 애노테이션을 나타내고 해당 애노테이션을 얻을 수 있게 해줍니다.

public interface KAnnotatedElement {
    public val annotations: List<Annotation>
}

 

만약 클래스나 함수를 리플렉션하는 경우, 해당 클래스나 함수의 Element와 그 Element들의 애노테이션을 가지고 있을 수 있게 됩니다.

 

그 정보를 `KAnntatedElement` 인터페이스를 통해 확인할 수 있습니다. 

 

 

 

 

함수 참조


함수를 참조하기 위해서는 더블 콜론(::)과 함수 이름을 사용해야 합니다.

import kotlin.reflect.KFunction2

fun add(x: Int, y: Int): Int = x + y

fun main() {
    val addF : KFunction2<Int, Int, Int> = ::add
    println(addF(1, 2)) // 3
}

 

 

함수 참조로 얻을 수 있는 타입은 KFuctionN이 됩니다. N은 파라미터의 개수를 말하고 마지막 파라미터는 리턴 타입이 됩니다.

 

모든 KFuctionN들은 `KFuction` 인터페이스를 구현하고 있습니다.

 

위의 계층 그림에서 함수 참조와 관련된 부분만 다시 보겠습니다.

 

 

가장 위에는 우리가 알아보았던 `KAnntatedElement` 인터페이스가 있습니다.

 

`KFuction` 인터페이스가 이것들을 가지고 있습니다.

 

 

함수 참조는 그럼 리플렉션인가?

그럼 함수 참조는 리플렉션을 사용할까요?? 정답은 아니요 입니다.

 

코틀린에서 함수 참조를 사용하면 컴파일러는 이를 람다 표현식으로 바꿉니다. 우리는 그래서 함수 참조를 리플렉션을 걱정하지 않고 편의성을 위해 사용할 수 있습니다.

class Complex(val real: Double, val imaginary: Double) {
    fun plus(number: Number): Complex = Complex(
        real = real + number.toDouble(),
        imaginary = imaginary
    )
}

fun Complex?.isNullOrZero(): Boolean =
    this == null ||
            (this.real == 0.0 && this.imaginary == 0.0)
            
fun nonZeroDoubled(numbers: List<Complex?>): List<Complex?> =
    numbers
        .filterNot(Complex?::isNullOrZero)
        .filterNotNull()
        .map(Complex::double)

 

 

 

위의 함수 참조는 아래와 같이 바뀝니다.

fun nonZeroDoubled(numbers: List<Complex?>): List<Complex?> =
    numbers
        .filterNot { it.isNullOrZero() }
        .filterNotNull()
        .map { it.double() }

 

 

 

이것이 가능한 이유는 바로 KFuctionN이 구체적인 함수 유형을 구현하기 때문입니다. 위의 예시에서 사용했던 유형은 KFunction2<Int, Int, Int>이고 여기서 코틀린 컴파일러는 (Int, Int) -> Int로 이를 구현할 수 있습니다.

 

 

 

마무리하며


함수 참조는 결국 리플렉션을 사용하지 않습니다. 그렇기 때문에 각자의 스타일이나 편의성에 있어서 함수 참조를 사용하는데 두려워하지 않아도 되겠다라는 생각이 듭니다.

 

 

 

 

Reference

 

Kotlin Reflection: Method and property references

The general hierarchy of Kotlin reference classes, and details about method and property references.

kt.academy

 

Annotation과 Reflection, 그리고 코드 속의 MetaData

Annotation과 Reflection, 그리고 코드 속의 MetaDataQ: Annotation 이란 무엇인가?A: Annotation ...

blog.naver.com

 

profile

Developing Myself Everyday

@배준형

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!