Kotlin知識總結:類、對象和接口

類、對象和接口

接口

kotlin接口可以包含抽象方法的定義以及非抽象方法的實現(與 Java 8 中的默認方法類似),但它們不能包含任何狀態,通過interface 關鍵字定義,可以有一個默認實現

interface Clickable {
	fun click ()
 	fun showoff() = println ("clickable!")
}

如果多個接口,每一個都包含了帶默認實現的 showoff 方法,如果要調用,需要顯示實現,

super<Clickable>. showoff ()

可見性修飾符

Java 的類和方法默認是 open 的,而 Kotlin 中默認都是 final 的。
如果想創建一個類的子類,就需要用open修飾符來標示這個類,需要給每一個可以被重寫的屬性或方法添加 open 修飾符。如果你重寫了一個基類或者接口的成員,重寫了的成員同樣默認是open 的 。 如果你想改變這一行爲,阻止你的類的子類重寫你的實現,可以顯式地將重寫的成員標註爲 final 。抽象成員始終是 open 的, 所以不需要顯式地使用 open 修飾符.在接口中 , 不能使用final、 open 或者是 abstract。 接口中的成員始終是 open 的,
不能將其聲明爲 final 的
在這裏插入圖片描述
public :公開,可見性最大,哪裏都可以引用。
private:私有,可見性最小,根據聲明位置不同可分爲類中可見和文件中可見。
protected:保護,相當於 private + 子類可見。
internal:內部,僅對 module 內可見。
比Java少了一個default 「包內可見」,多了一個internal「module 內可見」。比如Android Studio 裏的 module。默認是public。protected 成員只在類和它的子類中可見,類的擴展函數不能訪問它的 private 和 protected 成員。Kotlin 中一個外部類不能看到其內部(或者嵌套)類中的 private 成員
在這裏插入圖片描述

內部類和嵌套類

Kotlin 的嵌套類不能訪問外部類的實例,要把它變成一個內部類來持有一個外部類的引用的話需要使用 inner 修飾符。
在這裏插入圖片描述
需要使用 this@Outer 從Inner 類去訪問 Outer 類


class Outer {
inner class Inner {
	fun getOuterReference(): Outer= this@Outer
	}
}

但是,嵌套函數中可以訪問在它外部的所有變量或常量,例如類中的屬性、當前函數中的參數與變量等。

fun login(user: String, password: String, illegalStr: String) {
     
    fun validate(value: String, illegalStr: String) {
      if (value.isEmpty()) {
          throw IllegalArgumentException(illegalStr)
      }
    }
 
    validate(user, illegalStr)
    validate(password, illegalStr)
}

改進:

fun login(user: String, password: String, illegalStr: String) {
    fun validate(value: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException(illegalStr)
        }
    }
    ...
}

validate()可以直接訪問illegalStr

爲父類添加一個 sealed修飾符,對可能創建的子類做出嚴格的限制。所有的直接子類必須嵌套在父類中 。就是說,當你在 when 中使用 sealed 類並且添加 一個新的子類的時候,有返回值的when 表達式會導致編譯失敗,它會告訴你哪裏的代碼必須要修改

sealed class Expr {
    class Num(val value: Int) : Expr()
    class Sum(val left: Expr, val right: Expr) : Expr()

    fun eval(e: Expr): Int =
        when (e) {
            is Expr.Num -> e.value
            is Expr.Sum -> eval(e.right) + eval(e.left)
        }
}

構造函數

class User constructor(_nickname: String) {
    val nickname: String
    init {
        nickname = nickname
    }
}

主構造函數:在類名之後加上constructor,用來開始一個主構造方法或從構造方法的聲明。 init 關鍵字用來引入一個初始化語句塊。init 代碼塊是緊跟在主構造器之後執行的,這是因爲主構造器本身沒有代碼體,init 代碼塊就充當了主構造器代碼體的功能。主構造方法有語法限制,不能包含初始化代碼。如果類中有主構造器,那麼其他的次構造器都需要通過 this 關鍵字調用主構造器,可以直接調用或者通過別的次構造器間接調用

class User constructor(var name: String) {
     //  直接調用主構造器
    constructor(name: String, id: Int) : this(name) {
    }
     // 通過上一個次構造器,間接調用主構造器
    constructor(name: String, id: Int, age: Int) : this(name, id) {
    }
}

init 代碼塊是先於次構造器執行的。如果主構造方法沒有註解或可見性修飾符,同樣可以去掉 constructor 關鍵字

class User(_nickname: String) {
	val nickname = nickname
}

如果有註解或可見性修飾符,就不可省略

class User private constructor(name: String) {
//主構造器被修飾爲私有的,外部就無法調用該構造器
}

如果屬性用相應的構造方法參數來初始化,代碼可以通過把 val或var 關鍵字加在參數前的方式來進行簡化

class User(val nickname: String)
//val 意味着相應的屬性會用構造方法的參數來初始化.就等價於在類中創建了該名稱的屬性(property),並且初始值就是主構造器中該參數的值

可以給構造函數裏的參數加上默認值。

class User (val nickname: String,val isSubscribed : Boolean = true)
//爲 isSubscribed  參數使用默認值“true”
val alice = User ("Alice")
//可以按照聲明順序寫明所有的參數
val bob = User ("Bob", false)
//可以顯式地爲某些構造方法參數標明名稱
val carol =User ("Carol", isSubscribed  = false)

屬性

接口可以包含抽象屬性聲明,但是接口本身並不包含任何狀態,因此只有實現這個接口的類在需要的情況下會存儲這個值。

interface User {
val nickname: String
}

可以在實現類中賦值

override val nickname: String
get() =.....

接口還可以包含具有 getter 和 setter 的屬性,只要它們沒有引用一個支持字段(支持宇段需要在接口中存儲狀態,而這是不允許的)

