1. 類
Kotlin 中使用關(guān)鍵字class
聲明類
class Person {
}
類聲明由類名讶凉、類頭(指定其類型參數(shù)畏浆、主 構(gòu)造函數(shù)等)和由大括號包圍的類體構(gòu)成鉴扫。類頭和類體都是可選的; 如果一個類沒有類體味悄,可以省略花括號草戈。
class Person
2.構(gòu)造函數(shù)
在 Kotlin 中的一個類可以有一個主構(gòu)造函數(shù)和一個或多個次構(gòu)造函數(shù)。主構(gòu)造函數(shù)是類頭的一部分:它跟在類名(和可選的類型參數(shù))后侍瑟。
class Person constructor(firstName: String) {
}
如果主構(gòu)造函數(shù)沒有任何注解或者可見性修飾符唐片,可以省略這個 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 修飾,則會創(chuàng)建與這個參數(shù)同名的成員變量咐低,并使用傳入的參數(shù)值初始化這個成員變量揽思。
class Person (val name:String,val age:Int)//等價于上面的代碼
如果類有一個主構(gòu)造函數(shù),每個次構(gòu)造函數(shù)需要委托給主構(gòu)造函數(shù)见擦, 可以直接委托或者通過別的次構(gòu)造函數(shù)間接委托钉汗。委托到同一個類的另一個構(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)建類的實例
val person= Person("mlk",28,1)//name is mlk,age is 28
//id is 1
person.age
person.name
4.屬性
聲明一個屬性的完整語法
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
其初始器(initializer)、getter 和 setter 都是可選的鲤屡。屬性類型如果可以從初始器 (或者從其 getter 返回值损痰,如下文所示)中推斷出來,也可以省略酒来。
val
定義只讀屬性卢未,var
定義可讀寫屬性。
我們可以編寫自定義的訪問器,非常像普通函數(shù)辽社,剛好在屬性聲明內(nèi)部伟墙。這里有一個自定義 getter 的例子:
class Person(val name: String) {
val isEmpty: Boolean //自定義訪問器
get() = name.length == 0
}
val person= Person("")
println("${person.isEmpty}")//true
4.1幕后字段
首先要明確什么是字段,什么是屬性滴铅,其實這一點在 C# 語言當中有更好的體現(xiàn):
- 字段是實際存儲在內(nèi)存中的變量戳葵,并且是私有的可以直接訪問的變量,通常命名前帶有“_”符號
- 屬性是不占用實際內(nèi)存的汉匙,它提供對字段的訪問方法拱烁,實際是對字段和訪問方法的封裝,因此屬性會有只讀噩翠、只寫戏自、讀/寫等類型
而在 Kotlin 中,屬性既可以直接存儲值伤锚,也可以利用字段來存儲值的擅笔,但是這里字段沒有顯式表達出來,因此叫做幕后字段见芹,幕后字段的產(chǎn)生是有條件的,必須滿足下面兩個條件之一:
- 屬性至少有一個訪問器采用默認實現(xiàn)
- 自定義訪問器通過 field 引用幕后字段
class Person{
var name: String =""
get() = field
set(value) {
field = value //使用field引用幕后字段
}
}
幕后字段是非常重要的蠢涝,因為在在屬性的 get 和 set 方法中玄呛,不能直接使用該屬性,否則會發(fā)生棧溢出錯誤和二,這時就只能用幕后字段來操作相應(yīng)的值徘铝。
4.2幕后屬性
class Person{
var _name:String = ""
var name: String
get() = this._name
set(value) {
this._name = value
}
}
4.3 編譯期常量
已知值的屬性可以使用 const 修飾符標記為 編譯期常量。
4.4 延遲初始化屬性
一般地惯吕,屬性聲明為非空類型必須在構(gòu)造函數(shù)中初始化惕它。 然而,這經(jīng)常不方便废登。例如:屬性可以通過依賴注入來初始化淹魄, 或者在單元測試的 setup 方法中初始化。 這種情況下堡距,你不能在構(gòu)造函數(shù)內(nèi)提供一個非空初始器甲锡。 但你仍然想在類體中引用該屬性時避免空檢查。
為處理這種情況羽戒,你可以用 lateinit 修飾符標記該屬性:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // 直接解引用
}
}
該修飾符只能用于在類體中(不是在主構(gòu)造函數(shù)中)聲明的 var 屬性缤沦,并且僅當該屬性沒有自定義 getter 或 setter 時。該屬性必須是非空類型易稠,并且不能是原生類型缸废。
在初始化前訪問一個 lateinit 屬性會拋出一個特定異常,該異常明確標識該屬性被訪問及它沒有初始化的事實。
4.5 可觀察屬性
Delegates.observable()
接受兩個參數(shù):初始值和修改時處理程序(handler)企量。 每當我們給屬性賦值時會調(diào)用該處理程序(在賦值后執(zhí)行)测萎。它有三個參數(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 把屬性儲存在映射中
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 標注與 Java 中 final 相反梁钾,它允許其他類從這個類繼承绳泉。默認情況下,在 Kotlin 中所有的類都是 final姆泻。
如果該類有一個主構(gòu)造函數(shù)零酪,其基類型并且必須用基類型的主構(gòu)造函數(shù)參數(shù)就地初始化。
如果類沒有主構(gòu)造函數(shù)拇勃,那么每個次構(gòu)造函數(shù)必須使用 super 關(guān)鍵字初始化其基類型四苇,或委托給另一個構(gòu)造函數(shù)做到這一點。 注意方咆,在這種情況下月腋,不同的次構(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.覆蓋方法
我們之前提到過,Kotlin 力求清晰顯式瓣赂。與 Java 不同榆骚,Kotlin 需要顯式標注可覆蓋的成員(我們稱之為開放)和覆蓋后的成員:
open class Base {
open fun v() {}
fun nv() {}
}
class Derived() : Base() {
override fun v() {}
}
Derived.v() 函數(shù)上必須加上 override標注。如果沒寫煌集,編譯器將會報錯妓肢。 如果函數(shù)沒有標注 open 如 Base.nv(),則子類中不允許定義相同簽名的函數(shù)苫纤, 不論加不加 override碉钠。在一個 final 類中(沒有用 open 標注的類),開放成員是禁止的卷拘。
標記為 override 的成員本身是開放的喊废,也就是說,它可以在子類中覆蓋栗弟。如果你想禁止再次覆蓋污筷,使用 final 關(guān)鍵字:
open class AnotherDerived() : Base() {
final override fun v() {}
}
5.2 覆蓋屬性
屬性覆蓋與方法覆蓋類似;在超類中聲明然后在派生類中重新聲明的屬性必須以 override 開頭乍赫,并且它們必須具有兼容的類型颓屑。每個聲明的屬性可以由具有初始化器的屬性或者具有 getter 方法的屬性覆蓋。
open class Foo {
open val x: Int =0
}
class Bar1 : Foo() {
override val x: Int =1
}
你也可以用一個 var 屬性覆蓋一個 val 屬性耿焊,但反之則不行揪惦。這是允許的,因為一個 val 屬性本質(zhì)上聲明了一個 getter 方法罗侯,而將其覆蓋為 var 只是在子類中額外聲明一個 setter 方法器腋。
請注意,你可以在主構(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 中纫塌,實現(xiàn)繼承由下述規(guī)則規(guī)定:如果一個類從它的直接超類繼承相同成員的多個實現(xiàn)诊县, 它必須覆蓋這個成員并提供其自己的實現(xiàn)(也許用繼承來的其中之一)。 為了表示采用從哪個超類型繼承的實現(xiàn)措左,我們使用由尖括號中超類型名限定的 super依痊,如 super<Base>:
open class A {
open fun f() { print("A") }
fun a() { print("a") }
}
interface B {
fun f() { print("B") } // 接口成員默認就是“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()
}
}
同時繼承 A 和 B 沒問題,并且 a() 和 b() 也沒問題因為 C 只繼承了每個函數(shù)的一個實現(xiàn)怎披。 但是 f() 由 C 繼承了兩個實現(xiàn)胸嘁,所以我們必須在 C 中覆蓋 f() 并且提供我們自己的實現(xiàn)來消除歧義。
6.抽象類
類和其中的某些成員可以聲明為 abstract
凉逛。 抽象成員在本類中可以不用實現(xiàn)性宏。 需要注意的是,我們并不需要用 open 標注一個抽象類或者函數(shù)——因為這不言而喻状飞。
我們可以用一個抽象成員覆蓋一個非抽象的開放成員
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
7.接口
接口定義
interface MyInterface {
fun bar()
fun foo() {
// 可選的方法體
}
}
接口實現(xiàn)
class Child : MyInterface {
override fun bar() {
// 方法體
}
}
你可以在接口中定義屬性毫胜。在接口中聲明的屬性要么是抽象的,要么提供訪問器的實現(xiàn)诬辈。在接口中聲明的屬性不能有幕后字段(backing field)酵使,因此接口中聲明的訪問器不能引用它們。
interface MyInterface {
val prop: Int // 抽象的
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
class Child : MyInterface {
override val prop: Int = 29
}
8.可見性修飾符
- private:意味著只在這個類內(nèi)部(包含其所有成員)可見焙糟;
- protected:這個類內(nèi)部及其子類中可見口渔。
- internal:能見到類聲明的 本模塊內(nèi) 的任何客戶端都可見其 internal 成員;
- public:能見到類聲明的任何客戶端都可見其 public 成員酬荞。
Kotlin 中外部類不能訪問內(nèi)部類的 private 成員搓劫。
如果你覆蓋一個 protected 成員并且沒有顯式指定其可見性瞧哟,該成員還會是 protected 可見性混巧。
open class Outer {
private val a = 1
protected open val b = 2
internal val c = 3
val d = 4 // 默認 public
protected class Nested {
public val e: Int = 5
}
}
class Subclass : Outer() {
// a 不可見
// b、c勤揩、d 可見
// Nested 和 e 可見
override val b = 5 // “b”為 protected
}
class Unrelated(o: Outer) {
// o.a咧党、o.b 不可見
// o.c 和 o.d 可見(相同模塊)
// Outer.Nested 不可見,Nested::e 也不可見
}
9.數(shù)據(jù)類
我們經(jīng)常創(chuàng)建一些只保存數(shù)據(jù)的類陨亡。在這些類中傍衡,一些標準函數(shù)往往是從數(shù)據(jù)機械推導(dǎo)而來的。在 Kotlin 中负蠕,這叫做 數(shù)據(jù)類 并標記為 data
data class User(val name: String, val age: Int)
編譯器自動添加如下方法:
- equals()蛙埂、hashCode()方法
- toString()格式是 "User(name=John, age=42)"
- componentN()函數(shù) 按聲明順序?qū)?yīng)于所有屬性,
- copy() 函數(shù)
如果這些函數(shù)中的任何一個在類體中顯式定義或繼承自其基類型遮糖,則不會生成該函數(shù)绣的。
為了確保生成的代碼的一致性和有意義的行為,數(shù)據(jù)類必須滿足以下要求:
- 主構(gòu)造函數(shù)需要至少有一個參數(shù);
- 主構(gòu)造函數(shù)的所有參數(shù)需要標記為 val 或 var屡江;
data class User(val name: String = "", val age: Int = 0)
在很多情況下芭概,我們需要復(fù)制一個對象改變它的一些屬性,但其余部分保持不變惩嘉。 copy() 函數(shù)就是為此而生成罢洲。對于上文的 User 類,其實現(xiàn)會類似下面這樣:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
標準庫提供了 Pair
和 Triple
文黎。
10.密封類
密封類用來表示受限的類繼承結(jié)構(gòu):當一個值為有限集中的類型惹苗、而不能有任何其他類型時。在某種意義上臊诊,他們是枚舉類的擴展:枚舉類型的值集合也是受限的鸽粉,但每個枚舉常量只存在一個實例,而密封類的一個子類可以有可包含狀態(tài)的多個實例抓艳。
要聲明一個密封類触机,需要在類名前面添加 sealed 修飾符。雖然密封類也可以有子類玷或,但是所有子類都必須在與密封類自身相同的文件中聲明儡首。(在 Kotlin 1.1 之前, 該規(guī)則更加嚴格:子類必須嵌套在密封類聲明的內(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 的一個額外的新功能:數(shù)據(jù)類擴展包括密封類在內(nèi)的其他類的可能性蔬胯。 )
請注意,擴展密封類子類的類(間接繼承者)可以放在任何位置位他,而無需在同一個文件中氛濒。
使用密封類的關(guān)鍵好處在于使用 when表達式 的時候,如果能夠驗證語句覆蓋了所有情況鹅髓,就不需要為該語句再添加一個 else
子句了
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// 不再需要 `else` 子句舞竿,因為我們已經(jīng)覆蓋了所有的情況
}
11.嵌套類
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}
val demo = Outer.Nested().foo() // == 2
類可以標記為 inner 以便能夠訪問外部類的成員。內(nèi)部類會帶有一個對外部類的對象的引用:
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
使用對象表達式創(chuàng)建匿名內(nèi)部類實例:
window.addMouseListener(object: MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ……
}
override fun mouseEntered(e: MouseEvent) {
// ……
}
})
12.枚舉類
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
因為每一個枚舉都是枚舉類的實例窿冯,所以他們可以是初始化過的骗奖。
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)的方法、以及覆蓋基類的方法醒串。注意执桌,如果枚舉類定義任何成員,要使用分號將成員定義中的枚舉常量定義分隔開芜赌,就像在 Java 中一樣仰挣。
就像在 Java 中一樣,Kotlin 中的枚舉類也有合成方法允許列出定義的枚舉常量以及通過名稱獲取枚舉常量缠沈。這些方法的簽名如下(假設(shè)枚舉類的名稱是 EnumClass):
EnumClass.valueOf(value: String): EnumClass
EnumClass.values(): Array<EnumClass>