在沒(méi)有Kotlin的世界與Android共舞
開(kāi)始投入一件事比遠(yuǎn)離它更容易。?—?Donald Rumsfeld
沒(méi)有 Kotlin 的生活就像在觸摸板上玩魔獸爭(zhēng)霸 3。購(gòu)買(mǎi)鼠標(biāo)很簡(jiǎn)單,但如果你的新雇主不想讓你在生產(chǎn)中使用 Kotlin,你該怎么辦?
下面有一些選擇。
- 與你的產(chǎn)品負(fù)責(zé)人爭(zhēng)取獲得使用 Kotlin 的權(quán)利。
- 使用 Kotlin 并且不告訴其他人因?yàn)槟阒?**的東西是只適合你的。
- 擦掉你的眼淚,自豪地使用 Java。
想象一下,你在和產(chǎn)品負(fù)責(zé)人的斗爭(zhēng)中失敗,作為一個(gè)專(zhuān)業(yè)的工程師,你不能在沒(méi)有同意的情況下私自去使用那些時(shí)髦的技術(shù)。我知道這聽(tīng)起來(lái)非常恐怖,特別當(dāng)你已經(jīng)品嘗到 Kotlin 的好處時(shí),不過(guò)不要失去生活的信念。
在文章接下來(lái)的部分,我想簡(jiǎn)短地描述一些 Kotlin 的特征,使你通過(guò)一些知名的工具和庫(kù),可以應(yīng)用到你的 Android 里的 Java 代碼中去。對(duì)于 Kotlin 和 Java 的基本認(rèn)識(shí)是需要的。
數(shù)據(jù)類(lèi)
我想你肯定已經(jīng)喜歡上 Kotlin 的數(shù)據(jù)類(lèi)。對(duì)于你來(lái)說(shuō),得到 equals()、 hashCode()、 toString() 和 copy() 這些是很容易的。具體來(lái)說(shuō),data 關(guān)鍵字還可以按照聲明順序生成對(duì)應(yīng)于屬性的 componentN() 函數(shù)。 它們用于解構(gòu)聲明。
data class Person(val name: String)
val (riddle) = Person("Peter")
println(riddle)
你知道什么會(huì)被打印出來(lái)嗎?確實(shí),它不會(huì)是從 Person 類(lèi)的 toString() 返回的值。這是解構(gòu)聲明的作用,它賦值從 name 到 riddle。使用園括號(hào) (riddle) 編譯器知道它必須使用解構(gòu)聲明機(jī)制。
val (riddle): String = Person("Peter").component1()
println(riddle) // prints Peter)
這個(gè)代碼沒(méi)編譯。它就是展示了構(gòu)造聲明怎么工作的。
正如你可以看到 data 關(guān)鍵字是一個(gè)超級(jí)有用的語(yǔ)言特性,所以你能做什么把它帶到你的 Java 世界? 使用注釋處理器并修改抽象語(yǔ)法樹(shù)(Abstract Syntax Tree)。 如果你想更深入,請(qǐng)閱讀文章末尾列出的文章(Project Lombok—?Trick Explained)。
使用項(xiàng)目 Lombok 你可以實(shí)現(xiàn) data關(guān)鍵字所提供的幾乎相同的功能。 不幸的是,沒(méi)有辦法進(jìn)行解構(gòu)聲明。
import lombok.Data;
@Data class Person {
final String name;
}
@Data 注解生成 equals()、hashCode() 和 toString()。 此外,它為所有字段創(chuàng)建 getter,為所有非最終字段創(chuàng)建setter,并為所有必填字段(final)創(chuàng)建構(gòu)造函數(shù)。 值得注意的是,Lombok 僅用于編譯,因此庫(kù)代碼不會(huì)添加到您的最終的 .apk。
Lambda 表達(dá)式
Android 工程師有一個(gè)非常艱難的生活,因?yàn)?Android 中缺乏 Java 8 的特性,而且其中之一是 lambda 表達(dá)式。 Lambda 是很棒的,因?yàn)樗鼈優(yōu)槟銣p少了成噸的樣板。 你可以在回調(diào)和流中使用它們。 在 Kotlin 中,lambda 表達(dá)式是內(nèi)置的,它們看起來(lái)比它們?cè)?Java 中看起來(lái)好多了。 此外,lambda 的字節(jié)碼可以直接插入到調(diào)用方法的字節(jié)碼中,因此方法計(jì)數(shù)不會(huì)增加。 它可以使用內(nèi)聯(lián)函數(shù)。
button.setOnClickListener { println("Hello World") }
最近 Google 宣布在 Android 中支持 Java 8 的特性,由于 Jack 編譯器,你可以在你的代碼中使用 lambda。還要提及的是,它們?cè)?API 23 或者更低的級(jí)別都可用。
button.setOnClickListener(view -> System.out.println("Hello World!"));
怎樣使用它們?就只用添加下面幾行到你的 build.gradle 文件中。
defaultConfig {
jackOptions {
enabled true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
如果你不喜歡用 Jack 編譯器,或者你由于一些原因不能使用它,這里有一個(gè)不同的解決方案提供給你。Retrolambda 項(xiàng)目允許你在 Java 7,6 或者 5 上運(yùn)行帶有 lambda 表達(dá)式的 Java 8 代碼,下面是設(shè)置過(guò)程。
dependencies {
classpath 'me.tatarka:gradle-retrolambda:3.4.0'
}
apply plugin: 'me.tatarka.retrolambda'
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
正如我前面提到的,在 Kotlin 下的 lambda 內(nèi)聯(lián)函數(shù)不增加方法計(jì)數(shù),但是如何在 Jack 或者 Retrolambda 下使用它們呢? 顯然,它們不是沒(méi)成本的,隱藏的成本如下。
該表展示了使用不同版本的 Retrolambda 和 Jack 編譯器生成的方法數(shù)量。該比較結(jié)果來(lái)自 Jake Wharton 的“探索 Java 的隱藏成本” 技術(shù)討論之中。
數(shù)據(jù)操作
Kotlin 引入了高階函數(shù)作為流的替代。 當(dāng)您必須將一組數(shù)據(jù)轉(zhuǎn)換為另一組數(shù)據(jù)或過(guò)濾集合時(shí),它們非常有用。
fun foo(persons: MutableList<Person>) {
persons.filter { it.age >= 21 }
.filter { it.name.startsWith("P") }
.map { it.name }
.sorted()
.forEach(::println)
}
data class Person(val name: String, val age: Int)
流也由 Google 通過(guò) Jack 編譯器提供。 不幸的是,Jack 不使用 Lombok,因?yàn)樗诰幾g代碼時(shí)跳過(guò)生成中間的 .class 文件,而 Lombok 卻依賴(lài)于這些文件。
void foo(List<Person> persons) {
persons.stream()
.filter(it -> it.getAge() >= 21)
.filter(it -> it.getName().startsWith("P"))
.map(Person::getName)
.sorted()
.forEach(System.out::println);
}
class Person {
final private String name;
final private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
String getName() { return name; }
int getAge() { return age; }
}
這簡(jiǎn)直太好了,所以 catch 在哪里? 令人悲傷的是,流從 API 24 才可用。谷歌做了好事,但哪個(gè)應(yīng)用程序有用 minSdkVersion = 24?
幸運(yùn)的是,Android 平臺(tái)有一個(gè)很好的提供許多很棒的庫(kù)的開(kāi)源社區(qū)。Lightweight-Stream-API 就是其中的一個(gè),它包含了 Java 7 及以下版本的基于迭代器的流實(shí)現(xiàn)。
import lombok.Data;
import com.annimon.stream.Stream;
void foo(List<Person> persons) {
Stream.of(persons)
.filter(it -> it.getAge() >= 21)
.filter(it -> it.getName().startsWith("P"))
.map(Person::getName)
.sorted()
.forEach(System.out::println);
}
@Data class Person {
final String name;
final int age;
}
上面的例子結(jié)合了 Lombok、Retrolambda 和 Lightweight-Stream-API,它看起來(lái)幾乎和 Kotlin 一樣棒。使用靜態(tài)工廠方法允許您將任何 Iterable 轉(zhuǎn)換為流,并對(duì)其應(yīng)用 lambda,就像 Java 8 流一樣。 將靜態(tài)調(diào)用 Stream.of(persons) 包裝為 Iterable 類(lèi)型的擴(kuò)展函數(shù)是***的,但是 Java 不支持它。
擴(kuò)展函數(shù)
擴(kuò)展機(jī)制提供了向類(lèi)添加功能而無(wú)需繼承它的能力。 這個(gè)眾所周知的概念非常適合 Android 世界,這就是 Kotlin 在該社區(qū)很受歡迎的原因。
有沒(méi)有技術(shù)或魔術(shù)將擴(kuò)展功能添加到你的 Java 工具箱? 因 Lombok,你可以使用它們作為一個(gè)實(shí)驗(yàn)功能。 根據(jù) Lombok 文檔的說(shuō)明,他們想把它從實(shí)驗(yàn)狀態(tài)移出,基本上沒(méi)有什么變化的話(huà)很快。 讓我們重構(gòu)***一個(gè)例子,并將 Stream.of(persons) 包裝成擴(kuò)展函數(shù)。
import lombok.Data;
import lombok.experimental.ExtensionMethod;
@ExtensionMethod(Streams.class)
public class Foo {
void foo(List<Person> persons) {
persons.toStream()
.filter(it -> it.getAge() >= 21)
.filter(it -> it.getName().startsWith("P"))
.map(Person::getName)
.sorted()
.forEach(System.out::println);
}
}
@Data class Person {
final String name;
final int age;
}
class Streams {
static <T> Stream<T> toStream(List<T> list) {
return Stream.of(list);
}
}
所有的方法是 public、static 的,并且至少有一個(gè)參數(shù)的類(lèi)型不是原始的,因而是擴(kuò)展方法。 @ExtensionMethod 注解允許你指定一個(gè)包含你的擴(kuò)展函數(shù)的類(lèi)。 你也可以傳遞數(shù)組,而不是使用一個(gè) .class 對(duì)象。
我完全知道我的一些想法是非常有爭(zhēng)議的,特別是 Lombok,我也知道,有很多的庫(kù),可以使你的生活更輕松。請(qǐng)不要猶豫在評(píng)論里分享你的經(jīng)驗(yàn)。干杯!
????