interface User {
val email: String
val nickname: String
get () = ....
}

屬性沒有支持字段:結果值在每次訪問時通過計算或其他方式獲得。
val 和 Java 中的 final 類似,表示只讀變量,不能修改,但是可以通過getter()來修改返回值

val size: Int
    get() { //  每次獲取 size 值時都會執行 items.size
        return items.size
    }

在setter 的函數體中,使用了特殊的標識符 field 來訪問支持字段的值。在
getter 中,只能讀取值;而在setter 中 , 既能讀取它 也 能修改它。

object

在java中,常量可以通過static final來定義,Kotlin中,靜態變量和靜態方法這兩個概念被去除了。可以通過companion object來達到相同的效果

class Sample {
    ...
    companion object {
        val anotherString = "Another String"
    }
}

“object ”關鍵字:將聲明一個類與創建一個實例結合起來,也就是說,創建一個類,並且創建一個這個類的對象。如果要使用它,直接通過類名來訪問:Sample.name。
單例
在java中單例這樣寫:

public class A {
    private static A sInstance;
    
    public static A getInstance() {
        if (sInstance == null) {
            sInstance = new A();
        }
        return sInstance;
    }

    ...
}

Kotlin中可以這樣寫

// class 替換成了 object
object A {
    val number: Int = 1
    fun method() {
        println("A.method()")
    }
}

這種通過 object 實現的單例是一個餓漢式的單例,並且實現了線程安全
object 聲明的類,不允許使用構造方法(包括主構造方法和從構造方法)。因爲在定義的時候就立即創建了,不需要在代碼的其他地方調用構造方法 。 因此,定義一個構造方法是沒有意義的。
匿名類
在Java中,匿名類是這樣寫的,

ViewPager.SimpleOnPageChangeListener listener = new ViewPager.SimpleOnPageChangeListener() {
    @Override // 
    public void onPageSelected(int position) {
        // override
    }
};

Kotlin中,是這樣寫的

val listener = object: ViewPager.SimpleOnPageChangeListener() {
    override fun onPageSelected(position: Int) {
        // override
    }
}  

伴生對象
Kotlin 中的類不能擁有靜態成員,作爲替代, Kotlin 依賴包級別函數(在大多數情形下能夠替代 Java 的靜態方法)和對象聲明(在其他情況下替代 Java 的靜態方法,同時還包括靜態字段) 。但是頂層函數不能訪問類的 private成員。通過關鍵字companion,獲得了直接通過容器類名稱來訪問這個對象的方法和屬性的能力

class A {
    companion object {
        fun bar() {
            println ("Companion object called")
        }
    }
}

A.bar()

一個類中最多隻可以有一個伴生對象。
java中的靜態初始化,在Kotlin中也可以實現,

class Sample {
       
    companion object {
       
        init {
            ...
        }
    }
}

工廠模式:

//伴生對象,工廠模式
class UserCom private constructor(val nickName: String) {
    companion object {
        fun newSubscribingUser(email: String) =
            UserCom(email)

        fun newSubscribingUser2(email2: String) =
            UserCom(email2)
    }
}

數據類

在開發中,經常獲取網路數據,需要定義數據類來解析(可以用JsonToKotlin,方便轉換),在java中,會有很多get/set方法,在kotlin中,通過data標識符,可以實現相同的效果:

data class User(val name: String, val age: Int)

重寫了:
• equals 用來比較實例
• hashCode 用來作爲例如 HashMap 這種基於哈希容器的鍵
• toString 用來爲類生成按聲明順序排列的所有字段的字符串表達形式
數據類有一些限制:
• 主構造函數需要至少有一個參數;
• 主構造函數的所有參數需要標記爲 val 或 var;
• 數據類不能是抽象、開放、密封或者內部的;
數據類的屬性,強烈建議使用val來定義。數據類可以通過copy()來複制副本,副本有着單獨的生命週期而且不會影響代碼中引用原始實例的位置。

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
或者
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

類委託

如果要實現一個接口,可以使用 by 關鍵字將接口的實現委託到另一個對象 。官網上給的例子

interface Base {
fun print()
} 
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
} 
class Derived(b: Base) : Base by b
fun main() {
val b = BaseImpl(10)
Derived(b).print()
}

看一個覆蓋的例子

interface Base {
    fun printMessage()
    fun printMessageLine()
}
class BaseImpl(val x: Int) : Base {
    override fun printMessage() { println(x) }
    override fun printMessageLine() { println(x) }
}
class Derived(b: Base) : Base by b {
    override fun printMessage() { println("abc") }
}

fun main(){
   val b = BaseImpl(10)
    Derived(b).printMessage()
    Derived(b).printMessageLine()
    b.printMessage()
    b.printMessageLine()
    }
輸出:
abc
10
10
10

可以看出,這種方式重寫的成員不會在委託對象的成員中調用 , 委託對象的成員只能訪問其自身對接
口成員實現

委託屬性:val/var <屬性名>: <類型> by <表達式> 。 在 by 後邊的表達式是該 委託,因爲屬性對應
的 get() (與 set() ) 會被委託給它的 getValue() 與 setValue() 方法。 屬性的委託不必實現
任何的接口, 但是需要提供一個 getValue() 函數 (與 setValue() -對於 var 屬性)

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.")
}
}

委託屬性,就是可以把一些常見的屬性類型,比如:
延遲屬性 (lazy properties) : 其值只在首次訪問時計算;
可觀察屬性 (observable properties) : 監聽器會收到有關此屬性變更的通知;
把多個屬性儲存在一個映射 (map) 中, 而不是每個存在單獨的字段中。
把get和set方法的一些處理,封裝成一個庫或者公共類,以後的處理可以通過by 來委託給這個庫或公共類

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章