Kotlin核心語法(三):kotlin類、對象、接口

博客主頁java

定義類繼承結構

1.kotlin中的接口

kotlin的接口能夠包含抽象方法的定義和非抽象方法的實現,但不能包含任何狀態。json

// 使用interface關鍵字聲明一個kotlin接口
interface Clickable {
    fun click()
}

// kotlin 在類名後面使用冒號(:)代替java中的extends和implements關鍵字
// 與java同樣,一個類能夠實現任意多個接口,但只能繼承一個類
class Button : Clickable {
    // override 修飾符用來標註被重寫的父類或者接口的方法和屬性,與java中的@Override註解相似
    // kotlin中override 修飾符是強制要求的
    override fun click() {
        println(" i was clicked.")
    }
}

接口中的方法能夠有一個默認實現,只須要提供一個方法體。segmentfault

interface Clickable {
    fun click()
    // 帶默認實現的方法,在子類中也能夠從新定義該方法的行爲
    fun showOff() = println("I'm clickable.")
}

若是在類中同時實現了兩個接口,而這兩個接口都包含了帶默認實現的showOff方法,若是沒有顯式實現showOff,編譯器報錯,強制要求提供實現。ide

interface Focusable {
    fun showOff() = println("I'm focusable.")

    fun setFocus(b: Boolean) =
        println("i ${if (b) "got" else "lost"}")
}

class Button : Clickable, Focusable {
    // 若是實現的兩個接口中有相同的默認的實現方法,必須顯式的實現一個
    override fun showOff() {
        // 使用尖括號加上父類型名字的super代表了想要調用哪個父類的方法
        // java中把基類的名字放在super關鍵字的前面:Clickable.super.showOff()
        super<Clickable>.showOff()
        super<Focusable>.showOff()
    }

    override fun click() {
        println(" i was clicked.")
    }
}

2. open、final、abstract修飾符:默認爲final

java的類和方法默認是open的,而kotlin中默認都是final的。函數

// 聲明一個帶一個open方法的open類,其餘的類能夠繼承它
open class RichButton : Clickable {
    // 這個函數重寫一個open函數且它自己一樣是open的
    // public void click()
    override fun click() {}

    // 這個函數時final的,不能在子類中重寫它
    // public final void disable()
    fun disable() {}

    // 這個函數是open的,能夠在子類中重寫它,若是不想子類重寫它,能夠顯式的標註爲final
    // public void animate()
    open fun animate() {}
}

kotlin中聲明一個抽象類使用 abstract 修飾符,這種類不能實例化。post

abstract class Animated {
    // 這個函數時抽象的,它沒有實現必須被子類重寫。抽象成員始終是open的
    // public abstract void animate();
    abstract fun animate()

    // 抽象類中的非抽象函數並非默認open的,但能夠標註爲open的
    // public void stopAnimating()
    open fun stopAnimating() {}

    // 抽象類中的非抽象函數並非默認open的
    // public final void animateTwice()
    fun animateTwice() {}
}

類中訪問修飾符的意義,在接口中,不能使用final、open或者abstract,接口中的成員始終是open的,不能將其聲明爲final的。this

修飾符 相關成員 評註
final 不能被重寫 類中成員默認使用
open 能夠被重寫 須要明確的代表
abstract 必須被重寫 只能在抽象類中使用,抽象成員不能有實現
override 重寫父類或者接口中的成員 若是沒有使用final代表,重寫的成員默認是開放的

3.可見性修飾符:默認爲public

kotlin中的可見性修飾符與java相似,public、private、protected,但默認的可見性不同,kotlin中若是省略了修飾符,聲明就是public的,java中默承認見性是包私有,kotlin中並無使用,kotlin只把包做爲在命名空間裏組織代碼的一種方式使用,沒有將其做可見性控制。rest

kotlin提供一個新的修飾符,internal,表示 只在模塊內部可見internal 優點在於它提供了對模塊實現細節的真正封裝。java中外部代碼能夠將類定義到與你代碼相同的包中,從而獲得訪問包私有聲明的權限。code

kotlin也容許在頂層聲明中使用private可見性,包括類、函數、屬性。這些聲明就會只在聲明它們的文件中可見。對象

修飾符 類成員 頂層聲明
public(默認) 全部地方可見 全部地方可見
internal 模塊內部可見 模塊內部可見
protected 子類中可見 ---
private 類中可見 文件中可見

4.內部類和嵌套類:默認是嵌套類

