解毒 Kotlin Koans: 02 震驚!你的 Java 程式碼居然被轉換成了這樣…
- 2020 年 2 月 20 日
- 筆記
上回書我們說道,一個簡單的 HelloWorld 背後也可以隱藏著眾多不可告人的秘密。那麼這些秘密究竟是什麼呢?
那就是,只要我們寫的程式碼可以支援下面的程式碼運行,並返回 "OK",那麼這事兒就成啦:
start()
既然這樣,我們除了可以有上一回提到的兩種普通解法之外,還應該有以下幾種高端解法:
- 默認參數法:
fun
start(str: String =
"OK")
= str
- Lambda 表達式/匿名函數法
val start =
{
"OK"
}
- 運算符重載法
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. 本期問題
- 請大家閱讀 Kotlin 泛型,並且給出第 3 節中提到的
BaseView
和BasePresenter
的 Kotlin 的正確寫法。 - 請大家為
String
添加擴展方法, 實現 "abc" – "bc" -> "a"
那麼我們下周再見咯~