Kotlin是那家以做IDE起家的大名鼎鼎的Jetbrains公司開發的一門程式設計語言,旨在Java放棄免費之後,成為Java的替代品。Google在2019年的Google I/O大會上宣告了Kotlin First戰略,使得Kotlin成為Android開發的“御用程式設計語言”。Kotlin對於Java開發人員來講應當會十分熟悉。
函式定義
Kotlin的函式定義有幾種方式。開始之前,先讓我插一個關於函式引數的小tip:變長引數。使用vararg
來定義變長引數。使用vararg
修飾的引數,可以傳入任意長度的該類型引數。
傳統寫法
1 | fun sum(a: Int, b: Int): Int { |
無回傳值,可指定回傳值類型為Uint
,類似於void
。Uint
也可以省略不寫。
簡化寫法
當一個函式不是public函式,且只有一行表達式作為回傳值,那麼可以使用簡化寫法:
1 | fun sum(a: Int, b: Int) = a + b |
回傳值的類型會自動推斷。如果是public函式的話,則必須指定回傳值類型:
1 | public func sum(a: Int, b: Int) : Int = a + b |
Lambda
直接看例子,也比較簡單。Lambda講到底就是所謂的匿名函式罷了。1
2
3
4val plusFunc: (Int, Int) -> Int = { x, y ->
println("Hello, world.")
x + y
}
這段怎麼理解呢?首先,我定義了一個lambda:1
2
3
4{ x, y ->
println("Hello, world.")
x + y
}
然後將它交付給了常數plusFunc,常數plusFunc的類型是函式類型,具體是(Int, Int) -> Int
。
樣板字面值
以下的例子會很好理解:
1 | var a = 1 |
空安全
類似於Java和Swift中的Optional。以下的例子有對可空值的處理:
1 | // ?表示可空 |
陣列
陣列用Array實現:
1 | var arr: Array<Int> = arrayOf(1, 2, 3) |
條件控制
if-else
和Java最大的一點不同是,if-else是有回傳值的。
1 | var result = if (a > b) a else b |
這可以被方便地用來替代Java中的三元運算子?:
。
when-else
類似於Java中的switch-case
,只是不再需要break
。
1 | when (x) { |
或者直接進行大規模的判斷:
1 | when { |
迴圈控制
for迴圈
Kotlin中的for迴圈是for-in
迴圈。
1 | // Only value |
while迴圈和do-while迴圈
與C家族語言相同。
迴圈控制標籤
之前在用Java的時候,當我們碰到巢狀迴圈,想要從內層迴圈直接break掉外層迴圈,我們需要在內外層各寫一個break。然而在Kotlin中,我們可以透過標籤方便地達成這一點。
1 | loop@ for (i in 1..100) { |
即如果滿足條件,跳出loop@
所標示的迴圈。
型別和物件
作為一個物件導向程式設計語言,型別和物件自然是重中之重。
使用class
來定義型別。
1 | class MyClass { |
可以使用預設建構子來初始化物件。
1 | val myClass = MyClass() |
不需要new
。
建構子
Kotlin中的建構子和Java有非常大的不同。每一個Kotlin型別可以包含有一個主建構子和若干次建構子。
主建構子
主建構子的引數直接與型別名稱放在一起:
1 | class MyClass constructor(name: String) { |
如果主建構子沒有任何修飾,則可以省略constructor
。
主建構子的函式體放在init{}
段中:
1 | class MyClass(name: String) { |
如果希望把主建構子的引數作為型別的成員變數,可以採用簡易寫法:
1 | class MyClass(var name: String) { |
透過給引數添加var
或val
,可將它們變為成員變數。上述寫法等同於:
1 | class MyClass(name: String) { |
次建構子
次建構子在一個型別中可以有若干。使用constructor() {}
段來定義次建構子:
1 | class MyClass { |
注意,如果型別有主建構子,則任何次建構子都需要直接或間接代理主建構子。要實現這一點,可以使用this()
:
1 | class MyClass(name: String) { |
上述次建構子是說:使用this
呼叫主建構子,並將次建構子的引數name
作為主建構子的第一個引數傳送給主建構子,從而執行主建構子的建構過程。
也就是說,主建構子一旦定義,則必須被呼叫。
型別修飾元
修飾元
- abstract:抽象型別。只有抽象型別中才可以定義抽象和抽象方法。有了該修飾元即無須
open
。 - final:型別不可繼承,預設值。
- enum:枚舉型別。
- open:型別可繼承。
- annotation:標註型別。
權限修飾元
- public
- private
- protected
- internal
巢狀型別
可以將一個型別放在另一個型別裡面,組成巢狀型別。
1 | class Outer { |
內部型別
內部型別也是將一個型別放到另一個型別中的,但是和巢狀型別不同的是,內部型別可以訪問到外部型別的成員和成員方法。
1 | class Outer { |
匿名內部型別
匿名內部型別必須在初始化的時候複寫其中的方法:
1 | class Test { |
Getter和Setter
這裡講的Getter和Setter應該區別於Java中的。在Java中,Getter和Setter是用來將private的成員變數開放給外界來訪問和修改的控制器。在Kotlin中你仍然可以透過這種方法來實現這一點。
我們在這裡講的Getter和Setter是不同的概念。在Kotlin中,型別中所定義的任何變數,都會自動生成一個get()
和一個set()
,每一個常數,都會自動生成一個get()
。所以當我們訪問或修改它們的時候,實際上是透過了一層get()
或一層set()
來執行的訪問。因此,這兩個函式是可以客製化的。
1 | var lastName: String = "chen" |
這樣子,當外界讀取lastName的時候,就會自動讀取到全部大寫的字串。
我們也可以客製化Setter:
1 | var no: Int = 100 |
這裡的field
代表get到的的值。這個變數只能夠在Getter和Setter中使用,由Kotlin自動產生。
繼承與複寫
物件導向三大內容:封裝、繼承、多型。
Kotlin中的繼承有點子麻煩。首先,任何沒有open
或abstract
修飾的東西都是不可以被繼承的。
1 | class Animal { |
其次,如果基本型別有主建構子,則衍生型別必須在繼承的時候立即實現主建構子。
當衍生類別有主建構子的時候,必須在繼承的時候呼叫基本類別的主建構子:
1 | open class Animal(category: String) { |
而如果衍生型別沒有主建構子,則需要在每一個次建構子使用super
來呼叫基本型別的主建構子:
1 | open class Animal(category: String) { |
複寫
基底型別中帶有open
的內容允許被複寫,abstract的抽象內容必須被複寫。或者在多繼承體系中,相同名稱的內容也必須被複寫。
1 | open class Person{ |
當呼叫study()
函式的時候,呼到的是Student的函式。該函式透過super.study()
呼叫了父型別的函式。
的複寫也是一樣的道理。需要注意的是,你可以用一個var複寫一個val,但是無法用一個val複寫一個var。
介面實作
和Java一樣,Kotlin可以實現對多個介面的實作。
介面中的只能是abstract的,被實作後必須複寫,方法則可以有預設的實作。如果方法沒有預設實作,也必須被複寫。
1 | interface MyInterface { |
Kotlin擴充函式
擴充函式可以算是Kotlin的一項標誌性的創舉。它可以在一個已經定義好的型別外部——注意喔,是外部——去添加成員函式。但是這個改變並不會影響到型別本身。
這樣子,我們的型別就可以變得更加靈活了。
我們來看一個例子:
1 | class Jump { |
範型
和Java一樣,Kotlin也提供了範型支援。下面是宣告一個範型型別:
1 | class Box<T>(t: T) { |
範型上界約束
在Java中,我們會有到對範型的上界約束。在Kotlin中也是一樣的支援。
比如,需要範型實作Comparable介面,可以這樣寫:
1 | class MyClass<T : Comparable<T>>(t: T) { |
資料型別、密封型別、枚舉型別
資料型別
只含有資料的類。
1 | data class Person(var name: String, var age: Int) |
須滿足以下條件:
- 主建構子至少包含一個引數。
- 所有的主建構子的引數必須標識為val或var
- 不可以宣告為abstract, open, sealed或inner
- 不能繼承其他型別(但是可以實作介面)
密封型別
給我的感覺就是,這個東西是一個空空的型別,用來代指機種特定的型別。
1 | sealed class Expr |
枚舉型別
Kotlin中的枚舉型別和其他語言都不是很一樣。具體來講,枚舉型別中的每一個枚舉,都是這個枚舉型別的一個物件。
比如:
1 | enum class RGB(val rgb: Int) { |
甚至你可以:
1 | enum class ProtocolState { |