Android

[Android] @JvmOverloads란?

참깨빵위에참깨빵_ 2025. 3. 3. 23:35
728x90
반응형

https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.jvm/-jvm-overloads/

JvmOverloads

Instructs the Kotlin compiler to generate overloads for this function that substitute default parameter values. If a method has N parameters and M of which have default values, M overloads are generated: the first one takes N-1 parameters (all but the last

kotlinlang.org

이 함수에 대해 기본 매개변수 값을 대체하는 오버로드를 생성하도록 코틀린 컴파일러에 지시한다. 메서드에 n개의 매개변수가 있고 그 중 m개가 기본값이면 첫 번째 오버로드는 기본값을 사용하는 마지막 매개변수를 제외하고 모두 n-1개, 두 번째는 n-2개 등 m개의 매개변수를 사용하는 식으로 생성된다

 
@JvmOverloads 어노테이션은 코틀린 함수나 생성자에서 기본값이 지정된 매개변수가 있을 때 자바 코드에서 호출할 수 있게 여러 오버로드된 메서드를 자동 생성해주는 어노테이션이다.
코틀린에선 기본값이 있는 매개변수를 쓸 수 있지만 자바는 쓸 수 없기 때문에 모든 인자를 명시적으로 전달해야 하고, 자바 호출자에게 여러 오버로드 메서드를 노출하기 위해 이 어노테이션을 써야 한다.
이것은 커스텀 뷰를 만들기 위해 프레임 레이아웃, 리니어 레이아웃 등을 상속한 클래스를 만들 때 사용할 수 있다. 아래는 예시 코드다. xml은 만들지 않았다.
 

class MyButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr)

 
그럼 이건 왜 쓰는 것인가? 위 코드에서 어노테이션을 제거하고 FrameLayout을 상속하게만 수정하면 컴파일 에러가 발생하는데, 에러를 해결하기 위해 팝업을 열면 아래처럼 표시된다.
 

 
매개변수가 1개, 2개, 3개, 4개 있는 생성자 중 하나를 선택할 수 있는데 저 중 하나라도 선택하면 컴파일 에러는 사라진다.
그럼 해결된 건가? 1개, 3개, 4개 중 하나를 추가한 다음, 메인 액티비티 xml에 저 MyButton 클래스를 추가하고 앱을 실행하면 아래 에러가 발생한다. 아래는 매개변수가 4개인 생성자를 추가했을 때 발생하는 에러다.
 

android.view.InflateException: Binary XML file line #23 in com.example.regacyviewpractice:layout/item_my_data: Binary XML file line #23 in com.example.regacyviewpractice:layout/item_my_data: Error inflating class com.example.regacyviewpractice.presentation.MyButton
Caused by: android.view.InflateException: Binary XML file line #23 in com.example.regacyviewpractice:layout/item_my_data: Error inflating class com.example.regacyviewpractice.presentation.MyButton
Caused by: java.lang.NoSuchMethodException: com.example.regacyviewpractice.presentation.MyButton.<init> [class android.content.Context, interface android.util.AttributeSet]

 
에러 내용을 보면 MyButton을 inflate하는 과정에서 뻑난 것 같은데, MyButton 코틀린 코드를 자바 코드로 변환하면 아래처럼 표시되는 걸 볼 수 있다.
 

@Metadata(
   mv = {1, 9, 0},
   k = 1,
   xi = 48,
   d1 = {"\u0000\u001e\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\b\n\u0002\b\u0003\u0018\u00002\u00020\u0001B'\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\b\u0010\u0004\u001a\u0004\u0018\u00010\u0005\u0012\u0006\u0010\u0006\u001a\u00020\u0007\u0012\u0006\u0010\b\u001a\u00020\u0007¢\u0006\u0002\u0010\t¨\u0006\n"},
   d2 = {"Lcom/example/regacyviewpractice/presentation/MyButton;", "Landroid/widget/FrameLayout;", "context", "Landroid/content/Context;", "attrs", "Landroid/util/AttributeSet;", "defStyleAttr", "", "defStyleRes", "(Landroid/content/Context;Landroid/util/AttributeSet;II)V", "app_debug"}
)
public final class MyButton extends FrameLayout {
   public MyButton(@NotNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
      Intrinsics.checkNotNullParameter(context, "context");
      super(context, attrs, defStyleAttr, defStyleRes);
   }
}

 
생성자 시그니처가 Context, AttributeSet, int, int만 있는 걸 볼 수 있다.
잠깐 다른 이야기로 넘어가면, LayoutInflater는 xml 파일을 입력으로 받아서 뷰 객체를 빌드하는 역할을 한다. 커스텀 뷰를 메인 액티비티 xml에 선언한 후 앱을 실행하면 LayoutInflater는 뷰 객체를 빌드하기 위해 MyButton 클래스에서 필요한 생성자를 찾는다. 이 때 찾는 생성자가 없으면 NoSuchMethodException을 일으키고 앱이 다운된다.
 