kotlin的嵌套類不能訪問外部類的實例。在java中,在另外一個類中聲明一個類時,它會默認變成內部類,隱式地存儲了它的外部類的引用,若是內部類上加上static修飾符,靜態的嵌套類會從這個類中刪除包圍它的類的隱式引用。

interface State : Serializable

interface View {
    fun restoreState(state: State)
    fun getState(): State
}

class Button : View {
    override fun getState(): State {
        return ButtonState()
    }

    override fun restoreState(state: State) {
    }

    // kotlin中沒有顯式修飾符的嵌套類與java中的static嵌套類同樣
    // 若是要把它變成一個內部類來持有一個外部類的應用,須要使用inner修飾符
    class ButtonState : State {}
}

嵌套類和內部類在java與kotlin中對應關係

類A在另外一個類B中聲明 在java中 在kotlin中
嵌套類(不存儲外部類的引用) static class A class A
內部類(存儲外部類的引用) class A inner class A

在kotlin中,引用外部類實例的語法與java不一樣,須要使用 this@Outer 從Inner類去訪問Outer類

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

5.密封類:定義受限的類繼承結構

先來看下做爲接口實現的表達式,必須檢查else分支

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

fun eval(e: Expr): Int =
    when (e) {
        is Num -> e.value
        is Sum -> eval(e.left) + eval(e.right)
        // 必須檢查else分支
        else -> throw IllegalArgumentException("Unknown")
    }

kotlin提供了一個解決方案:sealed類。爲父類添加一個sealed修飾符,對可能建立的子類作出嚴格的限制,全部直接子類必須嵌套在父類中。

// 將基類標記爲密封類
// sealed修飾符隱含這個類是一個open類,不需顯式的添加open修飾符
sealed class Expr {
    // 將全部可能的類做爲嵌套類列出
    class Num(val value: Int) : Expr()
    class Sum(val left: Expr, val right: Expr) : Expr()
}

// when表達式處理全部的sealed類的子類,不在須要提供默認的else分支
fun eval(e: Expr): Int = when (e) {
    is Expr.Num -> e.value
    is Expr.Sum -> eval(e.left) + eval(e.right)
}

聲明一個帶非默認構造方法或者屬性的類

1.初始化類:主構造方法和初始化語句塊

// 這段被括號圍起來的語句塊叫做主構造方法,代表構造方法的參數,以及定義使用這些參數初始化的屬性
// val 意味着相應的屬性會用構造方法的參數來初始化
class User(val nickname: String)

// constructor 關鍵字用來開始一個主構造方法或者從構造方法的聲明
class User constructor(_nickname: String) {
    val nickname: String
    
    // init關鍵字用來引入一個初始化語句塊,包含了在類被建立時執行的代碼,與主構造方法一塊兒使用
    // 能夠在一個類中聲明多個初始化語句塊
    init {
        nickname = _nickname
    }
}

// 若是主構造方法沒有註解或者可見性修飾符,能夠去掉constructor關鍵字
class User(_nickname: String) {
    val nickname = _nickname
}

能夠像函數參數同樣爲構造方法參數聲明一個默認值

// 爲構造方法參數提供一個默認值
class User(
    val _nickname: String,
    val isSubscribed: Boolean = true
)


val kerwin = User("kerwin", false)
println(kerwin.isSubscribed) // false

val blob = User("blob") // isSubscribed參數使用默認值true
println(blob.isSubscribed) // true

// 能夠顯式的爲某些構造方法參數標明名稱
val alice = User("Alice", isSubscribed = false)
println(alice.isSubscribed) // false

若是一個類有父類,且該類的主構造方法也須要初始化父類,能夠經過該類的主構造方法參數引用提供給父類構造方法參數

open class User(
    val nickname: String,
    val isSubscribed: Boolean = true
)

class TwitterUser(nickname: String) : User(nickname)

若是類不被其餘代碼實例化,必須把構造方法標記爲private

// 由於Person類只有一個private的構造方法,這個類外部代碼不能實例化它
class Person private constructor() {}

2. 構造方法:用不一樣的方式來初始化父類

// 這個類沒有主構造方法,聲明瞭兩個從構造方法
// 從構造方法能夠聲明多個
open class View {
    constructor(ctx: Context) {}

    constructor(ctx: Context, attr: AttributeSet?) {}
}

class MyButton : View {
    // 可使用this關鍵字,調用本身類的另外一個構造方法
    constructor(ctx: Context) : this(ctx, null) {}

