原文: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)
複製代碼
咱們先來看看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 會自動爲咱們生成的代碼
接下來咱們寫一個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
註解的控件沒有工做。
發生了什麼?爲何會有一些樣式問題?
@JvmOverloads
註解讓咱們再花1秒鐘回看一下JvmOverloads
的定義,咱們知道Kotlin編譯器會產生兩個重載(在咱們的例子中N = 3
和M = 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
三參數構造函數。
如今讓咱們暫時關注到View的構造函數。
當View從XML文件中加載時,會調用其第二個構造函數。而後,此構造函數調用三參數構造函數。
public View(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
複製代碼
若是一般將0
做爲第三個參數,爲何這個三參數構造函數要存在呢?答案在文檔中。
View的這個構造函數容許子類在加載時使用它們本身的基本樣式。
繼承自View的類能夠傳遞它們本身的樣式來修改全部基本view
屬性。
簡單,如今咱們來看看爲何會出錯。
此時,從第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
,所以就丟失了原來的樣式。
從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
做爲一個例子,但一樣的狀況將發生在Button
,EditText
,RadioButton
,Switch
,和許多其餘組件。
您能夠在個人Github倉庫中找到展現這些問題的示例實現。
本文的一個重要內容 - 若是你在使用繼承View來自定義一些組件遇到顯示樣式有問題時,那麼檢查您是否使用@JvmOverloads
是值得的。也許就是它致使的錯誤。