Kotlin入門(二) —— 類、接口與對象篇

1. 可見性

  • private 只在該類(以及它的成員)中可見
  • protected 和 private 一樣但在子類中也可見
  • internal 在本模塊的所有可以訪問到聲明區(qū)域的均可以訪問該類的所有 internal 成員
  • public 任何地方可見 當沒寫明可見性,默認為public
  • 特別的:外部類不可以訪問內(nèi)部類的 private 成員。

以上模塊是指

  • an IntelliJ IDEA module;
  • a Maven or Gradle project;

2. 構(gòu)造函數(shù)

類可以包含一個主構(gòu)造函數(shù)和多個二級構(gòu)造函數(shù),完整聲明如下。如果構(gòu)造函數(shù)不包括可見性聲明和注解,則constructor可以省略

class Customer public @inject constructor (name: String) {...}

主構(gòu)造函數(shù)不能包含任意代碼,初始化代碼放在init{}代碼塊內(nèi)

主構(gòu)造函數(shù)可以直接初始化域變量,也可以(必須)直接帶入超類的參數(shù)

class Person(val firstName: String, val lastName: String, var age: Int) : BasePerson(firstName, lastName) {
}

二級構(gòu)造函數(shù)需要constructor前綴
如果存在主構(gòu)造函數(shù),每一個二級構(gòu)造函數(shù)都要直接調(diào)用,或間接通過另一個二級構(gòu)造函數(shù)代理調(diào)用主構(gòu)造函數(shù),用this關鍵字來指代其他構(gòu)造函數(shù)

class Person(val name: String) {
    constructor (name: String, paret: Person) : this(name) {
        parent.children.add(this)
    }
}

構(gòu)造函數(shù)中的參數(shù)可以有默認值,類似于python,當有默認值的參數(shù),new的時候,可以忽略而使用默認值
實際上Kotlin創(chuàng)建對象不使用new 直接使用 Person("Wang")

3. Kotlin的接口

Kotlin的接口允許包含了方法的實現(xiàn),但和抽象類不同的是,接口不能保存狀態(tài)。如下,

  • C實現(xiàn)A接口,bar()方法是抽象的,則強制需要重寫實現(xiàn)它。
  • D同時實現(xiàn)A和B,對于bar()方法,因為B已經(jīng)包含了實現(xiàn),所以會默認選擇B的實現(xiàn);但因為A和B都包含了對foo()這個方法的實現(xiàn),造成了沖突,所以強制重寫此方法。
interface A {
    fun foo() { print("A") }
    fun bar()
}

interface B {
    fun foo() { print("B") }
    fun bar() { print("bar") }
}

class C : A {
    override fun bar() { print("bar") }
}

class D : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
    }
}

4. 類繼承和方法屬性復寫

  • kotlin 中堅持做明確的事,Kotlin的類默認下都是final的,需要加上open關鍵字才可被繼承。而抽象類和接口則天生是open的。
  • 對于可以復寫的方法,Kotlin也需要明確使用open來聲明,并且重寫他們
  • override的成員也是open的,如果想終結(jié)這個open,需要加final
  • 可以用var去覆蓋一個val,但反之則不允許
  • 當繼承的類和實現(xiàn)的接口包含同名同參方法,并且均有實現(xiàn)時,需要重新復寫這個方法(參考接口)

5. 屬性

屬性可以有g(shù)et set方法

var stringRepresentation: String
  get() = this.toString()
  set(value) {
    setDataFromString(value) // parses the string and assigns values to other properties
  }

備份域:

var counter = 0 // the initializer value is written directly to the backing field
  set(value) {
    if (value >= 0)
      field = value
  }

備份屬性

private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
  get() {
    if (_table == null)
      _table = HashMap() // Type parameters are inferred
    return _table ?: throw AssertionError("Set to null by another thread")
  }

對于成員屬性,不管是空或非空,都不許再定義的時候初始化。

如果想延遲初始化,有以下方法:

val a : Any by Delegates.notNull()
var b : Any by Delegates.notNull()
lateinit var c : Any
val d : Any? by lazy {  }
val e : Any by lazy {  }

6. 枚舉類

  • 實際上每一個枚舉都是一個對象實例。
  • 此外,枚舉類實現(xiàn)了Comparable接口,比較適使用的是枚舉類的自然順序進行比較
// 1 普通枚舉類
enum class Direction {
    NORTH,SOUTH,WEST,EAST
}

// 2 可初始化的枚舉類
enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

// 3 可以聲明枚舉實例自己的匿名類
enum class SwitchState {
    ON {
        override fun switch() = OFF
    },
    OFF{
        override fun switch() = ON
    };
    abstract fun switch(): SwitchState
}

7. 密封類 sealed

  • 密封類相當于一個枚舉類的擴展:枚舉值集合的類型是嚴格限制的,但每個枚舉常量只有一個實例,而密封類的子類可以有包含不同狀態(tài)的多個實例。
  • 密封類主要的好處體現(xiàn)在when表達式,可以確保聲明覆蓋所有情形而不需要else
sealed class Expr {
    class Const(val number: Double) : Expr()
    class Sum(val e1: Expr, val e2: Expr) : Expr()
    object NotANumber : Expr()
}

fun eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
    // the `else` clause is not required because we've covered all the cases
}

8. 代理屬性

有些常用屬性,其讀寫方法可以進行抽象(類似于Gson的屬性封裝),get set方法可以通過代理類進行封裝,以后可以重用該代理。