    // 調用父類構造方法,使用super關鍵字調用父類對應的構造方法
    constructor(ctx: Context, attr: AttributeSet?) : super(ctx, attr) {}
}

3. 實如今接口中聲明的屬性

kotlin中,接口能夠包含抽象屬性聲明。

// 接口沒有說明這個屬性存儲到一個支持字段仍是經過getter來獲取,自己並不包含任何狀態
interface User {
    val nickname: String
}

// 直接在主構造方法聲明一個來自實現了User抽象屬性
class PrivateUser(override val nickname: String) : User

// 經過自定義getter,每次訪問須要計算
class SubscribingUser(val email: String) : User {
    override val nickname: String
        get() = email.substringBefore("@")
}

// 屬性初始化,一個支持字段來存儲
class FacebookUser(val accountId: Int) : User {
    override val nickname = accountId.toString()
}

接口還能夠包含具備getter和setter屬性,只要它們沒有引用一個支持字段

interface User2 {
    // 必須在子類中重寫
    val email: String
    // 能夠被繼承,屬性沒有支持字段,結果值在每次訪問時經過計算獲得
    val nickname: String
        get() = email.substringBefore("@")
}

4. 經過getter或者setter訪問支持字段

// 在setter中訪問支持字段
class User(val name: String) {
    var address: String = "Unknown"
        set(value) {
            println("Addressn was changed for $name: $field -> $value")
            // 使用特殊的標識符field 來訪問支持字段的值
            // getter中只能讀取值,setter中,既能讀取它也能修改它
            field = value
        }
}

// 修改屬性的值,在底層調用了setter
>>> val user = User("kerwin")
>>> user.address = "shanghai"

5. 修改訪問器的可見性

訪問器的可見性默認與屬性的可見性相同。

// 聲明一個具備private setter的屬性
class LengthCounter {
    // 不能在類外部修改這個屬性,確保只能在類中被修改
    var counter: Int = 0
        private set

    fun addWord(word: String) {
        counter += word.length
    }
}

編譯器生成的方法:數據類和類委託

1. 通用對象方法

字符串表示:toString()

class Client(
    val name: String,
    val postalCode: Int
) {
    // 重寫toString
    override fun toString() = "Client{name:$name, postalCode:$postalCode}"
}

>>> val client = Client("kerwin", 1234)
>>> println(client)
Client{name:kerwin, postalCode:1234}

對象相等性:equals()

class Client(
    val name: String,
    val postalCode: Int
) {
    override fun toString() = "Client{name:$name, postalCode:$postalCode}"
 
    // 重寫equals
    override fun equals(other: Any?): Boolean {
        // kotlin中is檢查一個值是否爲一個指定的類型
        if (other == null || other !is Client) {
            return false
        }
        return name == other.name && postalCode == other.postalCode
    }
}

// 在kotlin中,==檢查對象是否相等,而不是比較引用
>>> val client1 = Client("kerwin", 1234)
>>> val client2 = Client("kerwin", 1234)
>>> println(client1 == client2)
true
在kotlin中,==運算符是比較兩個對象的默認方式:本質上就是經過調用equals來比較兩個值的。若是進行引用比較,可使用 ===運算符

Hash容器:hashCode()
hashCod方法一般與equals一塊兒被重寫。

// 什麼狀況?返回了false
// 緣由就是Client缺乏了hashCode方法,若是兩個對象相等,必須有這相同的hash值
// HashSet中的值首先比較它們的hash值,相等纔會比較值
>>> val set = hashSetOf(Client("kerwin", 1234))
>>> println(set.contains(Client("kerwin", 1234)))
false


class Client(
    val name: String,
    val postalCode: Int
) {
    // ...

    // 重寫hashCode     
    override fun hashCode(): Int = name.hashCode() * 31 + postalCode
}

2. 數據類:自動生成通用方法的實現

在kotlin中,在類添加data修飾符,自動生成通用的方法(toString、equals、hashCode)實現。

// 數據類Client
// equals和hashCode方法會將全部在主構造方法中的聲明的屬性歸入考慮。
// 注意:沒有在主構造方法中聲明的屬性將不會加入到相等性檢查和哈希值計算中去
data class Client(
    val name: String,
    val postalCode: Int
)

