自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Kotlin重載個(gè)方法,還有兩幅面孔,省代碼的同時(shí)也帶來一個(gè)深坑 | Kotlin 原理

開發(fā) 開發(fā)工具
今年五月的 Google I/O 上,Google 正式向全球宣布 Kotlin-First 這一重要概念,Kotlin 將成為 Android 開發(fā)者的首選語言。

[[281676]]

 一. 序

今年五月的 Google I/O 上,Google 正式向全球宣布 Kotlin-First 這一重要概念,Kotlin 將成為 Android 開發(fā)者的首選語言。

新語言有新特性,開發(fā)者還保持 Java 的編程習(xí)慣去寫 Kotlin,也不是不行,但是總感覺差點(diǎn)意思。

最近公眾號(hào)「谷歌開發(fā)者」連載了一個(gè)《實(shí)用 Kotlin 構(gòu)建 Android 應(yīng)用 | Kotlin 遷移指南》的系列文章,就舉例了一些 Kotlin 編碼的小技巧。

既然是一種指南性質(zhì)的文章,自然在「多而廣」的基礎(chǔ)上,有意去省略一些細(xì)節(jié),同時(shí)舉例的場(chǎng)景,可能還有一些不恰當(dāng)?shù)牡胤健?/p>

這里我就來補(bǔ)齊這些細(xì)節(jié),今天聊聊利用 Kotlin 的方法默認(rèn)參數(shù)的特性,完成類似 Java 的方法重載的效果。完全解析這個(gè)特性的使用方式和原理,以及在使用過程中的一個(gè)深坑。

二. Kotlin 的簡(jiǎn)易方法重載

2.1 Kotlin 如何簡(jiǎn)化方法重載?