class Delegate {
    fun get(thisRef: Any?, prop: PropertyMetadata): String {
        return "$thisRef, thank you for delegating '${prop.name}' to me !"
    }
    
    fun set(thisRef: Any?, prop: PropertyMatada, value: String) {
        println("$value has been assigned to '${prop.name} in $thisRef.'")
    }
}

class Example {
    var p: String by Delegate()
}
Kotlin提供的標準代理

kotlin.properties.Delegates 對象是標準庫提供的一個工廠方法并提供了很多有用的代理

// 懶加載
val lazy: String by Delegates.lazy { // 如果你想要線程安全,使用 blockingLazy()
    println("computed!")
    "Hello"
}

// 觀察者
var name: String by Delegates.observable("default") {
        d,old,new -> println("$old -> $new")
}
    
// NotNull
// 正常來說 非空的var不允許不初始化,也不允許用null初始化
class Foo {
    var bar: Bat //錯誤必須初始化
}
// 使用Delegates.notNull()可以作為替代進行null初始化,但如果在set之前get,則會拋出異常
class Foo {
    var bar: Bar by Delegates.notNull()
}

// 在Map中解析屬性
class User(val map: Map<String, Any?>) {
    val name: String by Delegates.mapVal(map) // var 則可以用 mapVar
    val age: Int     by Delegates.mapVal(map)
}
9. 代理類

Kotlin提供代理類的方式去快速實現(xiàn)代理模式

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() { printz(x) }
}

class Derived(b: Base) : Base by b

fun main() {
    val b = BaseImpl(10)
    Derived(b).print()
}

10. 嵌套類

嵌套類可分為

  1. 一般嵌套類:這個跟Java差不多,不展開講。區(qū)別是無法引用外部類的屬性和方法
  2. 內(nèi)部類:用inner修飾,可以引用外部類的屬性和方法。但外部類無法使用內(nèi)部類的private屬性或方法
  3. 匿名內(nèi)部類:使用對象表達式創(chuàng)建,參考下一條“對象聲明”
class Outer {
    private val bar: Int = 1
    // 內(nèi)部類
    inner class Inner {
        fun foo() = bar
    }
}

11. 對象聲明

Kotlin中有3種可以進行聲明的對象
  1. 匿名內(nèi)部類
  2. 單例對象
  3. 伴隨對象
// 類似于匿名內(nèi)部類
button.setOnClickListener( object : OnClickListener {
    override fun onClick(v: View) {
        // do something
    }
})

// 單例
object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }
}

// 伴隨對象 其中聲明的方法和屬性相當于是java的類里面聲明的靜態(tài)屬性和靜態(tài)方法,可以使用類名來直接引用
class MyClass {
    companion object : Factory<MyClass> {
        override fun create(): MyClass = MyClass()
    }
}

以上單例類似于java中的

public final class DataProviderManager {
    private DataProviderManager() {}
    public static DataProviderManager INSTANCE = new DataProviderManager();
    
    void registerDataProvider(DataProvider provider) {
        // ...
    }
}

12. 多重聲明與data類

針對常用的數(shù)據(jù)存儲類Java bean,Kotlin提供了更便捷的data類。data類的限制如下

  1. 主構(gòu)造函數(shù)至少有一個參數(shù)
  2. 所有構(gòu)造函數(shù)參數(shù)必須標注為val或var,即不能是局部變量
  3. 數(shù)據(jù)類不能使abstract open sealed 或 inner
  4. 不能繼承,但能實現(xiàn)接口
  5. 如果在 JVM 中如果構(gòu)造函數(shù)是無參的,則所有的屬性必須有默認的值
  • data class User(val name: String = "", val age: Int = 0)
data class User(val name: String, val age: Int)
// toString() of the form "User(name=John, age=42)";

// copy() 便于原型模式的使用 
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

// componentN() 便于多重聲明和多重返回,對應構(gòu)造參數(shù)的聲明順序
val (name, age) = person
val name = persion.component1()
val age = persion.component2()

data class Result(val result: Int, val status: Status)

fun function(...): Result {
    //...
    return Result(result, status)
}

val (result, status) = function(...)
  • Kotlin標準庫提供了 Pair 和 Triple 兩種標準data類,但可讀性不強不建議使用。其實可以作為多參數(shù)返回時使用。

13. 類的擴展

在 java 中,我們通常使用一系列名字為 "Utils" 的類: FileUtils,StringUtils等等。很有名的 java.util.Collections 也是其中一員的,但我們不得不像下面這樣使用他們:

Collections.swap(list, 
                Collections.binarySearch(list, Collections.max(otherList)),
                Collections.max(list));

Kotlin類的擴展可以讓我們可以使用以下方式去引用這樣的工具類靜態(tài)方法

list.swap(list.binarySearch(otherList.max()), list.max())

如對MutableList<T>使用類的擴展:

fun <T> MutableList<T>.swap(x: Int, y: Int) {
    val temp = this[x] // this 對應 list
    this[x] = this[y]
    this[y] = tmp
}

類的擴展需要注意以下幾點:

  1. 類的擴展是被靜態(tài)解析的,如果有同名同參數(shù)的成員函數(shù)和擴展函數(shù),調(diào)用的時候必然會使用成員函數(shù)
  2. 被擴展的類甚至可以為Any?
  3. 可擴展的包括 方法、屬性、伴隨對象
  4. 擴展的作用域 僅當前包有效。其他包要使用某個擴展需要進行引用,如以上擴展,需要執(zhí)行如下導入:
import x.x.x.swap
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內(nèi)容