你們好,我是 Michael Pardo,今天我要給你們展現一下 Kotlin 這門語言,同時看看他如何讓你在 Android 開發的時候更開心,更有效率。javascript
Kotlin 是一個基於 JVM 實現的靜態語言。Kotlin 是 JetBrains 創造並在持續維護這門語言,對,就是那個創造了 Android Studio 和 IntelliJ 的公司。html
Kotlin 有幾個核心的目標:java
爲何不等 Java 8?android
咱們來看看 Java 和 Android 的歷史以及他們的關係。 在 2006 年,Java 6 發佈了。幾年以後,Android 1 的 Alpha 版本發佈了,四年後,Java 7 發佈了。Android 在 2 年後緊隨其後的開始支持 Java 7。去年,Java 8 又發佈了。express
你想一想,你何時才能用上 Java 8? 可能你學的很快,而後就能用上 Java 8。可是 Android 怎麼說都得幾年後才能開始支持 Java 8,你們適應 Java 8 又須要很長時間。Android 如今的碎片化很嚴重,Java 7 只支持 API 19 及以上。若是用了 Java 7,那你的 App 用戶羣一會兒就少了一半。即使咱們如今有了 Java 8,100% 的覆蓋到了全部的用戶設備上,可是 Java 自己仍是有些問題的。設計模式
咱們來看看 Java 的一些問題。數組
咱們來探索下 Kotlin 是如何解決上面的提到的這些問題的。安全
剛纔咱們提到過的這些缺陷,Kotlin 一般直接移除了那些特性。同時它也加了一些新的特性:app
咱們將在這篇文章裏說起以上大多數特性。Kotlin 之因此能跟隨者 JVM 的生態系統不斷地進步,是由於他沒有任何限制。它編譯出來的正是 JVM 字節碼。在 JVM 看來,它就跟其餘語言同樣樣的。事實上,若是你在 IntelliJ 或者 Android Studio 上用 Kotlin 的插件,它自帶裏一個字節碼查看器,能夠顯示每一個方法生成的 JVM 字節碼。dom
咱們來看看基本語法。下面是一個最簡單的,用 Kotlin 書寫的 Hello World:
fun main(args: Array<String>): Unit { println("Hello, World!") }
只有一個函數和一個 print 語句。不須要包聲明和類引用聲明。這就是一個 Kotlin Hello World 程序的全部代碼。聲明函數的關鍵字是 fun
,fun
後面跟的是函數的名稱,而後括號包裹起來的是函數參數,這個跟 Java 相似。
然而,在 Kotlin 裏,得把參數名放在前面,參數類型放在後面,用一個冒號隔開。函數的返回類型在最後,這個跟 Java 放在前面形式不太同樣。若是一個函數沒有返回任何類型,能夠返回一個 Unit
類型,固然也能夠省略。調用 Kotlin 標準庫中的函數 println
就能打印 Hello World 出來,實際上它最終調用了 Java 的 system.out.println
。
fun main(args: Array<String>) { println("Hello, World!") }
接下來,咱們來從 「Hello World」 中提取 「World」 這個詞,並把這個詞放到一個變量中。var
關鍵字後跟的是變量的名稱。Kotlin 支持字符串內插入變量,只用在字符串內用 $
符號開頭,隨後跟上輸出變量的變量名便可,就像以下這樣:
fun main(args: Array<String>) { var name = "World" println("Hello, $name!") }
隨後,咱們來檢查咱們是否給 main 函數傳遞了參數。先來判斷這個字符串數組是否是空,若是不爲空,咱們把第一個字符串分配給 name
變量。Kotlin 裏有個 val
類型的聲明方法,相似 Java 裏的 final
,也就是常量。
fun main(args: Array<String>) { val name = "World" if (args.isNotEmpty()) { name = args[0] } println("Hello, $name!") }
在咱們編譯這個程序的時候,咱們遇到一個問題:沒法從新分配新的值給一個常量。一種解決方法是用內聯的 if-else 方法。Kotlin 裏的多數的代碼塊都支持返回值。若是語句進入了 if 代碼塊兒,也就是說 args 非空,那麼就返回 arg[0]
,不然返回 「World」。 if-else 語句結束後,就直接賦值給咱們以前聲明的 name
常量,下面的例子就是條件賦值代碼塊:
fun main(args: Array<String>) {. val name = if (args.isNotEmpty()) { args[0] } else { "World" } println("Hello, $name!") }
咱們能夠把上面的代碼用一行來書寫,看起來有點像 Java 裏的三目運算符。移除掉那些大括號後,看起至關漂亮:
val name = if (args.isNotEmpty()) args[0] else "World"
咱們來看看類。類的定義要經過 class
關鍵字,跟 Java 裏的同樣,關鍵字後是類名。Kotlin 有一個主構造函數,咱們能夠直接將構造函數參數列表寫在類的聲明處,還能夠直接用 var
或者 val
關鍵字將參數聲明爲成員變量(又稱:類屬性),以下:
class Person(var name: String)
繼續以前的例子,有了主構造函數之後,咱們就再也不須要成員變量賦值語句了。在 Kotlin 裏建立實例的時候,沒必要使用 new
關鍵字。你只須要指明建立的類型名就能夠建立實例了。
class Person(var name: String) fun main(args: Array<String>) { val person = Person("Michael") println("Hello, $name!") }
很容易發現,字符串插值其實是錯誤的,由於 name
指向的是一個不存在的變量了。咱們能夠用剛纔提到的 字符串插值表達式 ,即用 $
符號和大括號包裹想要插入的變量,來修復這個問題:
class Person(var name: String) fun main(args: Array<String>) { val person = Person("Michael") println("Hello, ${person.name}!") }
下面是 enum
類。枚舉跟 Java 裏的枚舉很像。定義一個枚舉的方法以下:
enum class Language(val greeting: String) { EN("Hello"), ES("Hola"), FR("Bonjour") }
咱們來給 Person 類增長一個叫 lang 的屬性,表明一我的的所說的語言。
class Person(var name: String, var lang: Language = Language.EN)
Kotlin 支持參數默認值,如上:language 的默認值就是 Language.EN
,這樣就能夠在建立實例的時候忽略這個參數,除非你要改變 language 的屬性值。咱們來把這個例子變得更面向對象一些,給 person 增長一個打招呼的方法,簡單地輸出特定語言打招呼的方法還有人名:
enum class Language(val greeting: String) { EN("Hello"), ES("Hola"), FR("Bonjour") } class Person(var name: String, var lang: Language = Language.EN) { fun greet() = println("${lang.greeting}, $name!") } fun main(args: Array<String>) { val person = Person("Michael") person.greet() }
如今在 main 函數裏調用 person.greet()
方法,看看是否是很酷?!
val people = listOf( Person("Michael"), Person("Miguel", Language.SP), Person("Michelle", Language.FR) )
咱們能夠用標準庫函數 listOf
方法建立一個 person
列表。遍歷這些 person
能夠用 for-in
關鍵字:
for (person in people) { person.greet() }
隨後,咱們能夠在每次遍歷的時候執行 person.greet()
方法,甚至能夠更簡單,直接調用 people 集合的擴展方法 forEach
,傳入一個 lambda 表達式,在表達式裏用 it
表明每次遍歷到的person
對象,而後調用它們的 greet
方法。
people.forEach { it.greet() }
咱們來建立兩個新的類,每一個都傳入一個默認的語系。咱們能夠再也不像剛纔那樣重複聲明,能夠直接用繼承的方法來實現。下面是一個擴展版本的 Hello World。展現了不少 Kotlin 的特性:
enum class Language(val greeting: String) { EN("Hello"), ES("Hola"), FR("Bonjour") } open class Person(var name: String, var lang: Language = Language.EN) { fun greet() = println("${lang.greeting}, $name!") } class Hispanophone(name: String) : Person(name, Language.ES) class Francophone(name: String) : Person(name, Language.FR) fun main(args: Array<String>) { listOf( Person("Michael"), Hispanophone("Miguel"), Francophone("Michelle") ).forEach { it.greet() } }
下面,咱們來看看 Kotlin 在 Java 之上加了哪些更好用的新特性。
你可能在其餘語言中看到過類型推導。在 Java 裏,咱們須要本身聲明類型,變量名,以及數值。在 Kotlin 裏,順序有些不同,你先聲明變量名,而後是類型,而後是分配值。不少狀況下,你不須要聲明類型。一個字符串字面量足以指明這是個字符串類型。字符,整形,長整形,單浮點數,雙浮點數,布爾值都是能夠無需顯性聲明類型的。
var string: String = "" var string = "" var char = ' ' var int = 1 var long = 0L var float = 0F var double = 0.0 var boolean = true var foo = MyFooType()
只要 Kotlin 能夠推導,這個規則一樣適用與其餘一些類型。一般,若是是局部變量,當你在聲明一個值或者變量的時候你不須要指明類型。在一些沒法推導的場景裏,你才須要用完整的聲明變量語法指明變量類型。
Kotlin 一個強大的特性是空安全。咱們來看幾個例子:
String a = null; System.out.println(a.length());
在 Java 裏,聲明一個 string 類型,賦一個 null 給這個變量。一旦咱們要打印這個字符串的時候,會在運行時曝出空指針錯誤,由於咱們在嘗試去讀一個空值。下面是這個問題的 kotlin 寫法,咱們定義一個空值,可是在咱們嘗試操做它以前,Kotlin 的編譯器就告訴了咱們問題所在:
val a:String = null
曝出的錯誤是:咱們在嘗試着給一個非空類型分配一個 null。在 Kotlin 的類型體系裏,有空類型和非空類型。類型系統識別出了 string 是一個非空類型,而且阻止編譯器讓它以空的狀態存在。想要讓一個變量爲空,咱們須要在聲明後面加一個 ?
號,同時賦值爲 null。
val a: String? = null println(a.length())
如今,咱們修復了這個問題,繼續向下:就像在 Java 裏同樣,咱們嘗試打印 stirng 的長度,可是咱們遇到了跟 Java 同樣的問題,這個字符串有可能爲空,不過幸虧的是:Kotlin 編譯器幫助咱們發現了這個問題,而不像 Java 那樣,在運行時爆出這個錯誤。
編譯器在長度輸出的代碼前中止了。想要讓編譯器編譯下去,咱們得在調用 length 方法的時候考慮到可能爲空的狀況,要麼賦值給這個 string,要麼用一個問號在變量名後,這樣,代碼執行時在讀取變量的時候檢查它是否爲空。
val a: String? = null println(a?.length())
若是值是空,則會返回空。若是不是空值,就返回真實的值。print 遇到 null 會輸出空。
int length = a != null ? a.length() : -1
上面的代碼你可能在 Java 裏見到過。用三目運算符取值,檢查是否爲空,若是爲空則返回真實的長度,不然返回 -1,Kotlin 裏又相同的實現:
var length = if(a!= null) a.length() else -1
若是 a
不是 null, 那麼就能夠直接讀值,不然返回默認值。這裏用 elvis操做符 實現的簡寫:
var length = a?.length() ?: -1
咱們用 ?
號作了一個內聯空檢查。若是你還記得剛纔我說的,若是 a 是 null,第一個 ?
表達式就會返回 null ,若是 elivs 操做符
左側是空,那麼他就會返回右側,不然直接返回左側的值。
Kotlin 支持類型智能轉換的特性。 若是一個局部對象傳入一個類型檢查,你能夠直接經過這個類型來操做,而不須要再本身作轉換,看下面的例子你就明白了:
if (x is String) { print(x.length()) }
咱們檢查了 x
是否是一個字符串,若是是,就打印它的長度。作類型檢查,咱們須要用到 is
關鍵字,其實跟 Java 裏的 instanceOf
同樣。道理很簡單,咱們既然經過了類型檢查,咱們就能把它當作這個類型來使用。
if (x !is String) { return } print(x.size())
逆操做也沒有問題。上面這個例子檢查了是不是一個非字符串變量。若是不是,則直接返回。在咱們判斷了之後就能夠認爲它是一個字符串了,咱們也無需在作顯式的類型轉換。
if (x !is String || x.size() == 0) { return }
上面的例子裏,咱們檢查了一個字符串是不是一個非字符串變量,若是左側的值是 false,就會調用右側的 or 判斷。when
語句也適用上面的規則,when 實際上是一個加強版的 switch
。
when(x) { is Int -> print(x + 1) is String -> print(x.size() + 1) is Array<Int> -> print(x.sum()) }
咱們只要作了類型檢查,類型一切都會自動轉換。從類型檢查到類型自動轉換,就是 Kotlin 的智能轉換。
不少語言都有字符串模板和字符串插值。下面的例子大概就是你在 Java 裏常常用到的:
val apples = 4 println("I have " + apples + " apples.")
你能夠把 apples
變量和其餘字符串串聯起來。用更符合 Kotlin 風格的方式,你能夠用插值的方法,在字符串中用 $
符號前綴加變量名來表明這個字符串內容。
val apples = 4 println("I have $apples apples.")
你也能夠用下面的表達式:
val apples = 4 val bananas = 3 println("I have $apples apples and " + (apples + bananas) + " fruits.") // Java-esque println("I have $apples apples and ${apples+bananas} fruits.") // Kotlin
在 Java 中,可能最多見的方案是在須要顯示個數的地方,用加號操做蘋果和香蕉的個數,而後將字符串都串起來。但在 Kotlin 中,能夠用前綴 $
符號加上大括號將操做語句包裹起來表明操做語句的結果。
你可能在其餘的語言裏見到過這樣的表達式。的確, Kotlin 的很多特性是借鑑自其餘語言裏。下面這個表達式:若是 i
大於等於 1,而且小於等於 10,就將其打印出來。咱們檢測的範圍是 1 到 10。
if (1 <= i && i <= 10) { println(i) }
其實咱們能夠用 intRange
函數來完成這個操做。咱們傳入 1 和 10,而後調用 contains
函數來判斷是否在這個範圍裏。咱們打印出 i
便可。
if (IntRange(1, 10).contains(i)) { println(i) }
這個還能夠用擴展函數來實現,1.rangeTo
建立了一個 1 到 10 的 intRange,咱們能夠用 contain 來判斷它。
更完美的而簡潔的寫法,是用下面的操做符:
if(i in 1..10) { ... }
..
是 rangeTo
的一個別名,它實際背後工做原理仍是 rangeTo
。
咱們還可遍歷一個區間,好比:能夠用 step
關鍵字來決定每次遍歷時候的跳躍幅度:
for(i in 1..4 step 2) { ... }
也能夠逆向迭代,或者逆向遍歷而且控制每次的 step:
for (i in 4 downTo 1 step 2) { ... }
在 Kotlin 裏,也能夠結合不一樣的函數來實現你想要的區間遍歷。能夠遍歷不少不一樣的數據類型,好比建立 strings 或者你本身的類型。只要符合邏輯就行。
不少語言已經支持了高階函數,好比 Java 8,可是你並不能用上 Java 8。若是你在用 Java 6 或者 Java 7,下面的例子實現了一個具備過濾功能的函數:
public interface Function<T, R> { R call(T t); } public static <T> List<T> filter(Collection<T> items, Function<T, Boolean> f) { final List<T> filtered = new ArrayList<T>(); for (T item : items) if (f.call(item)) filtered.add(item); return filtered; } filter(numbers, new Function<Integer, Boolean>() { @Override public Boolean call(Integer value) { return value % 2 == 0; } });
咱們首先要聲明一個函數接口,接受參數類型爲 T
,返回類型爲 R
。咱們用接口中的方法遍歷操做了目標集合,建立了一個新的列表,把符合條件的過濾了出來。
fun <T> filter(items: Collection<T>, f: (T) -> Boolean): List<T> { val filtered = arrayListOf<T>() for (item in items) if (f(item)) filtered.add(item) return filtered }
上面的代碼是在 Kotlin 下的實現,是否是簡單不少?咱們調用的時候以下:kotlin filter(numbers, { value -> value % 2 == 0 })
你可能也發現了,咱們沒有定義任何的函數接口,這是由於在 Kotlin 中,函數也是一種數據類型。看到 f:(T) -> Boolean
這個語句了嗎?這就是函數類型做爲參數的寫法,f
是函數別名,T
是函數接受參數,Boolean
是這個函數的返回值。定義完成後,咱們隨後就能跟調用其餘函數同樣調用 f
。調用 filter
的時候,咱們是用 lambda 表達式來傳入過濾函數的,即:{value ->value % 2 = 0}
。
因爲函數類型參數是能夠經過函數聲明的簽名來推導的,因此其實還有下面的一種寫法,大括號內就是第二個參數的函數體:
filter(numbers) { it % 2 == 0 }
內聯函數和高階函數常常一塊兒見到。在某些場景下,當你用到泛型的時候,你能夠給函數加上inline
關鍵字。在編譯時,它會用 lambda 表達式替換掉整個函數,整個函數的代碼會成爲內聯代碼。
若是代碼是這樣的:
inline fun <T> filter(items: Collection<T>, f: (T) -> Boolean): List<T> { val filtered = arrayListOf<T>() for (item in items) if (f(item)) filtered.add(item) return filtered } filter(numbers) { it % 2 == 0 }
由 inline
關鍵字在編譯後會變成以下這樣:
val filtered = arrayListOf<T>() for (item in items) if (it % 2 == 0) filtered.add(item)
這也意味着咱們能實現一些常規函數實現不了的。好比:下面這個函數接受一個 lambda 表達式,但並不能直接返回:
fun call(f: () -> Unit) { f() } call { return // Not allowed }
可是若是咱們的函數變成內聯函數,如今咱們就能直接返回了,由於它是內聯函數,會自動和其餘代碼混合在一塊兒:
inline fun call(f: () -> Unit) { f() } call { return // Now allowed }
內聯函數也容許用 reified
類型。下面這個例子就是一個真實場景下的函數,經過一個 View 尋找類型爲 T
的父元素:
inline fun <T : Any> View.findViewParent(): T? { var parent = getParent() while (parent != null && parent !is T) { parent = parent.getParent() } return parent as T // Cast warning }
這個函數還有些問題。因爲泛型類型被擦除了,因此咱們沒法檢測類型,即使咱們手工來作檢查,依然會出現 warning。
解決方案是:咱們給函數參數類型加上 reified
關鍵字。由於函數會被編譯成內聯代碼,因此咱們如今就能手工檢查類型消除警告了:
inline fun <reified T : Any> View.findViewParent(): T? { var parent = getParent() while (parent != null && parent !is T) { parent = parent.getParent() } return parent as T // Type cast allowed }
函數擴展是 Kotlin 最強大的特性之一。下面是一個工具函數,檢測 App 是否運行在 Lollipop 或者更高的 Api 之上,它接受一個整數參數:
public fun isLollipopOrGreater(code: Int): Boolean { return code >= Build.VERSION_CODES.LOLLIPOP }
經過 被擴展類型.函數
的寫法,就能將函數變成被擴展類型的一部分,寫法以下:
public fun Int.isLollipopOrGreater(): Boolean { return this >= Build.VERSION_CODES.LOLLIPOP }
咱們不在須要參數,想要在函數體內調用整數對象須要用 this
關鍵字。下面就是咱們的調用方法,咱們能夠直接在整數類型上調用這個方法:
16.isLollipopOrGreater()
函數擴展能夠是任何整形,字面量或者包裝類型,也能夠在標記爲 final
的類上作相似操做。由於擴展函數不是真的給類增長代碼,任何人都沒有辦法去修改一個類,它其實是建立了一個靜態方法,用語法糖來讓擴展函數看着像是類自帶的方法同樣。
Kotlin 在 Java 集合中充分利用了擴展函數,這有一個例子操做集合:
final Function<Customer, Order> customerMapper = // ... final Function<Order, Boolean> orderFilter = // ... final Function<Order, Float> orderSorter = // ... final List<Order> vipOrders = sortBy(filter(map(customers, customerMapper), orderFilter), orderSorter);
咱們對一個 customer 集合,執行了 map, filter, 以及 sort 操做。嵌套的寫法混亂並且難以閱讀。下面是標準庫的擴展函數寫法,是否是簡潔了不少:
val vipOrders = customers .map { it.lastOrder } .filter { it.total >= 500F } .sortBy { it.total }
Kotlin 把屬性也變成了語言特性。
class Customer { private String firstName; private String lastName; private String email; public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public String getEmail() { return email; } public void setFirstName(String firstName) { this.firstName = firstName } public void setLastName(String lastName) { this.lastName = lastName } public void setEmail(String email) { this.email = email } }
上面是一個典型的 Java bean 類。可看到不少成員變量,和不少 getter
, setter
方法,這但是隻有三個屬性的時候,就生成了這麼多代碼。來看看 Kotlin 的寫法:
class Customer { var firstName: String = // ... var lastName: String = // ... var email: String = // ... }
你只須要將成員變量定義成一個變量便可,默認是 public
的。編譯器會自動生成 getter
和setter
方法。
Kotlin 中,類能夠擁有多個構造函數,這一點跟 Java 相似。但你也能夠有一個主構造函數。下面的例子是咱們從上面的例子裏衍生出來的,在函數頭裏添加了一個主構造函數:
在主構造函數裏,能夠直接用這些參數變量賦值給類的屬性,或者用構造代碼塊來實現初始化。
class Customer(firstName: String, lastName: String, email: String) { var firstName: String var lastName: String var email: String init { this.firstName = firstName this.lastName = lastName this.email = email } }
固然,更好的方法是:直接在主構造函數裏定義這些屬性,定義的方法是在參數名前加上 var
或者 val
關鍵字,val
是表明屬性是常量。
class Customer( var firstName: String, var lastName: String, var email: String)
你可能常常會用到單例設計模式。好比一個 Logger 類,在 Java 裏,有多種實現單例的寫法。
在 Kotlin 裏,你只要在 package 級別建立一個 object 便可!不論你在什麼域裏,你均可以像單例同樣調用這個 object。
object Singleton
好比下面是一個 looger 的寫法:
object Logger { val tag = "TAG" fun d(message: String) { Log.d(tag, message) } }
你能夠直接經過 Logger.D
的方法來調用 D
函數,它在任何地方都是可用的,並且始終只有一個實例。
Kotlin 移除了 static
的概念。一般用 companion object
來實現相似功能。你可能時常會看到一個 Activity 有一個 靜態類型的 string,名叫 tag
,和一個啓動 Activity 的靜態方法。Java 中的實現以下:
class LaunchActivity extends AppCompatActivity { public static final String TAG = LaunchActivity.class.getName(); public static void start(Context context) { context.startActivity(new Intent(context, LaunchActivity.class)); } }
在 Kotlin 下的實現以下:
class LaunchActivity { companion object { val TAG: String = LaunchActivity::class.simpleName fun start(context: Context) { context.startActivity(Intent(context, LaunchActivity::class)) } } } Timber.v("Starting activity ${LaunchActivity.TAG}") LaunchActivity.start(context)
有了 companion object
後,就跟類多了一個單例的對象和方法同樣。
委託是一個你們都知道的設計模式,Kotlin 把委託視爲很重要的語言特性。下面是一個在 Java 中典型的委託寫法:
public class MyList<E> implements List<E> { private List<E> delegate; public MyList(List<E> delegate) { this.delegate = delegate; } // ... public E get(int location) { return delegate.get(location) } // ... }
咱們有一個本身的 lists 實現,經過構造函數將一個 list 存儲起來,存在內部的成員變量裏,而後在調用相關方法的時候再委託給這個內部變量。下面是在 Kotlin 裏的實現:
class MyList<E>(list: List<E>) : List<E> by list
用 by
關鍵字,咱們實現了一個存儲 E
類型的 list,在調用 List
相關的方法時,會自動委託到 list 上。
譯者注:參考 Kotlin 官方文檔瞭解更多。
這個多是一個比較容易讓人迷惑的主題。首先,咱們用一個協變數組來開始咱們的例子,下面的代碼可以很好的編譯:
String[] strings = { "hello", "world" }; Object[] objects = strings;
string 數組能夠正常的賦值給一個 object 數組。可是下面的不行:
List<String> strings = Arrays.asList("hello", "world"); List<Object> objects = strings;
你不能分配一個 string 類型的 list 給一個 object 類型的 list。由於 list 之間是沒有繼承關係的。若是你編譯這個代碼,會獲得一個類型不兼容的錯誤。想要修復這個錯誤,咱們得用到 Java 中的點變型(use-site variance)去聲明,所謂的點變型就是在聲明 list 可接受類型的時候,用extends
關鍵字給出參數類型的可接受類型範圍,好比相似以下的例子:
譯者注:點變型只是一個名字,不要太在乎爲何叫這個,簡單理解就是相似通配符原理,具體能夠查看這個維基頁面。
public interface List<E> extends Collection<E> { public boolean addAll(Collection<? extends E> collection); public E get(int location); }
addAll
方法能夠接受一個參數,參數類型爲全部繼承自 E
的類型,這不是一個具體類型,而是一個類型範圍。每次調用 get
方法時,依然返回類型 E
。在 Kotlin 中,你能夠用 out
關鍵字來實現相似的功能:
public interface List<out E> : Collection<E> { public fun get(index: Int): E } public interface MutableList<E> : List<E>, MutableCollection<E> { override fun addAll(c: Collection<E>): Boolean }
上面的一系列被稱爲聲明點變型,即在聲明可接受參數的時候,就聲明爲它是可變的。好比上面例子:咱們聲明參數是能夠容許全部繼承自 E
類型的,返回類型也爲 E
的。
如今,咱們有了可變和不可變類型的列表。可變性(variance)
其實很簡單,就是取決於咱們在聲明的時候是動做。
譯者注:其實不論聲明點變型(Declaration-Site Variance) 仍是 點變型(Use-site variance) 都是爲了實現泛型的類型聲明,標註泛型類型可支持的範圍,釐清泛型類型上下繼承邊界。參考Generic Types。
enum class Coin(val cents: Int) { PENNY(1), NICKEL(5), DIME(10), QUARTER(25), } class Purse(var amount: Float) { fun plusAssign(coin: Coin): Unit { amount += (coin.cents / 100f) } } var purse = Purse(1.50f) purse += Coin.QUARTER // 1.75 purse += Coin.DIME // 1.85 purse += Coin.PENNY // 1.86
上面的代碼中,咱們建立了一個硬幣枚舉,每一個硬幣枚舉都表明一個特定數額的硬幣。咱們有一個 Purse
(錢包) 類, 它擁有一個 amount
成員變量,表明錢包裏如今有多少錢。咱們建立了一個叫作 plusAssign
的函數,plusAssign
是一個保留關鍵字。這個函數會重載 +=
操做符,也就是說當你在調用 +=
符號的時候,就會調用這個函數。
隨後,建立一個 purse
實例,能夠直接用 +=
操做來實現給錢包裏放錢進去。
讓你的項目支持 Kotlin 其實很是簡單,你須要讓你的項目裏啓用 gradle 插件,Kotlin Android 插件,拷貝代碼文件,引入標準庫便可。
Q: Kotlin 有沒有什麼缺點?
Michael: Kotlin 的少數特性可能有性能問題,好比 annotation 處理速度很慢。除此以外強烈推薦。
Q: 支持 Unit Test 麼?
Michael: 支持,咱們用Robolectric 和 Mockito 來作測試。
Q: Debug 的時候會很順利嗎?
Michael: Kotlin 生成的只是 JVM 字節碼而已,並且 Kotlin 和 IntelliJ 共存的很是好,畢竟都是一家作的。咱們以前用的 retrolambda,可是他生成的字節碼老是有些小問題。
Q: Kotlin 對 Jack and Jill 的支持如何?
Michael: Jack and Jill 和 Kotlin 不能一塊兒工做。
Q: Kotlin 上用反射怎麼樣?
Michael: Kotlin 上用反射的一大問題是回引入一個很大的庫…… 不過,這些你能夠用 Java 來作。