1. 類
Kotlin 中使用關(guān)鍵字class
聲明類
class Person {
}
類聲明由類名、類頭(指定其類型參數(shù)、主 構(gòu)造函數(shù)等)和由大括號(hào)包圍的類體構(gòu)成。類頭和類體都是可選的; 如果一個(gè)類沒(méi)有類體,可以省略花括號(hào)。
class Person
2.構(gòu)造函數(shù)
在 Kotlin 中的一個(gè)類可以有一個(gè)主構(gòu)造函數(shù)和一個(gè)或多個(gè)次構(gòu)造函數(shù)。主構(gòu)造函數(shù)是類頭的一部分:它跟在類名(和可選的類型參數(shù))后。
class Person constructor(firstName: String) {
}
如果主構(gòu)造函數(shù)沒(méi)有任何注解或者可見(jiàn)性修飾符,可以省略這個(gè) constructor 關(guān)鍵字。
class Person(firstName: String) {
}
主構(gòu)造函數(shù)不能包含任何的代碼。初始化的代碼可以放 到以 init 關(guān)鍵字作為前綴的初始化塊(initializer blocks)中:
class Person ( name:String, age:Int){
constructor(firstName: String) {
init {
println("firstName is $firstName")
}
}
主構(gòu)造的參數(shù)可以在初始化塊中使用。它們也可以在 類體內(nèi)聲明的屬性初始化中使用:
class Person ( name:String, age:Int){
val name:String = name
val age:Int = age
}
如果主構(gòu)造函數(shù)中定義的參數(shù)使用 val 或者 var 修飾,則會(huì)創(chuàng)建與這個(gè)參數(shù)同名的成員變量,并使用傳入的參數(shù)值初始化這個(gè)成員變量。
class Person (val name:String,val age:Int)//等價(jià)于上面的代碼
如果類有一個(gè)主構(gòu)造函數(shù),每個(gè)次構(gòu)造函數(shù)需要委托給主構(gòu)造函數(shù), 可以直接委托或者通過(guò)別的次構(gòu)造函數(shù)間接委托。委托到同一個(gè)類的另一個(gè)構(gòu)造函數(shù)用 this 關(guān)鍵字即可:
class Person ( name:String, age:Int){
init {
println("name is $name,age is $age")
}
constructor(name:String,age:Int,id:Int):this(name,age){
println("id is $id")
}
}
3.創(chuàng)建類的實(shí)例
val person= Person("mlk",28,1)//name is mlk,age is 28
//id is 1
person.age
person.name
4.屬性
聲明一個(gè)屬性的完整語(yǔ)法
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
其初始器(initializer)、getter 和 setter 都是可選的。屬性類型如果可以從初始器 (或者從其 getter 返回值,如下文所示)中推斷出來(lái),也可以省略。
val
定義只讀屬性,var
定義可讀寫屬性。
我們可以編寫自定義的訪問(wèn)器,非常像普通函數(shù),剛好在屬性聲明內(nèi)部。這里有一個(gè)自定義 getter 的例子:
class Person(val name: String) {
val isEmpty: Boolean //自定義訪問(wèn)器
get() = name.length == 0
}
val person= Person("")
println("${person.isEmpty}")//true
4.1幕后字段
首先要明確什么是字段,什么是屬性,其實(shí)這一點(diǎn)在 C# 語(yǔ)言當(dāng)中有更好的體現(xiàn):
- 字段是實(shí)際存儲(chǔ)在內(nèi)存中的變量,并且是私有的可以直接訪問(wèn)的變量,通常命名前帶有“_”符號(hào)
- 屬性是不占用實(shí)際內(nèi)存的,它提供對(duì)字段的訪問(wèn)方法,實(shí)際是對(duì)字段和訪問(wèn)方法的封裝,因此屬性會(huì)有只讀、只寫、讀/寫等類型
而在 Kotlin 中,屬性既可以直接存儲(chǔ)值,也可以利用字段來(lái)存儲(chǔ)值的,但是這里字段沒(méi)有顯式表達(dá)出來(lái),因此叫做幕后字段,幕后字段的產(chǎn)生是有條件的,必須滿足下面兩個(gè)條件之一:
- 屬性至少有一個(gè)訪問(wèn)器采用默認(rèn)實(shí)現(xiàn)
- 自定義訪問(wèn)器通過(guò) field 引用幕后字段
class Person{
var name: String =""
get() = field
set(value) {
field = value //使用field引用幕后字段
}
}
幕后字段是非常重要的,因?yàn)樵谠趯傩缘?get 和 set 方法中,不能直接使用該屬性,否則會(huì)發(fā)生棧溢出錯(cuò)誤,這時(shí)就只能用幕后字段來(lái)操作相應(yīng)的值。
4.2幕后屬性
class Person{
var _name:String = ""
var name: String
get() = this._name
set(value) {
this._name = value
}
}
4.3 編譯期常量
已知值的屬性可以使用 const 修飾符標(biāo)記為 編譯期常量。
4.4 延遲初始化屬性
一般地,屬性聲明為非空類型必須在構(gòu)造函數(shù)中初始化。 然而,這經(jīng)常不方便。例如:屬性可以通過(guò)依賴注入來(lái)初始化, 或者在單元測(cè)試的 setup 方法中初始化。 這種情況下,你不能在構(gòu)造函數(shù)內(nèi)提供一個(gè)非空初始器。 但你仍然想在類體中引用該屬性時(shí)避免空檢查。
為處理這種情況,你可以用 lateinit 修飾符標(biāo)記該屬性:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // 直接解引用
}
}
該修飾符只能用于在類體中(不是在主構(gòu)造函數(shù)中)聲明的 var 屬性,并且僅當(dāng)該屬性沒(méi)有自定義 getter 或 setter 時(shí)。該屬性必須是非空類型,并且不能是原生類型。
在初始化前訪問(wèn)一個(gè) lateinit 屬性會(huì)拋出一個(gè)特定異常,該異常明確標(biāo)識(shí)該屬性被訪問(wèn)及它沒(méi)有初始化的事實(shí)。
4.5 可觀察屬性
Delegates.observable()
接受兩個(gè)參數(shù):初始值和修改時(shí)處理程序(handler)。 每當(dāng)我們給屬性賦值時(shí)會(huì)調(diào)用該處理程序(在賦值后執(zhí)行)。它有三個(gè)參數(shù):被賦值的屬性、舊值和新值:
package cn.malinkang.kotlin
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$prop,$old,$new")
}
}
fun main(args: Array<String>) {
val user = User()
user.name = "first"
user.name = "second"
}
//輸出結(jié)果
//var cn.malinkang.kotlin.User.name: kotlin.String,<no name>,first
//var cn.malinkang.kotlin.User.name: kotlin.String,first,second
4.6 把屬性儲(chǔ)存在映射中
package cn.malinkang.kotlin
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
fun main(args: Array<String>) {
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
println(user.name) //John Doe
println(user.age)// 25
}
4.7 代理屬性
class Example {
var p: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name} in $thisRef.'")
}
}
val e = Example()
println(e.p)
//輸出Example@33a17727, thank you for delegating ‘p’ to me!
5.繼承
open class Base(p: Int) //父類
class Derived(p: Int) : Base(p) //子類
類上的 open 標(biāo)注與 Java 中 final 相反,它允許其他類從這個(gè)類繼承。默認(rèn)情況下,在 Kotlin 中所有的類都是 final。
如果該類有一個(gè)主構(gòu)造函數(shù),其基類型并且必須用基類型的主構(gòu)造函數(shù)參數(shù)就地初始化。
如果類沒(méi)有主構(gòu)造函數(shù),那么每個(gè)次構(gòu)造函數(shù)必須使用 super 關(guān)鍵字初始化其基類型,或委托給另一個(gè)構(gòu)造函數(shù)做到這一點(diǎn)。 注意,在這種情況下,不同的次構(gòu)造函數(shù)可以調(diào)用基類型的不同的構(gòu)造函數(shù):
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
5.1.覆蓋方法
我們之前提到過(guò),Kotlin 力求清晰顯式。與 Java 不同,Kotlin 需要顯式標(biāo)注可覆蓋的成員(我們稱之為開(kāi)放)和覆蓋后的成員:
open class Base {
open fun v() {}
fun nv() {}
}
class Derived() : Base() {
override fun v() {}
}
Derived.v() 函數(shù)上必須加上 override標(biāo)注。如果沒(méi)寫,編譯器將會(huì)報(bào)錯(cuò)。 如果函數(shù)沒(méi)有標(biāo)注 open 如 Base.nv(),則子類中不允許定義相同簽名的函數(shù), 不論加不加 override。在一個(gè) final 類中(沒(méi)有用 open 標(biāo)注的類),開(kāi)放成員是禁止的。
標(biāo)記為 override 的成員本身是開(kāi)放的,也就是說(shuō),它可以在子類中覆蓋。如果你想禁止再次覆蓋,使用 final 關(guān)鍵字:
open class AnotherDerived() : Base() {
final override fun v() {}
}
5.2 覆蓋屬性
屬性覆蓋與方法覆蓋類似;在超類中聲明然后在派生類中重新聲明的屬性必須以 override 開(kāi)頭,并且它們必須具有兼容的類型。每個(gè)聲明的屬性可以由具有初始化器的屬性或者具有 getter 方法的屬性覆蓋。
open class Foo {
open val x: Int =0
}
class Bar1 : Foo() {
override val x: Int =1
}
你也可以用一個(gè) var 屬性覆蓋一個(gè) val 屬性,但反之則不行。這是允許的,因?yàn)橐粋€(gè) val 屬性本質(zhì)上聲明了一個(gè) getter 方法,而將其覆蓋為 var 只是在子類中額外聲明一個(gè) setter 方法。
請(qǐng)注意,你可以在主構(gòu)造函數(shù)中使用 override 關(guān)鍵字作為屬性聲明的一部分。
interface Foo {
val count: Int
}
class Bar1(override val count: Int) : Foo
class Bar2 : Foo {
override var count: Int = 0
}
5.3 覆蓋規(guī)則
在 Kotlin 中,實(shí)現(xiàn)繼承由下述規(guī)則規(guī)定:如果一個(gè)類從它的直接超類繼承相同成員的多個(gè)實(shí)現(xiàn), 它必須覆蓋這個(gè)成員并提供其自己的實(shí)現(xiàn)(也許用繼承來(lái)的其中之一)。 為了表示采用從哪個(gè)超類型繼承的實(shí)現(xiàn),我們使用由尖括號(hào)中超類型名限定的 super,如 super<Base>:
open class A {
open fun f() { print("A") }
fun a() { print("a") }
}
interface B {
fun f() { print("B") } // 接口成員默認(rèn)就是“open”的
fun b() { print("b") }
}
class C() : A(), B {
// 編譯器要求覆蓋 f():
override fun f() {
super<A>.f() // 調(diào)用 A.f()
super<B>.f() // 調(diào)用 B.f()
}
}
同時(shí)繼承 A 和 B 沒(méi)問(wèn)題,并且 a() 和 b() 也沒(méi)問(wèn)題因?yàn)?C 只繼承了每個(gè)函數(shù)的一個(gè)實(shí)現(xiàn)。 但是 f() 由 C 繼承了兩個(gè)實(shí)現(xiàn),所以我們必須在 C 中覆蓋 f() 并且提供我們自己的實(shí)現(xiàn)來(lái)消除歧義。
6.抽象類
類和其中的某些成員可以聲明為 abstract
。 抽象成員在本類中可以不用實(shí)現(xiàn)。 需要注意的是,我們并不需要用 open 標(biāo)注一個(gè)抽象類或者函數(shù)——因?yàn)檫@不言而喻。
我們可以用一個(gè)抽象成員覆蓋一個(gè)非抽象的開(kāi)放成員
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
7.接口
接口定義
interface MyInterface {
fun bar()
fun foo() {
// 可選的方法體
}
}
接口實(shí)現(xiàn)
class Child : MyInterface {
override fun bar() {
// 方法體
}
}
你可以在接口中定義屬性。在接口中聲明的屬性要么是抽象的,要么提供訪問(wèn)器的實(shí)現(xiàn)。在接口中聲明的屬性不能有幕后字段(backing field),因此接口中聲明的訪問(wèn)器不能引用它們。
interface MyInterface {
val prop: Int // 抽象的
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
class Child : MyInterface {
override val prop: Int = 29
}
8.可見(jiàn)性修飾符
- private:意味著只在這個(gè)類內(nèi)部(包含其所有成員)可見(jiàn);
- protected:這個(gè)類內(nèi)部及其子類中可見(jiàn)。
- internal:能見(jiàn)到類聲明的 本模塊內(nèi) 的任何客戶端都可見(jiàn)其 internal 成員;
- public:能見(jiàn)到類聲明的任何客戶端都可見(jiàn)其 public 成員。
Kotlin 中外部類不能訪問(wèn)內(nèi)部類的 private 成員。
如果你覆蓋一個(gè) protected 成員并且沒(méi)有顯式指定其可見(jiàn)性,該成員還會(huì)是 protected 可見(jiàn)性。
open class Outer {
private val a = 1
protected open val b = 2
internal val c = 3
val d = 4 // 默認(rèn) public
protected class Nested {
public val e: Int = 5
}
}
class Subclass : Outer() {
// a 不可見(jiàn)
// b、c、d 可見(jiàn)
// Nested 和 e 可見(jiàn)
override val b = 5 // “b”為 protected
}
class Unrelated(o: Outer) {
// o.a、o.b 不可見(jiàn)
// o.c 和 o.d 可見(jiàn)(相同模塊)
// Outer.Nested 不可見(jiàn),Nested::e 也不可見(jiàn)
}
9.數(shù)據(jù)類
我們經(jīng)常創(chuàng)建一些只保存數(shù)據(jù)的類。在這些類中,一些標(biāo)準(zhǔn)函數(shù)往往是從數(shù)據(jù)機(jī)械推導(dǎo)而來(lái)的。在 Kotlin 中,這叫做 數(shù)據(jù)類 并標(biāo)記為 data
data class User(val name: String, val age: Int)
編譯器自動(dòng)添加如下方法:
- equals()、hashCode()方法
- toString()格式是 "User(name=John, age=42)"
- componentN()函數(shù) 按聲明順序?qū)?yīng)于所有屬性,
- copy() 函數(shù)
如果這些函數(shù)中的任何一個(gè)在類體中顯式定義或繼承自其基類型,則不會(huì)生成該函數(shù)。
為了確保生成的代碼的一致性和有意義的行為,數(shù)據(jù)類必須滿足以下要求:
- 主構(gòu)造函數(shù)需要至少有一個(gè)參數(shù);
- 主構(gòu)造函數(shù)的所有參數(shù)需要標(biāo)記為 val 或 var;
data class User(val name: String = "", val age: Int = 0)
在很多情況下,我們需要復(fù)制一個(gè)對(duì)象改變它的一些屬性,但其余部分保持不變。 copy() 函數(shù)就是為此而生成。對(duì)于上文的 User 類,其實(shí)現(xiàn)會(huì)類似下面這樣:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
標(biāo)準(zhǔn)庫(kù)提供了 Pair
和 Triple
。
10.密封類
密封類用來(lái)表示受限的類繼承結(jié)構(gòu):當(dāng)一個(gè)值為有限集中的類型、而不能有任何其他類型時(shí)。在某種意義上,他們是枚舉類的擴(kuò)展:枚舉類型的值集合也是受限的,但每個(gè)枚舉常量只存在一個(gè)實(shí)例,而密封類的一個(gè)子類可以有可包含狀態(tài)的多個(gè)實(shí)例。
要聲明一個(gè)密封類,需要在類名前面添加 sealed 修飾符。雖然密封類也可以有子類,但是所有子類都必須在與密封類自身相同的文件中聲明。(在 Kotlin 1.1 之前, 該規(guī)則更加嚴(yán)格:子類必須嵌套在密封類聲明的內(nèi)部)。
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
(上文示例使用了 Kotlin 1.1 的一個(gè)額外的新功能:數(shù)據(jù)類擴(kuò)展包括密封類在內(nèi)的其他類的可能性。 )
請(qǐng)注意,擴(kuò)展密封類子類的類(間接繼承者)可以放在任何位置,而無(wú)需在同一個(gè)文件中。
使用密封類的關(guān)鍵好處在于使用 when表達(dá)式 的時(shí)候,如果能夠驗(yàn)證語(yǔ)句覆蓋了所有情況,就不需要為該語(yǔ)句再添加一個(gè) else
子句了
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// 不再需要 `else` 子句,因?yàn)槲覀円呀?jīng)覆蓋了所有的情況
}
11.嵌套類
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}
val demo = Outer.Nested().foo() // == 2
類可以標(biāo)記為 inner 以便能夠訪問(wèn)外部類的成員。內(nèi)部類會(huì)帶有一個(gè)對(duì)外部類的對(duì)象的引用:
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
使用對(duì)象表達(dá)式創(chuàng)建匿名內(nèi)部類實(shí)例:
window.addMouseListener(object: MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ……
}
override fun mouseEntered(e: MouseEvent) {
// ……
}
})
12.枚舉類
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
因?yàn)槊恳粋€(gè)枚舉都是枚舉類的實(shí)例,所以他們可以是初始化過(guò)的。
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
枚舉常量也可以聲明自己的匿名類
enum class ProtocolState {
WAITING {
override fun signal() = TALKING
},
TALKING {
override fun signal() = WAITING
};
abstract fun signal(): ProtocolState
}
及相應(yīng)的方法、以及覆蓋基類的方法。注意,如果枚舉類定義任何成員,要使用分號(hào)將成員定義中的枚舉常量定義分隔開(kāi),就像在 Java 中一樣。
就像在 Java 中一樣,Kotlin 中的枚舉類也有合成方法允許列出定義的枚舉常量以及通過(guò)名稱獲取枚舉常量。這些方法的簽名如下(假設(shè)枚舉類的名稱是 EnumClass):
EnumClass.valueOf(value: String): EnumClass
EnumClass.values(): Array<EnumClass>