在 Java 中,我們可以在同一個(gè)類中,定義多個(gè)同名的方法,只需要保證每個(gè)方法具有不同的參數(shù)類型或參數(shù)個(gè)數(shù),這就是 Java 的方法重載。

  1. class Hello { 
  2.     public static void hello() { 
  3.         System.out.println("Hello, world!"); 
  4.     } 
  5.  
  6.     public static void hello(String name) { 
  7.         System.out.println("Hello, "name +"!"); 
  8.     } 
  9.  
  10.     public static void hello(String nameint age) { 
  11.         if (age > 0) { 
  12.             System.out.println("Hello, "name + "(" +age +")!"); 
  13.         } else { 
  14.             System.out.println("Hello, "name +"!"); 
  15.         } 
  16.     } 

在這個(gè)例子中,我們定義了三個(gè)同名的 hello() 方法,具有不同的邏輯細(xì)節(jié)。

在 Kotlin 中,因?yàn)樗С衷谕粋€(gè)方法里,通過 「?」標(biāo)出可空參數(shù),以及通過「=」給出參數(shù)的默認(rèn)值。那這三個(gè)方法就可以在 Kotlin 中,被柔和成一個(gè)方法。

  1. object HelloDemo{ 
  2.     fun hello(name: String = "world", age: Int = 0) { 
  3.         if (age > 0) { 
  4.             System.out.println("Hello, ${name}(${age})!"); 
  5.         } else { 
  6.             System.out.println("Hello, ${name}!"); 
  7.         } 
  8.     } 

在 Kotlin 類中調(diào)用,和前面 Java 實(shí)現(xiàn)的效果是一致的。

  1. HelloDemo.hello() 
  2. HelloDemo.hello("承香墨影"
  3. HelloDemo.hello("承香墨影", 16) 

但是這個(gè)通過 Kotlin 方法參數(shù)默認(rèn)值的特性申明的方法,在 Java 類中使用時(shí),就有些區(qū)別了。因?yàn)?HelloDemo 類被聲明為 object,所以在 Java 中需要使用 INSTANCE 來調(diào)用它的方法。

  1. HelloDemo.INSTANCE.hello("承香墨影",16); 

Kotlin 中調(diào)用 hello() 方法很方便,可以選擇性的忽略參數(shù),但是在 Java 中使用,必須全量的顯式的去做參數(shù)賦值。

這就是使用了參數(shù)默認(rèn)值的方法申明時(shí),分別在 Kotlin 和 Java 中的使用方式,接下來我們看看原理。

2.2 Kotlin 方法參數(shù)指定默認(rèn)值的原理

Kotlin 編寫的代碼,之所以可以在 Java 系的虛擬機(jī)中運(yùn)行,主要是因?yàn)樗诰幾g的過程中,會(huì)被編譯成虛擬機(jī)可識(shí)別的 Java 字節(jié)碼。所以我們通過兩次轉(zhuǎn)換的方式(Show Kotlin Bytecode + Decompile),就可以得到 Kotlin 生成的對(duì)應(yīng) Java 代碼了。

  1. public final void hello(@NotNull String nameint age) { 
  2.   Intrinsics.checkParameterIsNotNull(name"name"); 
  3.   if (age > 0) { 
  4.      System.out.println("Hello, " + name + '(' + age + ")!"); 
  5.   } else { 
  6.      System.out.println("Hello, " + name + '!'); 
  7.   } 
  8.  
  9. // $FF: synthetic method 
  10. public static void hello$default(HelloDemo var0, String var1, int var2, int var3, Object var4) { 
  11.   if ((var3 & 1) != 0) { 
  12.      var1 = "world"
  13.   } 
  14.  
  15.   if ((var3 & 2) != 0) { 
  16.      var2 = 0; 
  17.   } 
  18.   var0.hello(var1, var2); 

在這里會(huì)生成一個(gè) hello() 方法,同時(shí)還會(huì)有一個(gè)合成方法(synthetic method)hello$default,用來處理默認(rèn)參數(shù)的問題。在 Kotlin 中調(diào)用 hello()方法,會(huì)在編譯期間,有選擇性的自動(dòng)替換成 hello() 的合成方法去調(diào)用。

  1. // Kotlin 調(diào)用 
  2. HelloDemo.hello() 
  3. HelloDemo.hello("承香墨影"
  4. HelloDemo.hello("承香墨影", 16) 
  5.  
  6. // 編譯后的 Java 代碼 
  7. HelloDemo.hello$default(HelloDemo.INSTANCE, (String)null, 0, 3, (Object)null); 
  8. HelloDemo.hello$default(HelloDemo.INSTANCE, "承香墨影", 0, 2, (Object)null); 
  9. HelloDemo.INSTANCE.hello("承香墨影", 16); 

注意看示例的末尾,當(dāng)使用 hello(name,age) 這個(gè)方法重載時(shí),其實(shí)與 Java 中的調(diào)用,是一致的,這沒什么好說的。

這就是 Kotlin 方法重載時(shí),使用指定默認(rèn)參數(shù)的方式,省去多個(gè)方法重載代碼的原理。

理解原理后,發(fā)現(xiàn)它確實(shí)減少了我們編寫的代碼量,但是有沒有場(chǎng)景,是我們就需要顯式的存在這幾個(gè)方法的重載的?自然是有的,例如自定義 View 時(shí)。

三. 自定義 View 遇上 Kotlin

3.1 構(gòu)造方法也是方法

再回到前面提到的谷歌開發(fā)者的《實(shí)用 Kotlin 構(gòu)建 Android 應(yīng)用 | Kotlin 遷移指南》系列文章中,舉的例子其實(shí)很不恰當(dāng)。

 

它這里的例子中,使用了 View 這個(gè)詞,并且重載的幾個(gè)方法,都是 View 的構(gòu)造方法,我們?cè)谧远x View 時(shí),經(jīng)常會(huì)和這三個(gè)方法打交道。

但是谷歌工程師在這里舉的例子,很容易讓人誤會(huì),實(shí)際上你如果在自定義 View 時(shí),這么寫一定是會(huì)報(bào)錯(cuò)的。

例如我們自定義一個(gè) DemoView,它繼承自 EditView。

  1. class DemoView( 
  2.         context: Context,  
  3.         attrs: AttributeSet? = null,  
  4.         defStyleAttr: Int = 0 
  5. ) : EditText(context, attrs, defStyleAttr) { 

這個(gè)自定義的 DemoView,當(dāng)使用在 XML 布局中時(shí),雖然編譯不會(huì)出錯(cuò),但是運(yùn)行時(shí),你會(huì)得到一個(gè) NoSuchMethodException。

  1. Caused by: java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet] 

什么問題呢?

在 LayoutInflater 創(chuàng)建控件時(shí),找不到 DemoView(Context, AttributeSet) 這個(gè)重載方法,所以就報(bào)錯(cuò)了。

這其實(shí)很好理解,在前面說到 Kotlin 在使用帶默認(rèn)值的方法的原理,其實(shí) Kotlin 最終會(huì)在編譯后,額外生成一個(gè)合成方法,來處理方法的參數(shù)默認(rèn)值的情況,它和 Java 的方法重載還不一樣,用它生成的方法,確實(shí)不會(huì)存在多個(gè)方法的重載。

所以要明白,Kotlin 的方法指定默認(rèn)參數(shù)與 Java 的方法重載,并不等價(jià)。只能說它們?cè)谀承﹫?chǎng)景下,特性是類似的。

