解毒 Kotlin Koans: 02 震驚!你的 Java 程式碼居然被轉換成了這樣…

  • 2020 年 2 月 20 日
  • 筆記

上回書我們說道,一個簡單的 HelloWorld 背後也可以隱藏著眾多不可告人的秘密。那麼這些秘密究竟是什麼呢?

那就是,只要我們寫的程式碼可以支援下面的程式碼運行,並返回 "OK",那麼這事兒就成啦:

 start() 

既然這樣,我們除了可以有上一回提到的兩種普通解法之外,還應該有以下幾種高端解法:

  1. 默認參數法: fun start(str: String = "OK") = str
  2. Lambda 表達式/匿名函數法 val start = { "OK" }
  3. 運算符重載法 object start{ operator fun invoke() = "OK" }

你還想到什麼有趣的解法了么?

1. 轉換 Java 為 Kotlin

大家學習 Kotlin,一定知道有個神奇叫做 "Convert Java File to Kotlin File",不僅如此,如果你複製一段 Java 程式碼到 Kotlin 文件中,這段程式碼也會自動轉換成 Kotlin 程式碼。

有很多時候如果你不知道某種東西怎麼用 Kotlin 表達,怎麼辦呢?你總不能說:小二,給洒家來一本牛津大辭典吧?還好有 J2K 轉換工具,這些問題有時候只要你會 Java,你就可以喪心病狂的轉換出 Kotlin 程式碼。

我們今天按照 Kotlin Koan 給出的順序,要解毒的就是下面這道題:

  • 把下面這段 Java 程式碼轉換為 Kotlin 程式碼: public class JavaCode { public String toJSON(Collection<Integer> collection) { StringBuilder sb = new StringBuilder(); sb.append("["); Iterator<Integer> iterator = collection.iterator(); while (iterator.hasNext()) { Integer element = iterator.next(); sb.append(element); if (iterator.hasNext()) { sb.append(", "); } } sb.append("]"); return sb.toString(); } }

嗯,老夫想了想,這有何難,複製粘貼誰不會,真是的。

從此以後,我就成了 Kotlin 大神,反正只要用工具把 Java 程式碼轉一下就好啦,還學什麼學 >.<!

2. 什麼玩意,空指針啊

後來我就經常需要將原來用 Java 編寫的 Activity 轉換為 Kotlin 版本的,例如:

 public class TestActivity extends Activity {      @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          ...      }  } 

老闆說了,從今天起,誰寫 Java 就是跟他作對(請允許我 YY 一下 – -、),於是沒有辦法,我就得把它轉成 Kotlin:

 class TestActivity : Activity() {      override fun onCreate(savedInstanceState: Bundle) {          super.onCreate(savedInstanceState)         ...      }  } 

轉的挺快啊,我還沒反應過來,就轉完了!不過這程式碼你要是敢運行一遍,Crash 就敢噁心你一遍。savedInstanceState 這個參數可能為 null,顯然類型定為 Bundle 有些不合適。

對於平台類型(Platform Type),很多時候轉換工具是無從得知它是否可能為空的,畢竟 Java 沒有對此作出過任何承諾。

怎麼辦?Kotlin 提供了一對註解來標註 Java 類型是否可空:@Nullable@NotNull,Android Support Annotations 這個包也提供了一對:@Nullable@NonNull,我們用這些註解標註一下 Java 類型,那麼再做轉換,工具就會根據你做的標註來轉換程式碼。

 @Override  protected void onCreate(@Nullable Bundle savedInstanceState) {      super.onCreate(savedInstanceState);      ...  } 
 override fun onCreate(savedInstanceState: Bundle?) {      super.onCreate(savedInstanceState)     ...  } 

一句話,用轉換工具的時候一定要注意平台類型!

3. Raw 類型慘叫一聲…

儘管我們知道這轉換工具沒辦法有效識別平台類型的問題,不過,對於下面的情況,它支援起來可能就更有些尷尬了:

 public class BaseView <T extends BasePresenter>{      T presenter;  }  ...  public class BasePresenter<T extends BaseView> {      T view;  } 

下面是轉換後的結果:

 class BaseView<T : BasePresenter<*>> {     var presenter: T? = null  }  class BasePresenter<T : BaseView<*>> {     var view: T? = null  } 

看上去也沒啥問題啊,為啥 IDE 就報錯呢?因為我們要求 BaseView 當中的 T 類型是 BasePresenter 的子類,不過我們對這裡的 BasePresenter 有個小小的要求,那就是它的泛型參數得是 BaseView 的子類而不是 *。對於 BasePresenter 也是一樣的。

那麼 Java 中為什麼沒有這樣的問題呢?因為 Java 中有 Raw 類型,你可以不傳任何泛型參數給 BaseView 就像我們在聲明 BasePresenter 的時候那樣。

顯然,對於 Raw 類型的轉換,轉換工具會用 * 來代替,但這樣的程式碼有時候可以,有時候卻是行不通的。

小心 Raw 類型!

傳送門:Kotlin 泛型

4. Kotlin 風格的程式碼

吐槽轉換工具就好比我們吐槽Google翻譯一樣:有時候不對,就像我們在 2、3 兩節舉的例子一樣,

有時候呢,雖然不算錯,但也實在是彆扭…

比如我們今天提到的 Koans 的這道題目,程式碼轉換的結果雖然是對的,但這程式碼直接暴露了你不會 Kotlin 的事實。會寫 Kotlin 的人家都這麼寫:

 fun toJSON(collection: Collection<Int>): String = collection.joinToString(separator = ", ", prefix = "[", postfix = "]") 

不就是拼接字元串么,還用得著親自動手?祭出 joinToString 神器,可以解決你日常 80% 的拼接需求了。

可是 Collection 怎麼會有這麼個方法,在 Java 裡面沒見過呀!於是你不相信這眼前的一切,點進去看了一下源碼,就發現了擴展方法這樣的大殺器,從此你的 Kotlin 裝備增加了 100 點物理傷害,以及 40% 的攻速。

 public fun <T> Iterable<T>.joinToString(...): String {      return joinTo(...).toString()  } 

掌握擴展方法不需要太多的前置條件,只要你有過迫切的想法想給 Date 添加一個 format 的方法的衝動,那麼你就能理解這個特性是用來做什麼的。

 fun Date.format(pattern: String): String {      return SimpleDateFormat(pattern).format(this)  }  ...  Date().format("yyyy-MM-dd HH:mm:ss") 

5. 本期問題

  1. 請大家閱讀 Kotlin 泛型,並且給出第 3 節中提到的 BaseViewBasePresenter 的 Kotlin 的正確寫法。
  2. 請大家為 String 添加擴展方法, 實現 "abc" – "bc" -> "a"

那麼我們下周再見咯~