數據類和不可變性:copy()方法
數據類的屬性推薦只使用只讀屬性(val),讓數據類的實例不可變。
爲了讓使用不可變對象的數據類變得更容易,kotlin編譯器爲它們多生成了一個方法,一個容許copy類的實例的方法,並在copy的同時修改某些屬性的值。

class Client(
    val name: String,
    val postalCode: Int
) {

    override fun toString() = "Client{name:$name, postalCode:$postalCode}"

    fun copy(
        name: String = this.name,
        postalCode: Int = this.postalCode
    ) = Client(name, postalCode)
}

>>> val client = Client("kerwin", 1234)
>>> println(client.copy(postalCode = 4321))
>>> println(client)
Client{name:kerwin, postalCode:4321}
Client{name:kerwin, postalCode:1234}

3. 類委託:使用by關鍵字

class DelegatingCollection<T> : Collection<T> {
    private val innerList = arrayListOf<T>()

    override val size: Int
        get() = innerList.size

    override fun contains(element: T): Boolean = innerList.contains(element)

    override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(elements)

    override fun isEmpty(): Boolean = innerList.isEmpty()

    override fun iterator(): Iterator<T> = innerList.iterator()
}

在kotlin中,不管何時實現一個接口,均可以使用by關鍵字將接口的實現委託到另外一個對象。

// Collection接口的實現經過by關鍵字委託給innerList
// 編譯器會自動生成方法的實現
class DelegatingCollection<T>(
    val innerList: Collection<T> = arrayListOf()
) : Collection<T> by innerList

object關鍵字:將聲明一個類與建立一個實例結合起來

  1. 對象聲明是定義單例的一種方式
  2. 伴生對象能夠持有工廠方法和其餘與這個類相關,但在調用時並不依賴類實例方法。他們的成員能夠經過類名來訪問。
  3. 對象表達式用來替代java的匿名內部類

1. 對象聲明:建立單例易如反掌

對象聲明是經過 object 關鍵字引入。

// 與類同樣,一個對象聲明也能夠包括屬性、方法、初始化語句塊等。
// 惟一不容許的有構造方法(包括主構造方法和從構造方法)
// 與普通類的實例不一樣,對象聲明在定義的時就當即建立
object Payroll {
    val allEmployees = arrayListOf<Person>()

    fun calculateSalary() {
        for (person in allEmployees) {
        }
    }
}

// kotlin中的對象聲明被編譯成了經過靜態字段來持有它的單一實例的類,這個字段名字始終都是INSTANCE
// kotlin代碼轉換爲java代碼
public final class Payroll {
   @NotNull
   private static final ArrayList allEmployees;
   public static final Payroll INSTANCE;

   @NotNull
   public final ArrayList getAllEmployees() {
      return allEmployees;
   }

   public final void calculateSalary() {
      Person var1;
      for(Iterator var2 = allEmployees.iterator(); var2.hasNext(); var1 = (Person)var2.next()) {
      }

   }

   private Payroll() {
   }

   static {
      Payroll var0 = new Payroll();
      INSTANCE = var0;
      boolean var1 = false;
      allEmployees = new ArrayList();
   }
}

對象聲明能夠繼承類和實現接口。對於實現並不包含任何狀態的時候頗有用,如:實現java.util.Comparator接口,只需一個單獨的Comparator實例比較對象。

object FileComparator : Comparator<File>{
    override fun compare(o1: File, o2: File): Int {
        return o1.path.compareTo(o2.parent, ignoreCase = true)
    }
}

能夠在類中聲明對象,這樣對象也一樣只有一個單一實例

// 使用嵌套類實現Comparator
data class Person(val name: String) {
    object NameComparator : Comparator<Person> {
        override fun compare(o1: Person, o2: Person): Int {
            return o1.name.compareTo(o2.name)
        }
    }
}

>>> val list = listOf(Person("Kerwin"), Person("Bob"))
>>> println(list.sortedWith(Person.NameComparator))
    // [Person(name=Bob), Person(name=Kerwin)]

2. 伴生對象:工廠方法和靜態成員的地盤

kotlin中的類不能擁有靜態成員,java的static關鍵字並非kotlin語言的一部分。

kotlin對象聲明在其餘狀況下替代java的靜態方法,同時還包括靜態字段
kotlin依賴包級別函數在大多數狀況下可以替代java的靜態方法
推薦使用頂層函數,但頂層函數不能訪問類的private成員。

在類中定義的對象之一可使用一個特殊的關鍵字來標記:companion