3.2 使用 @JvmOverloads

那么回到這里的問題,在自定義 View 或者其他需要保留 Java 方法重載的場(chǎng)景下,怎么讓 Kotlin 在編譯時(shí),真實(shí)的去生成對(duì)應(yīng)的重載方法?

這里就需要用到 @JvmOverloads 了。

當(dāng) Kotlin 使用了默認(rèn)值的方法,被增加了 @JvmOverloads 注解后,它的含義就是在編譯時(shí),保持并暴露出該方法的多個(gè)重載方法。

其實(shí)當(dāng)我們自定義 View 時(shí),AS 已經(jīng)給了我們充分的提示,它會(huì)自動(dòng)幫我們生成帶 @JvmOverloads 構(gòu)造方法。

 

AS 幫我們補(bǔ)全的代碼如下:

  1. class DemoView @JvmOverloads constructor( 
  2.         context: Context,  
  3.         attrs: AttributeSet? = null,  
  4.         defStyleAttr: Int = 0 
  5. ) : AppCompatEditText(context, attrs, defStyleAttr) { 

再用「Kotlin Bytecode + Decompile」查看一下編譯后的代碼,來驗(yàn)證@JvmOverloads 的效果。

  1. @JvmOverloads 
  2. public DemoView(@NotNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 
  3.   Intrinsics.checkParameterIsNotNull(context, "context"); 
  4.   super(context, attrs, defStyleAttr); 
  5.  
  6. // $FF: synthetic method 
  7. public DemoView(Context var1, AttributeSet var2, int var3, int var4, DefaultConstructorMarker var5) { 
  8.   if ((var4 & 2) != 0) { 
  9.      var2 = (AttributeSet)null
  10.   } 
  11.  
  12.   if ((var4 & 4) != 0) { 
  13.      var3 = 0; 
  14.   } 
  15.  
  16.   this(var1, var2, var3); 
  17.  
  18. @JvmOverloads 
  19. public DemoView(@NotNull Context context, @Nullable AttributeSet attrs) { 
  20.   this(context, attrs, 0, 4, (DefaultConstructorMarker)null); 
  21.  
  22. @JvmOverloads 
  23. public DemoView(@NotNull Context context) { 
  24.   this(context, (AttributeSet)null, 0, 6, (DefaultConstructorMarker)null); 

可以看到,@JvmOverloads 生效后,會(huì)按照我們的預(yù)期生成對(duì)應(yīng)的重載方法,同時(shí)保留合成方法,完成在 Kotlin 中使用時(shí),使用默認(rèn)參數(shù)的需求。

是不是以為到這里就完了?并不是,如果你在自定義 View 時(shí),完全按照 AS 給你的提示生成代碼,雖然程序不會(huì)崩潰了,但你會(huì)得到一些未知的錯(cuò)誤。

3.3 View 中別直接用 AS 生成代碼

在自定義 View 時(shí),依賴 AS 的提示生成代碼,會(huì)遇到一些未知的錯(cuò)誤。例如在本文的例子中,我們想要實(shí)現(xiàn)一個(gè) EditView 的子類,用 AS 提示生成了代碼。

會(huì)出現(xiàn)什么問題呢?

在 EditView 的場(chǎng)景下,你會(huì)發(fā)現(xiàn)焦點(diǎn)沒有了,點(diǎn)擊之后軟鍵盤也不會(huì)自動(dòng)彈出。

那為什么會(huì)出現(xiàn)這種問題?

原因就在 AS 在自動(dòng)生成的代碼時(shí),對(duì)參數(shù)默認(rèn)值的處理。

當(dāng)在自定義 View 時(shí),通過 AS 生成重載方法時(shí),它對(duì)參數(shù)默認(rèn)值的處理規(guī)則是這樣的。

  1. 遇到對(duì)象,默認(rèn)值為 null。
  2. 遇到基礎(chǔ)數(shù)據(jù)類型,默認(rèn)值為基本數(shù)據(jù)類型的默認(rèn)值。例如 Int 就是 0,Boolean 就是 false。

而在這里的場(chǎng)景下, defStyleAttr 這個(gè)參數(shù)的類型為 Int,所以默認(rèn)值會(huì)被賦值為 0,但是它并不是我們需要的。

在 Android 中,當(dāng) View 通過 XML 文件來布局使用時(shí),會(huì)調(diào)用兩個(gè)參數(shù)的構(gòu)造方法 (Context context, AttributeSet attrs),而它內(nèi)部會(huì)調(diào)用三個(gè)參數(shù)的構(gòu)造方法,并傳遞一個(gè)默認(rèn)的 defStyleAttr,注意它并不是 0。

既然找到了問題,就很好解決了。我們看看自定義 View 的父類中,兩個(gè)參數(shù)的構(gòu)造方法如何實(shí)現(xiàn)的,將 defStyleArrt 當(dāng)默認(rèn)值傳遞進(jìn)去就好了。

那我們先看看 AppCompatEditText 中的實(shí)現(xiàn)。

  1. public AppCompatEditText(Context context,  
  2.                          AttributeSet attrs) { 
  3.     this(context, attrs, R.attr.editTextStyle); 

再修改 DemoView 中對(duì) defStyleAttr 默認(rèn)值的指定即可。

  1. class DemoView @JvmOverloads constructor( 
  2.         context: Context, 
  3.         attrs: AttributeSet? = null,  
  4.         defStyleAttr: Int = R.attr.editTextStyle 
  5. ) : AppCompatEditText(context, attrs, defStyleAttr) { 

到這里,自定義 View 中,使用默認(rèn)參數(shù)的構(gòu)造方法重載問題,也解決了。

在自定義 View 的場(chǎng)景下,當(dāng)然也可以通過重寫多個(gè) constructor 方法來實(shí)現(xiàn)類似的效果,但是既然已經(jīng)明白了它的原理,那就放心大膽的使用吧。

四. 小結(jié)時(shí)刻

到這里就弄清楚 Kotlin 中,使用默認(rèn)參數(shù)來減少方法重載代碼的使用技巧和原理,以及注意事項(xiàng)了。

弄清楚原理以及需要注意的點(diǎn),可以幫助我們更好的使用 Kotlin 的特性。我們最后再總結(jié)一下本文的知識(shí)點(diǎn):

Kotlin 可以通過對(duì)一個(gè)方法的參數(shù),通過指定默認(rèn)值的方式,來完成類似 Java 中「方法重載」的效果。

若想保留 Java 的重載方法,可以使用 @JvmOverloads 注解標(biāo)記,它會(huì)自動(dòng)生成該方法的全部重載方法。

在自定義 View 時(shí),需要注意指定參數(shù) defStyleAttr 的默認(rèn)值,而不應(yīng)該是 0。

【本文為51CTO專欄作者“張旸”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過微信公眾號(hào)聯(lián)系作者獲取授權(quán)】

 

戳這里,看該作者更多好文

責(zé)任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

2017-12-27 14:51:12

Kotlin谷歌Java

2019-04-01 14:17:36

kotlin開發(fā)Java

2020-11-01 17:11:51

time.sleep暫停代碼Python

2016-01-05 13:52:05

Kotlin掌握語言

2020-09-18 10:12:24

KotlinTCP網(wǎng)絡(luò)協(xié)議

2017-05-10 14:49:52

Kotlin語言Java

2020-11-10 07:13:44

端口號(hào)進(jìn)程

2025-04-14 08:21:07

2017-12-15 10:50:40

Kotlin語法糖程序員

2014-05-19 09:40:48

SDN

2022-02-28 10:38:13

Kotlin插件Android

2021-05-20 09:14:09

Kotlin協(xié)程掛起和恢復(fù)

2017-05-19 18:01:04

GoogleKotlin數(shù)據(jù)

2017-08-03 15:54:50

Kotlin繼承

2018-06-05 10:30:28

KotlinJava語言

2021-09-26 05:25:33

邊緣計(jì)算IoT

2009-06-30 10:37:59

Linux操作系統(tǒng)

2009-06-06 19:15:39

imagebuffer

2016-11-24 17:21:30

2011-01-07 13:42:00

Fpingping掃描程序
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)