不要老是相信 @JvmOverloads

原文:mrw.so/5ghhcs 譯者:依然範特稀西java

在使用Kotlin編程時,一般能夠經過@JvmOverloads註解把多個構造函數合併成一個構造函數,尤爲是當經過繼承Android View 來自定義View 的時候。大多數狀況下,這樣寫是沒問題的,可是有時候它又會出現一些意想不到的問題。android

讓咱們看一下@JvmOverloads的定義:git

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 one that takes a default value), the second takes N-2 parameters, and so on.github

什麼意思呢?解釋一下:編程

告訴Kotlin編譯器爲@JvmOverloads註解的方法替換默認參數生成重載函數 若是一個方法有N個參數且其中M個有默認值,則會產生M個重載:第一個取N-1個參數(除了最後一個取默認值),第二個取N-2個參數,以此類推。安全

聽來不錯,所以咱們常常將這些構造函數:app

class CustomLinearLayout : LinearLayout {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
}
複製代碼

合併成一個構造函數:函數

class CustomLinearLayout @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr)
複製代碼

第0步: 出現的問題

咱們先來看看Design庫中的TextInputEditText控件,ui

在咱們自定義類中,以下:this

class CustomTextInputEditText : TextInputEditText {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
}
複製代碼

上面的代碼能夠經過一個構造函數來替換:

class CustomTextInputEditText @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : TextInputEditText(context, attrs, defStyleAttr)
複製代碼

這也是Android Studio 會自動爲咱們生成的代碼

image.png

接下來咱們寫一個Activity,它有兩個CustomTextInputEditText 控件,第一個包含全部三個構造函數,第二個包含 @JvmOverloads註釋。

class CustomTextInputEditText1 : TextInputEditText {
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
}
class CustomTextInputEditText2 @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : TextInputEditText(context, attrs, defStyleAttr)
複製代碼

它們的表現最終會相同嗎?

[站外圖片上傳中...(image-465510-1568899351475)]

正如你所看到的,第二個使用@JvmOverloads註解的控件沒有工做。

發生了什麼?爲何會有一些樣式問題?

image.png

第1步: 理解@JvmOverloads註解

讓咱們再花1秒鐘回看一下JvmOverloads 的定義,咱們知道Kotlin編譯器會產生兩個重載(在咱們的例子中N = 3M = 2)。因此咱們最終會獲得三個相似的構造函數:

@JvmOverloads
public CustomTextInputEditText(@NotNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
   super(context, attrs, defStyleAttr);
}
@JvmOverloads
public CustomTextInputEditText(@NotNull Context context, @Nullable AttributeSet attrs) {
   this(context, attrs, 0);
}
@JvmOverloads
public CustomTextInputEditText(@NotNull Context context) {
   this(context, null, 0);
}
複製代碼

所以在咱們的自定義類中,咱們老是會調用到CustomTextInputEditText三參數構造函數。

第2步:理解View的構造函數

如今讓咱們暫時關注到View的構造函數。

當View從XML文件中加載時,會調用其第二個構造函數。而後,此構造函數調用三參數構造函數。

public View(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, 0);
}
複製代碼

若是一般將0做爲第三個參數,爲何這個三參數構造函數要存在呢?答案在文檔中

View的這個構造函數容許子類在加載時使用它們本身的基本樣式。

繼承自View的類能夠傳遞它們本身的樣式來修改全部基本view屬性。

簡單,如今咱們來看看爲何會出錯。

第3步:瞭解到底哪兒出了問題

此時,從第1步開始,咱們知道因爲@JvmOverloads註解,咱們老是調用一個三參數構造函數,而從第2步開始,繼承自View的類可使用這個三參數構造函數來傳遞它們本身的樣式。

讓咱們回到TextInputEditText的構造函數來看看,特別是第二個:

public TextInputEditText(Context context, AttributeSet attrs) {
    this(context, attrs, attr.editTextStyle);
}
複製代碼

這裏正是問題的所在,TextInputEditText的三參數構造函數默認傳的是android.support.design.R.attr.editTextStyle樣式,當咱們使用@JvmOverloads時,默認參數傳的是0,所以就丟失了原來的樣式。

第4步: 修復問題

TextInputEditText的實現咱們知道 ,android.support.design.R.attr.editTextStyle做爲它第三個參數傳遞,因此咱們能夠經過將它設置爲defStyleAttrparam的默認值來進行修復:

class CustomTextInputEditText @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.support.design.R.attr.editTextStyle
) : TextInputEditText(context, attrs, defStyleAttr)
複製代碼

如今,一切都正常運行了,除非...... TextInputEditText的構造函數實現將發生變化,例如經過它傳遞其餘樣式。

另外一個例子,咱們正在使用的子類組件原本工做得很好,忽然看起來與咱們app中的其餘部分有點不一樣,由於它的新版本開始傳遞樣式,咱們只在這一個地方繼承它。

如何保持安全?

當使用@JvmOverloads 存在風險的時候,就不要使用它了,經過實現全部構造函數來代替,只是在實現全部構造函數時編寫一些註釋,以便未來能夠理解。

一些最後的筆記

本文挑選TextInputEditText做爲一個例子,但一樣的狀況將發生在ButtonEditTextRadioButtonSwitch,和許多其餘組件。

您能夠在個人Github倉庫中找到展現這些問題的示例實現。

本文的一個重要內容 - 若是你在使用繼承View來自定義一些組件遇到顯示樣式有問題時,那麼檢查您是否使用@JvmOverloads是值得的。也許就是它致使的錯誤。

相關文章
相關標籤/搜索