class A {
    // 伴生對象
    companion object {
        fun bar() {
            println("companion object called.")
        }
    }
}

// 能夠直接經過類名來訪問這個伴生對象的方法和屬性
// 這種調用方式有點像java中靜態方法的調用
>>> A.bar()

伴生對象能夠訪問類中的全部的private成員,包括private構造方法。使用工廠方法來建立類實例,下面User實例就是經過工廠方法來建立,而不是經過多個構造方法。伴生對象成員在子類中不能被重寫。

// 將主構造方法標記爲私有的,該類不被其餘代碼實例化
class User private constructor(
    val nickname: String
) {
 
    // 聲明伴生對象 
    companion object {
        fun newSubscribingUser(email: String) = User(email.substringBefore("@"))

        // 用工廠方法建立一個新用戶
        fun newFacebookUser(accountId: Int) = User(accountId.toString())
    }
}

// 經過類名調用 companion object 的方法
>>>   val user = User.newSubscribingUser("kerwin0618@163.com")
>>>   println(user.nickname)
kerwin0618

3. 做爲普通對象使用的伴生對象

伴生對象是一個聲明在類中的普通對象。能夠有名字,實現一個接口或者有擴展函數或者屬性。

class Person(
    val name: String
) {
   
    // 伴生對象能夠有名字,若是省略伴生對象名字,默認名字爲Companion
    // 伴生對象沒有命名,在java中,能夠經過Person.Companion.fromJSON("");訪問
    companion object Loader {
        fun fromJson(jsonText: String) : Person {
            return Person(jsonText)
        }
    }
}

// 能夠經過兩種方式來調用fromJson
>>>    Person.Loader.fromJson("{name: 'kerwin'}")
>>>    Person.fromJson("{name: 'kerwin'}")

在伴生對象中實現接口:

interface JSONFactory<T> {
    fun fromJSON(jsonText: String): T
}

class Person(
    val name: String
) {

    // 在伴生對象中實現接口
    companion object : JSONFactory<Person> {
        override fun fromJSON(jsonText: String): Person {
            return Person(jsonText)
        }
    }
}

fun <T> loadFromJSON(factory: JSONFactory<T>) : T {
    return factory.fromJSON("{name: 'kerwin'}")
}

// 將伴生對象實例傳入函數中。Person類的名字被看成JSONFactory的實例
>>>    loadFromJSON(Person)

若是要和java代碼一塊兒工做,須要類中的成員是靜態的,能夠在對應的成員上使用 @JvmStatic 註解達到目的。若是想聲明一個static字段,能夠在一個頂層屬性或者聲明在object中的屬性上使用 @JvmField 註解。

伴生對象擴展:擴展函數定義經過類的實例調用方法 ,但須要定義能夠經過類調用方法怎麼辦呢?

// 爲伴生對象定義一個擴展函數
data class Person(
    val firstName: String,
    val lastName: String
) {
    // 聲明一個空的伴生對象
    companion object {

    }
}

// 聲明一個擴展函數
fun Person.Companion.fromJSON(jsonText: String) : Person {
    return Person("xu", "jinbing")
}

4. 對象表達式:改變寫法的匿名內部類

object 關鍵字不只僅能用來聲明單例式的對象,還能用來聲明匿名對象。匿名對象代替java中匿名內部類的用法。

// 使用匿名對象來實現事件監聽器
    val button = Button()
    button.setOnClickListener(
        // 聲明一個實現OnClickListener的匿名對象
        object : OnClickListener {
           // 重寫OnClickListener的方法
            override fun onClick(view: View) {

            }
        }
    )

   // 給對象分配一個名字
   val listener = object : OnClickListener {
        override fun onClick(view: View) {
        }
    }

kotlin的匿名對象能夠實現多個接口或者不實現接口。

與對象聲明不一樣,匿名對象不是單例的,每次對象表達式被執行都會建立一個新的對象實例。

對象表達式中的代碼能夠訪問建立它的函數中的變量,與java不一樣,訪問並無被限制在final變量,還能夠在對象表達式中修改變量的值

// 匿名對象訪問局部變量
fun countClicks(button: Button) {
    // 聲明局部變量
    var clickCount = 0
    button.setOnClickListener(object : OnClickListener {
        override fun onClick(view: View) {
            // 更新變量的值
            clickCount++
        }
    })
}

若是個人文章對您有幫助,不妨點個贊鼓勵一下(^_^)

相關文章
相關標籤/搜索