그래서 LayoutInflater가 MyButton을 인스턴스화하는 과정에서 매개변수 4개짜리 생성자만 정의했으니 필요한 생성자를 찾지 못해 NoSuchMethodException을 일으킨 것이라고 볼 수 있다.
에러를 해결하려면 아래처럼 부 생성자들을 작성해서 해결할 수 있다.
 

class MyButton: FrameLayout {
    constructor(context: Context) : this(context, null, 0)
    constructor(context: Context, attr: AttributeSet?) : this(context, attr, 0)
    constructor(context: Context, attr: AttributeSet?, defStyleAttr: Int) : super(context, attr, defStyleAttr)
}

 
 
찾아보니 위와 같이 생성자 3개를 정의하는 게 관례인 듯하다.
그러나 커스텀 뷰를 만들 때마다 저 생성자들을 전부 정의해야 하는 건가? 좀 더 편하게 같은 역할을 하는 코드를 짤 수는 없는가?
그래서 @JvmOverloads 어노테이션이 사용된다. 생성자 3개를 생성하지 않아도, 처음에 본 코드로 수정하면 문제 없이 커스텀 뷰를 사용할 수 있게 된다.
 

class MyButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr)

 
아래는 또 다른 코틀린 공식문서에서 @JvmOverloads를 설명하는 부분이다.
 
https://kotlinlang.org/docs/java-to-kotlin-interop.html#overloads-generation

Calling Kotlin from Java | Kotlin

kotlinlang.org

일반적으로 기본 매개변수 값을 써서 코틀린 함수를 만들면 모든 매개변수가 포함된 전체 서명으로만 자바에 표시된다. 자바 호출자에게 여러 오버로드를 노출하려면 @JvmOverloads를 쓰면 된다. 이 어노테이션은 생성자, 정적 메서드 등에도 사용할 수 있다. 인터페이스에 정의된 메서드를 포함한 추상 메서드에는 사용할 수 없다
기본값이 있는 모든 매개변수에 대해 이 매개변수와 매개변수 목록의 오른쪽에 있는 모든 매개변수가 제거된 추가 오버로드가 하나씩 생성된다
부 생성자에서 말한 대로 모든 생성자 매개변수에 대한 기본값이 있는 경우 매개변수가 없는 공용 생성자가 생성된다. 이는 @JvmOverloads가 지정되지 않아도 작동한다

 
그러나 @JvmOverloads가 항상 만능인 건 아니다. defStyleAttr의 값에 확장하려는 뷰의 기본값이 아닌 @JvmOverloads의 생성자에 있는 기본값이 들어갈 수 있다. 이 때는 부 생성자들을 작성하고 그 뒤에 전부 super를 명시하면 된다.
 

class MyButton: FrameLayout {
    constructor(context: Context): super(context)
    constructor(context: Context, attrs: AttributeSet?): super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): super(context, attrs, defStyleAttr)
}

 
또한 API 21(롤리팝) 미만의 구형 기기에선 파라미터가 4개인 생성자를 호출할 수 없어서 파라미터가 3개인 생성자만 호출된다. minSdk가 21 미만인 경우는 흔하지 않겠지만 해당하는 경우 아래처럼 할 수 있다.
 

open class RadioButton : TextView, Checkable {
    @JvmOverloads
    constructor(context: Context,
                attrs: AttributeSet? = null,
                defStyleAttr: Int = android.R.attr.radioButtonStyle,
                defStyleRes: Int = R.style.guide_RadioButton)
            : super(context, attrs, defStyleAttr, defStyleRes) {
        initRadioButton(attrs, defStyleAttr, defStyleRes)
    }
    fun initRadioButton(attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) {
        // ..
    }
}

 
 
참고한 사이트)
 
https://stackoverflow.com/questions/10034430/android-using-layoutinflater-inflate-to-pass-custom-parameters-to-a-constructor

Android: using LayoutInflater.inflate to pass custom parameters to a constructor

This has been bothering me for a while, and none of my searching has yielded results. If I have a custom GUI element, I can use a LayoutInflater to inflate it as I would a normal component. The inf...

stackoverflow.com

 
https://damon-911.tistory.com/entry/Android-JvmOverloads

[Android] @JvmOverloads

View의 생성자 View의 생성자에는 총 4가지가 있습니다. 종류 설명 View(Context context) 코드 상에서 View 객체를 생성할때 사용하는 생성자 View(Context context, AttributeSet attrs) XML로부터 View를 객체를 생성(i

damon-911.tistory.com

 
https://medium.com/@Zielony/guide-to-android-custom-views-constructors-df47476e334c

Guide to Android custom views: constructors

I’ve been designing, writing and publishing Android custom views for the past 5 years now. I guess it’s about time to sum it up and share…

medium.com

 

반응형