Kotlin如何成為我們Android開發的主要語言
引言
Kotlin是一個基于JVM的新的編程語言,由JetBrains開發。JetBrains,作為目前廣受歡迎的Java IDE IntelliJ的提供商,在Apache許可下已經開源其Kotlin編程語言。與Java相比,Kotlin的語法更簡潔、更具表達性,而且提供了更多的特性,比如,高階函數、操作符重載、字符串模板。它與Java高度可互操作,可以同時用在一個項目中。
Kotlin的定位非常有特點,它并不像Scala那樣另起爐灶,Scala是一切盡量自己來,將類庫,尤其是集合類都自己來了一遍。實在不夠用了再用java的;而Kotlin是對現有Java的增強,盡量用Java的,不夠用了再擴展,尤其體現在二者的容器庫上,但同時始終保持對java的兼容。這種特點導致Kotlin的學習曲線極低。這是Kotlin官網首頁重點強調的:“ 100% interoperable with Java? ”。這意味著什么呢?或者換個問法:我什么時候可以開始在我的項目中引入Kotlin呢?我的回答是:現在就可以視你對kotlin的掌握程度,逐步引入kotlin的代碼。
Dima Kovalenko 在 博客 中分享了他們團隊使用Kotlin開發商業應用程序的心得和經驗,并提供了一些參考資料。希望本文能對廣大Android開發程序員有所啟發。
幾個月前,我們的團隊決定開始新的嘗試:完全應用Kotlin編程語言開發一個商業應用程序,這是JetBrains公司設計并開源的一種新編程語言。以前,我們有過Kotlin的經驗,但那只是小規模應用:將應用程序的一部分轉換到一種新的語言,或者應用在花里胡哨的項目。然而,用新的編程語言來開發商業應用程序,我們遇到了一些困難:
- 我們深深扎根于基于Java的Android開發。切換到Kotlin相當困難,對于以前沒有函數式編程經驗的人員而言,尤為困難。
- 有些東西只是不工作。 Dagger也沒有立即很好的使用。
所有這些問題,都可能會導致項目無法按期交付、并帶來應用程序的穩定性問題。
一個人應該有強烈的轉型動力。我們的激勵是:相信 Kotlin將是Android平臺開發的顛覆者 ——這只是一句 玩笑話 。
讓我們打開Kotlin的參考書,開始開發 Voter應用程序 。Kotlin是一種與Java具有100%互操作性的JVM語言,如果您熟悉Java,那么學習Kotlin就會很容易。然而,如果想充分利用這個編程語言,理解函數式編程概念是至關重要的。
學習函數式編程需要一段時間。所以要有耐心。
至少在學習之初,函數式編程并不容易。我強烈建議使用 Martin Ordersky的“Scala中的函數式編程”的課程 來學習。Scala有時勢不可擋,但它提供了一個極好的函數式編程思維的概述。你可以把Kotlin看成Scala的一個更簡化的版本。
為什么我們轉向Kotlin陣營
函數式編程風格
Kotlin與Java是100%可互操作的。此外,Kotlin是一種函數式語言。后者允許將表達性的代碼編寫得更優雅。
1.純函數
純函數(沒有副作用的函數)是最重要的函數概念,它允許我們大大降低代碼復雜性并消除大多數可變狀態。
在JavaScript、Java和C#這些命令式編程語言中,副作用無處不在。這使得調試非常困難,因為變量可以在程序中的任何位置更改。所以當出現一個錯誤時,由于變量可以在錯誤的時間更改為錯誤的值,那么你到哪里去尋找錯誤呢?到處尋找錯誤嗎?這可不好玩啊!
請注意我們是如何操作數據而不更改其內容的。
fun flatTree(tree: TreeNode): List<TreeNode>
= listOf(tree, *tree.children.flatMap(::flatTree).toTypedArray())
2.高階函數
高階函數將函數用作參數,返回函數或將函數作為返回值的函數。
高階函數無處不在。你只需將函數傳遞給集合,就能使代碼更容易閱讀。比如, titles.map {it.toUpperCase()} 讀取簡單的英語,是不是很棒?
讓我們設想一種情況,假設要計算不同類型的未讀消息的數量。典型的方法是:
private fun getUnreadCountFromUsers() {
val conversations = datasource.getConversations()
var count = 0
for (conversation in conversations) {
if (conversation.recipientId != null) {
for (message in conversation.messages) {
if (message.unread) {
count += 1
}
}
}
}
}
private fun getNumberOfUnreadAttachmentsInGroupConversations() {
val conversations = datasource.getConversations()
var count = 0
for (conversation in conversations) {
if (conversation.groupId != null) {
for (message in conversation.messages) {
if (message.unread && message.type == MessageType.ATTACHMENT) {
count += 1
}
}
}
}
}</code></pre>
正如你所看到的,當引入新的需求時,代碼變得難以理解、不可收拾。讓我們看看如何使用高階函數來解決這個問題:
private fun getNumberOfAttachmentsInGroupConvesationsFun() {
return getCount({conv -> conv.groupId != null}, {it -> it.type == MessageType.ATTACHMENT && it.unread})
}
private fun getUnreadCountFromUsersFun() {
return getCount({conv -> conv.recipientId != null}, {message -> message.unread})
}
private fun getTotalNumberOfMessages() = getCount({true}, {true})
private fun getCount(convFilter: (Conversation) -> Boolean, messageFilter: (Message) -> Boolean) {
datasource.getConversations()
.filter(convFilter)
.flatMap { it.messages }
.filter(messageFilter)
.fold(0, { count, message -> count + 1})
}</code></pre>
我們還可以想象一下用例,假設想將 fold 函數變量參數化。比方說,計算未讀消息的乘積。
使用高階函數的另一個例子是用簡單的高階函數代替多個監聽器:
BillingView : LinearLayout {
var billingChangeListener: (() -> Unit)? = null
...
}
... // in an activity far, far away
billingView.billingChangeListener { updateUI() }
3.不變性
不變性使得代碼更容易編寫,使用和推理代碼(類不變性一次建立,然后不變——一勞永逸)。應用程序組件的內部狀態將更加一致。Kotlin通過引入 val 關鍵字以及Kotlin集合來強制不變性,Kotlin集合在默認情況下是不可變的。 一旦 val 或者一個集合被初始化,你就可以確定它的有效性。(有關 val 關鍵字的更精確的定義,請參閱文末的更新)。
data class Address(val line1: String, val city: String)
valitems = listOf(Address("242 5th St", "Los Angeles"), Address("Dovzhenka St. 5", "Kiev"))
空安全(Null-safety)
這個語言特性使我們仔細考慮了模型類中字段的可空性。以前,當不確定DTO中的字段是否已初始化時,@Nullable和@NotNull的注釋就能提供幫助,但也很有限。現在,使用Kotlin,就能讓你準確知道什么字段可以為null,什么字段被初始化(例如,Dagger注入的字段),并且你可以對這些字段有嚴格的控制。結果?幾乎沒有 NullPointerExceptions 。(在內部我們管 ?. 叫做“鵝”操作符,因為它看起來像一個鵝的脖子。)
brand?.let { badge.enabled = brand.isNewBadge }
// Can also be written as
badge.enabled = brand?.isNewBadge?:false
Anko
Anko DSL是一個很了不起的的庫,它大大簡化了工作視圖、線程和Android生命周期。據Github的描述,Anko是“令人愉快的Android應用程序開發”,事實證明,的確如此。
selector(items = listOf("Like", "Dislike") {
when (it) {
0 -> if (!liked) likePost()
else -> if (!disLiked) disLikePost()
}
}
doAsync {
// Long background task
uiThread {
alert(R.string.could_not_log_in) {
yesButton { dismiss() }
cancellable = false
}.show()
}
}</code></pre>
注意,當 uiThread 在Activity內調用時,如果 isFinishing 為 true ,塊將不會執行。我們實際上不使用這個功能,因為RxJava會處理應用程序中的所有線程,但它是一個很好的功能。
使用Anko而不是XML。雖然Anko還沒有做好準備取代標準的Android UI構建,但有的時候,它非常方便。
verticalLayout() {
friendsPanel = friendsPanel.with(friendsData).lparams(width = matchParent)
politicalMapCardView {
setMarker(quizManager.getMarker())
}.lparams(width = matchParent) { topMargin = dip(10) }
cardView() {
verticalLayout() {
topPadding = dip(5)
textView(getString(R.string.register_question))
blueButtonView(text="Register here") {
onClick { browse("如您所見,Anko DSL允許您在Android內置視圖中使用自定義視圖。這一點與標準XML相比有很大的優勢。
Kotlin Android擴展:刪除ButterKnife依賴
@Bind(R.id.first_name)
protected EditText firstName;
@Bind(R.id.last_name)
protected EditText lastName;
@Bind(R.id.address_line1)
protected EditText addressLine1;
@Bind(R.id.address_line2)
protected EditText addressLine2;
@Bind(R.id.zip_code)
protected EditText zipCode;
@Bind(R.id.state)
protected TextView state;
@Bind(R.id.state_spinner)
protected HintSpinner stateSpinner;
@Bind(R.id.city)
protected EditText city;
@Bind(R.id.frag_shipping_address_save_btn)
protected Button saveBtn;
@Bind(R.id.agreement)
protected TextView agreement;
@Bind(R.id.email)
protected EditText email;
@Bind(R.id.password)
protected EditText password;
@Bind(R.id.create_account_container)
protected LinearLayout accountContainer;
@Bind(R.id.member_container)
protected LinearLayout memberContainer;
@Bind(R.id.logged_in_title)
protected TextView loggedInTitle;
@Bind(R.id.user_email)
protected TextView userEmail;
@Bind(R.id.sign_out)
protected TextView signOut;
@Bind(R.id.scrollview)
protected ScrollView scrollView;
@Bind(R.id.dummy)
protected EditText dummyView;</code></pre>
上面那段代碼讀起來無聊嗎?我敢打賭你一直滾動而沒有閱讀。在Kotlin,你并不需要任何這些東西。您可以通過其@id XML參數引用視圖屬性,這些屬性將與XML文件中聲明的名稱相同。
其他整潔的功能
1.擴展功能和構建器
items = StoreInfo().apply { storeItems = fetchItems() }.let { manager.process(it) }
container.apply {
removeAllViews()
items.forEach { addView(ShopItemView(context).withData(it)) }
}
fun ShopItemView.withData(item: StoreItem): ShopItemView {
title = item.title
image = item.image
Brand.findById(item.id)?.let { brandName = it.name }
}</code></pre>
apply 、 let 和擴展功能可以輕松地用于創建簡潔的構建器。
2.為初學者快速破解
在最初的前幾天,你經常被一個問題難倒:你不知道如何在Kotlin中寫一個相當簡單的Java表達式。這里有一個簡單的訣竅,就是是在Java中編寫一段代碼,然后將其粘貼到Kotlin文件中。感謝JetBrains的工程師們,它會自動轉換為Kotlin。 黑客的工作方式就像一個魔術!
3.擺脫不必要的依賴
Kotlin替換了許多第三方庫,如ButterKnife、Google Autovalue、Retrolambda、Lombok和一些RxJava代碼。
總結
作為一個軟件開發團隊,我們面臨的主要挑戰是提供優秀的產品,并有效地完成工作。雖說開始用Kotlin有效的開發軟件,需要你有函數式編程的背景,但是,投入精力去學習是值得的,能給你巨大的回報。我相信,Kotlin是常規Android開發的一個重大改進,能讓我們及時提供錯誤更少的、更加優秀的應用程序。
更新: val 實際上并不意味著“不可變的”,而是“只讀”。
參考文獻
- 《Kotlin 參考手冊》
- 《所以,你要成為一名函數式程序員》
- 《為什么函數式編程很重要》
- 《Java使用不可變對象編程的6大好處》
- 《Anko DSL對Android XML-First》
- 《將應用轉換為純Kotlin的經驗教訓》
- 《結果:應用程序投票選舉:99.8%無故障用戶》
- 《Kotlin:val不意味著不可變,而意味著只讀》
來自:http://www.infoq.com/cn/articles/how-kotlin-become-our-android-develop-language