前言
在Kotlin中,一切都是對象。沒有像Java中那樣的原始基本類型。這個是非常有幫
助的,因為我們可以使用一致的方式來處理所有的可用的類型。
基本類型
當然,像integer,float或者boolean等類型仍然存在,但是它們全部都會作為對象
存在的。基本類型的名字和它們工作方式都是與Java非常相似的,但是有一些不同
之處你可能需要考慮到:
- 數字類型中不會自動轉型。舉個例子,你不能給 Double 變量分配一
個 Int 。必須要做一個明確的類型轉換,可以使用眾多的函數之一:
val i:Int=7
val d: Double = i.toDouble()
- 字符(Char)不能直接作為一個數字來處理。在需要時我們需要把他們轉換為
一個數字:
val c:Char='c'
val i: Int = c.toInt()
- 位運算也有一點不同。在Android中,我們經常在 flags 中使用“或”,所以我
使用"and"和"or來舉例:
// Java
int bitwiseOr = FLAG1 | FLAG2;
int bitwiseAnd = FLAG1 & FLAG2;
// Kotlin
val bitwiseOr = FLAG1 or FLAG2
val bitwiseAnd = FLAG1 and FLAG2
還有很多其他的位操作符,比如 sh1 , shs , ushr , xor 或 inv 。當我
們需要的時候,可以在Kotlin官網查看。
- 字面上可以寫明具體的類型。這個不是必須的,但是一個通用的Kotlin實踐時
省略變量的類型(我們馬上就能看到),所以我們可以讓編譯器自己去推斷出具體的類型。
val i = 12 // An Int
val iHex = 0x0f // 一個十六進制的Int類型
val l = 3L // A Long
val d = 3.5 // A Double
val f = 3.5F // A Float
- 一個String可以像數組那樣訪問,并且被迭代:
val s = "Example"
val c = s[2] // 這是一個字符'a'
// 迭代String
val s = "Example"
for(c in s){
print(c)
}
變量
變量可以很簡單地定義成可變( var )和不可變( val )的變量。這個與Java中使
用的 final 很相似。但是不可變在Kotlin(和其它很多現代語言)中是一個很重要
的概念。
一個不可變對象意味著它在實例化之后就不能再去改變它的狀態了。如果你需要一
個這個對象修改之后的版本,那就會再創建一個新的對象。這個讓編程更加具有健
壯性和預估性。在Java中,大部分的對象是可變的,那就意味著任何可以訪問它這
個對象的代碼都可以去修改它,從而影響整個程序的其它地方。
不可變對象也可以說是線程安全的,因為它們無法去改變,也不需要去定義訪問控
制,因為所有線程訪問到的對象都是同一個。
所以在Kotlin中,如果我們想使用不可變性,我們編碼時思考的方式需要有一些改
變。一個重要的概念是:盡可能地使用 val 。除了個別情況(特別是在Android
中,有很多類我們是不會去直接調用構造函數的),大多數時候是可以的。
之前提到的另一件事是我們通常不需要去指明類的類型,它會自動從后面分配的語
句中推斷出來,這樣可以讓代碼更加清晰和快速修改。我們在前面已經有了一些例
子:
val s = "Example" // A String
val i = 23 // An Int
val actionBar = supportActionBar // An ActionBar in an Activity
如果我們需要使用更多的范型類型,則需要指定:
val a: Any = 23
val c: Context = activity
屬性
屬性與Java中的字段是相同的,但是更加強大。屬性做的事情是字段加上getter加
上setter。我們通過一個例子來比較他們的不同之處。這是Java中字段安全訪問和
修改所需要的代碼:
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
···
Person person = new Person();
person.setName("name");
String name = person.getName();
在Kotlin中,只需要一個屬性就可以了:
public class Person {
var name: String = ""
}
···
val person = Person()
person.name = "name"
val name = person.name
如果沒有任何指定,屬性會默認使用getter和setter。當然它也可以修改為你自定義
的代碼,并且不修改存在的代碼:
public classs Person {
var name: String = ""
get() = field.toUpperCase()
set(value){
field = "Name: $value"
}
}
如果需要在getter和setter中訪問這個屬性自身的值,它需要創建一個 backing
field 。可以使用 field 這個預留字段來訪問,它會被編譯器找到正在使用的并
自動創建。需要注意的是,如果我們直接調用了屬性,那我們會使用setter和getter
而不是直接訪問這個屬性。 backing field 只能在屬性訪問器內訪問。
就如在前面章節提到的,當操作Java代碼的時候,Kotlin將允許使用屬性的語法去
訪問在Java文件中定義的getter/setter方法。編譯器會直接鏈接到它原始的
getter/setter方法。所以當我們直接訪問屬性的時候不會有性能開銷。
Anko是什么?
Anko是JetBrains開發的一個強大的庫。它主要的目的是用來替代以前XML的方式
來使用代碼生成UI布局。這是一個很有趣的特性,我推薦你可以嘗試下,但是我在
這個項目中暫時不使用它。對于我(可能是由于多年的UI繪制經驗)來說使用XML
更容易一些,但是你會喜歡那種方式的。
然而,這個不是我們能在這個庫中得到的唯一一個功能。Anko包含了很多的非常有
幫助的函數和屬性來避免讓你寫很多的模版代碼。我們將會通過本書見到很多例
子,但是你應該快速地認識到這個庫幫你解決了什么樣的問題。
盡管Anko是非常有幫助的,但是我建議你要理解這個背后到底做了什么。你可以在
任何時候使用 ctrl + 點擊 (Windows)或者 cmd + 點擊 (Mac)的方式跳轉
到Anko的源代碼。Anko的實現方式對學習大部分的Kotlin語言都是非常有幫助的。
開始使用Anko
在之前,讓我們來使用Anko來簡化一些代碼。就像你將看到的,任何時候你使用了
Anko庫中的某些東西,它們都會以屬性名、方法等方式被導入。這是因為Anko使
用了擴展函數在Android框架中增加了一些新的功能。我們將會在以后看到擴展函
數是什么,怎么去編寫它。
在 MainActivity:onCreate ,一個Anko擴展函數可以被用來簡化獲取一個
RecyclerView:
val forecastList: RecyclerView = find(R.id.forecast_list)
我們現在還不能使用庫中更多的東西,但是Anko能幫助我們簡化代碼,比如,實例
化Intent,Activity之間的跳轉,Fragment的創建,數據庫的訪問,Alert的創建……
我們將會在實現這個App的過程中學習到很多有趣的例子。
擴展函數
擴展函數數是指在一個類上增加一種新的行為,甚至我們沒有這個類代碼的訪問權
限。這是一個在缺少有用函數的類上擴展的方法。在Java中,通常會實現很多帶有
static方法的工具類。Kotlin中擴展函數的一個優勢是我們不需要在調用方法的時候
把整個對象當作參數傳入。擴展函數表現得就像是屬于這個類的一樣,而且我們可
以使用 this 關鍵字和調用所有public方法。
舉個例子,我們可以創建一個toast函數,這個函數不需要傳入任何context,它可以
被任何Context或者它的子類調用,比如Activity或者Service:
fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, message, duration).show()
}
這個方法可以在Activity內部直接調用:
toast("Hello world!")
toast("Hello world!", Toast.LENGTH_LONG)
當然,Anko已經包括了自己的toast擴展函數,跟上面這個很相似。Anko提供了一
些針對 CharSequence 和 resource 的函數,還有兩個不同的toast和longToast方
法:
toast("Hello world!")
longToast(R.id.hello_world)
擴展函數也可以是一個屬性。所以我們可以通過相似的方法來擴展屬性。下面的例
子展示了使用他自己的getter/setter生成一個屬性的方式。Kotlin由于互操作性的特
性已經提供了這個屬性,但理解擴展屬性背后的思想是一個很不錯的練習:
public var TextView.text: CharSequence
get() = getText()
set(v) = setText(v)
擴展函數并不是真正地修改了原來的類,它是以靜態導入的方式來實現的。擴展函
數可以被聲明在任何文件中,因此有個通用的實踐是把一系列有關的函數放在一個
新建的文件里。
這是Anko功能背后的魔法。現在通過以上,你也可以自己創建你的魔法。
執行一個請求
對于感受我們要實現的想法而言,我們目前的文本是很好開始,但是現在是時候去
請求一些顯示在RecyclerView上的真正的數據了。我們將會使用OpenWeatherMap
API來獲取數據,還有一些普通類來現實這個請求。多虧Kotlin非常強大的互操作
性,你可以使用任何你想使用的庫,比如用Retrofit來執行服務器請求。當只是執行
一個簡單的API請求,我們可以不使用任何第三方庫來簡單地實現。
而且,如你所見,Kotlin提供了一些擴展函數來讓請求變得更簡單。首先,我們要
創建一個新的Request類:
public class Request(val url: String) {
public fun run() {
val forecastJsonStr = URL(url).readText()
Log.d(javaClass.simpleName, forecastJsonStr)
}
}
我們的請求很簡單地接收一個url,然后讀取結果并在logcat上打印json。實現非常
簡單,因為我們使用 readText ,這是Kotlin標準庫中的擴展函數。這個方法不推
薦結果很大的響應,但是在我們這個例子中已經足夠好了。
如果你用這些代碼去比較Java,你會發現我們僅使用標準庫就節省了大量的代碼。
比如 HttpURLConnection 、 BufferedReader 和需要達到相同效果所必要的迭
代結果,管理連接狀態、reader等部分的代碼。很明顯,這些就是場景背后函數所
作的事情,但是我們卻不用關心。
為了可以執行請求,App必須要有Internet權限。所以需要
在 AndroidManifest.xml 中添加:
<uses-permission android:name="android.permission.INTERNET" />
在主線程以外執行請求
如你所知,HTTP請求不被允許在主線程中執行,否則它會拋出異常。這是因為阻
塞住UI線程是一個非常差的體驗。Android中通用的做法是使用 AsyncTask ,但是
這些類是非常丑陋的,并且使用它們無任何副作用地實現功能也是非常困難的。如
果你使用不小心, AsyncTasks 會非常危險,因為當運行到 postExecute 時,如
果Activity已經被銷毀了,這里就會崩潰。
Anko提供了非常簡單的DSL來處理異步任務,它滿足大部分的需求。它提供了一個
基本的 async 函數用于在其它線程執行代碼,也可以選擇通過調用 uiThread 的
方式回到主線程。在子線程中執行請求如下這么簡單:
async() {
Request(url).run()
uiThread { longToast("Request performed") }
}
UIThread 有一個很不錯的一點就是可以依賴于調用者。如果它是被一
個 Activity 調用的,那么如果 activity.isFinishing() 返回 true ,
則 uiThread 不會執行,這樣就不會在Activity銷毀的時候遇到崩潰的情況了。
假如你想使用 Future 來工作, async 返回一個Java Future 。而且如果你需
要一個返回結果的 Future ,你可以使用 asyncResult 。
真的很簡單,對吧?而且比 AsyncTasks 更加具有可讀性。現在,我僅僅給請求
發送了一個url,來測試我們是否可以正確接收內容,這樣我們才能在Activity中把它
畫出來。我很快會講到怎么去進行json解析和轉換成app中的數據類,但是在我們
繼續之前,學習什么是數據類也是很重要的。
檢查代碼并審查url請求和包結構的代碼。你可以運行app并且確保你可以在打印的
json日志和請求完畢之后的toast。