Kotlin基礎三

稀飯_發表於2018-06-12

內容:

  • 類,介面和抽象類
  • 繼承修飾符和可見性修飾符
  • 巢狀類、內部類和密封類
  • 主構造方法和從構造方法,複雜的單例實現
  • 介面定義屬性

一類和介面

在第一篇基礎文章中已經介紹了類的有引數構造方法的宣告語法,但對於開發是遠遠不夠的,還有很多細節需要我們去注意和學習。

1.1 Kotlin中的類和java中的類區別

Kotlin中的類和java中的類大致一樣,但是還是有些許區別,如第一篇講解的。

  1. Kotlin預設是final和public的,而java卻不是。
  2. Kotlin中巢狀類並不是內部類,並沒有包含對其外部類的隱式引用。

   3. 之前文章還沒有介紹構造方法的方法體。

   4.data類,提供了資料類標準的可用的方法。

   5.更簡便的單例模式,伴生物件,以及匿名類,繼承和實現介面等語法

1.2 介面的定義

Kotlin中的介面和java類似,使用interface關鍵字來宣告一個Kotlin的介面。
interface Clickable {

    fun click();
}
複製程式碼
interface Clickable2{
    fun click2();
}複製程式碼
class Son1(name: String, address: String = "") :Father(name,address),Clickable,Clickable2 {
   
    override fun click2() {}

    override fun click() {}
}複製程式碼
使用冒號來代替Java中的extends和implements關鍵字。和Java一樣,單繼承多實現的特性。

       override 修飾符必須要求強制寫出。與java不同的是,java中的介面中定義的必須全部是抽象方法,但是在kotlin中介面中可以實體方法,有點和java中的抽象類相似,像這樣:

interface Clickable {

    fun click()

    fun showoff() {//介面中定義的預設實現方法,預設是open,可以被子類重寫
        Log.e("rrrrrrrrrr", "rrrrrrrr")
    }

}複製程式碼
      如果你實現了這個介面,你需要為click提供一個實現。可以重新定義showoff方法的行為,或者如果你對預設行為感到滿意也可以直接省略它。

假設我們再去定義一個介面,也有一個同名方法的預設實現,而子類不去實現,看看會出現什麼情況

interface Clickable2{
    fun click2()

    fun showoff() {
        Log.e("rrrrrrrrrr", "Clickable2")
    }
}複製程式碼

發現IDE去要求我們必須實現預設方法,否則會報錯

class aaaa : Clickable ,Clickable2{
    override fun click2() {

    }

    override fun showoff() {

    }

    override fun click() {

    }


}複製程式碼

假如我們就是鑽牛角尖,就想在這種情況下呼叫其中一個父類方法,或者兩個父類方法都呼叫,怎麼辦?

Kotlin工程師給我們提供了顯示的呼叫方式!!!

語法:使用尖括號加上父型別名字的“Super”表明了你想要呼叫哪一個父類的方法,像這樣:

override fun showoff() {
    super<Clickable>.showoff()
    super<Clickable2>.showoff()
}複製程式碼

       事實上,java中介面並不能宣告實體方法,那麼Kotlin可以這樣寫,那麼它的編譯原理是什麼呢?它會把每個帶預設方法的介面編譯成一個普通介面和一個將方法體作為靜態函式的類的結合體。

1.3類之間繼承和重寫

Java允許你建立任意類的子類並重寫任意方法,除非顯式地使用了final關鍵字進行標註。而在Kotlin中中預設都是final的。如果你想允許建立一個類的子類,需要使用open符來標示這個類,方法和屬性。類似這樣:
open class DD {

    open var name: String = ""
    
    open fun setValue(neme: String) {
    }

}複製程式碼
class EE: DD() {

   override var name  = "eee"
    override fun setValue(neme: String) {
        this.name = neme
    }

}複製程式碼
注意,如果你重寫了一個基類或者介面的成員,重寫了的成員同樣預設是open的。如果你想改變這一行為,阻止你的類的子類重寫你的實現,可以顯式地將重寫的成員標註為final。像這樣:
open class EE : DD() {

    override var name = "eee"
    
    final override fun setValue(neme: String) {
        this.name = neme
    }
}複製程式碼

延伸: 預設是final的好處,可以更好的支援型別的自動轉換。

1.4抽象類的定義

在Kotlin中,同Java一樣,可以將一個類宣告為abstract的,這種類不能被例項化,預設是open修飾。含有的抽象方法預設是open的,但是非抽象方法預設是final的,需要的時候可以用open修飾。如:
//抽象類預設是open的
abstract class Animated {
    open fun fun1() {
    //預設是final,可以用open去修飾,開啟重寫
    }
    //抽象方法,預設是open狀態
    abstract fun fun2()

}複製程式碼

到了這裡,我們可以看出,介面和抽象類都可以有實體方法,唯一的區別就是抽象類的實體方法預設是final,而介面中實體方法預設是open。

二 繼承修飾符和可見性修飾符

2.1繼承修飾符

 通過上邊類,介面和抽象類的使用可以總結如下:

open: 重寫類,方法,屬性開關,預設介面都是open修飾,抽象類和抽象方法是open修飾。

final:不能被重寫類,方法,屬性開關,預設類中全部是final,抽象類中的實體全是final。

abstract :只有抽象類和抽象方法可以使用,預設被它修飾的同時是被open修飾。

override :重寫父類的識別符號,如果是重寫,必須顯示的顯示出來。

2.2 可見性修飾符

Kotlin中的可見性修飾符與Java中的類似。同樣可以使用public、protected和private修飾符。不同的是,預設java是包內可見,而kotlin是public型別。且kotlin增加了一個internal模組內可見修飾符。

我們都知道,kotlin中多了頂層宣告的函式或方法,這些修飾符大部分同樣適用頂層宣告中。

Kotlin基礎三

延伸:可見修飾符和java有不同的地方,那麼在編譯成位元組碼的時候,kotlin會怎麼轉化呢?

public還是public,private被轉換成包私有,protected和java的一樣,internal會變成public

三 巢狀類和內部類

3.1 巢狀類

和java一樣,Kotlin也支援在一個類中宣告一個類。區別是kotlin的內部類預設情況不能使用外部類物件。

class Student() {
    var name :String?=null

    class Score() {
        
    }

}複製程式碼

預設情況下Score類是不能使用name屬性的,因為他們是巢狀類,內部預設情況下是沒有持有外部類引用的。

3.2 內部類

如我們因為某種需要,就是想寫出類似java中的內部類。這裡介紹一個關鍵字inner,而且引用需要使用this@外部類名字去呼叫。如:

class Outer {
    var arr = ArrayList<String>()
     inner class Innter {
        val size: Int
            get() = this@Outer.arr.size
    }
}
複製程式碼

3.3密封類

為一個類新增一個修飾符sealed,那麼所有的直接子類必須巢狀在父類中。

sealed class A{

    open class B : A(){
        
    }

}複製程式碼

但是可以在外部繼承B類。

四 構造方法

       總算是說道構造方法了,如果看文章的人,應該內心早就罵娘了,這麼久了,還不說構造,你小子到底是不是專業的?講!這就講!看官,讓你久等了。。。。。

在java中構造方法可以過載,而我們已經學過了Kotlin的過載,是不是傳遞一個預設值就行了?這樣只是簡單的過載。構造方法的特殊性,註定它的與眾不同,生下來就含著金鑰匙。Kotlin區分主構造方法從構造方法。

主構造方法:通常是主要而簡潔的初始化類的方法,並且在類體外部宣告。

從構造方法:在類體內部宣告。

4.1主構造方法和方法體

在第一篇已經簡單接觸了主構造方法:

class Person(val name: String)

這段被小括號圍起來的語句塊就叫作主構造方法。他主要有兩個目的:

1.宣告構成方法的引數;

2.可以把引數宣告成類中的屬性。

我們還可以這樣寫:

class Person constructor(name: String) {
    val name: String

    init {
        this.name = name
    }
}複製程式碼
constructor關鍵字用來開始一個主構造方法或從構造方法的宣告。主構造一般情況可以省略。

init關鍵字用來引入一個初始化語句塊。

可以與屬性的宣告結合。主構造方法沒有註解或可見性修飾符,同樣可以去掉constructor關鍵字。像這樣:
class Person(name: String) {
    val name: String = name
}複製程式碼
要建立一個類的例項,只需要直接呼叫構造方法,不需要new關鍵字,事實上,我們一直在這樣做,這裡提一下,kotlin中沒有new這個關鍵字。

注意:如果沒有給這個類宣告任何構造方法或者宣告瞭有引數的構造方法都有預設值,編譯器都會額外的生成一個空引數的構造方法。如:

class Person(name: String="") 複製程式碼
class Person複製程式碼

4.2複雜的單例實現

Kotlin中如果我們想要把一個類變成單例模式你可以這樣寫:

把一個類的構造方法私有化,

class Person private constructor()複製程式碼

而結果卻發現沒法寫靜態方法,因為我們還沒有學。如果用頂層方法嘗試去寫,還是建立不出來私有構造方法。這就是第二篇為什麼說頂層方法不適合單例模式。

錯誤程式碼:

class Person private constructor() {  

}
public  fun  getPerson () =  Person()//這裡會報錯,因為構造方法是私有的複製程式碼

這裡如果提供一個靜態方法的宣告方法多好,Kotlin其實是支援的,需要使用這樣的結構:

//這裡邊的都是靜態方法和靜態屬性
companion object {

}複製程式碼

所以我們可以把方法寫到這裡邊去建立一個單例模式,之後還會講解更簡單的單例模式寫法。

class Person private constructor() {
    //這裡邊的都是靜態方法和靜態屬性
    companion object {
        private var mInstance: Person? = null//宣告一個靜態常量
        //生命一個靜態屬性
        val instance: Person
            //從新寫他的get方法
            get() {
                if (mInstance == null) {
                    synchronized(Person::class.java) {
                        mInstance = Person()
                    }
                }
                return mInstance!!
            }
    }

}複製程式碼

這樣一個複雜的單例模式就建立成功。下一篇我將會講怎麼簡單建立一個單例模式。

4.3次構造方法

假如我們就是想要申請多個構造方法,而且不同的構造方法的初始化邏輯還都不一樣,我們可以這樣去宣告:

class MyView{

    constructor(context: Context){

    }
    constructor(context: Context,attributes: AttributeSet){

    }

}複製程式碼

注意,這個類沒有宣告一個主構造方法。這裡就沒有了無引數的構造方法。

如我們想要自定義一個View,並擴充套件父類方法,你可以這樣讓構造方法分別繼承父類構造方法,像這樣:繼承super

class MyView : View {

    constructor(context: Context) : super(context) {
      //do
    }

    constructor(context: Context, attributes: AttributeSet) : super(context, attributes) {
        //do
    }

}複製程式碼

亦或是呼叫自身類的構造方法:繼承this()

class MyView : View {

    constructor(context: Context) : this(context,null) {
      //do
    }

    constructor(context: Context, attributes: AttributeSet?) : super(context, attributes) {
        //do
    }

}複製程式碼

就是任性想要主構造和次構造都寫出來,就是不用預設值:

class Person constructor() {
    lateinit var name :String

    constructor( name :String):this(){
        this.name = name

    }
}複製程式碼

很顯然達到你的目的了,但是卻失去原有的簡潔性,也沒有意義。何必呢?

總結,構成方法要麼只有主構造,要麼只有次構造,次構造要麼繼承this,要麼繼承super,但必須繼承一個。這樣使用更加合理簡單。

五 屬性

5.1介面定義屬性

我們在第一篇已經介紹了可變屬性,不可變屬性,重寫get和set方法。這裡我們更深入瞭解一下屬性。

在java中介面中不能定義屬性,只能定義抽象方法。在抽象類中,java可以定義屬性,子類也可以使用屬性。而在Kotlin中,介面可以定義屬性,抽象類和java一樣可以定義屬性,使用屬性。

比如我們在介面P中宣告瞭一個屬性:

interface P {
    val name: String
}複製程式碼

那麼這個屬性並沒有對應的get或者set方法的,這裡需要我們子類自己去實現它的get或者set方法,你可以這樣寫:

class Person(override var name: String) :P複製程式碼

或者自己去定義get和set方法。

突然想到我們介面可以去實現方法,那麼我們能不能在介面中去直接把get、set方法定義好呢?當然是可以的!

我們可以直接去在介面中提供get方法,程式碼修改成這樣:

interface P {
    val name: String
    get()= "張三"
}複製程式碼

且子類可以重寫。

class Person() :P{
//    override val name:String
//    get() = "李四"

}複製程式碼

注意:如果重寫,列印就是李四,如果不重寫,列印就是張三。


小結:

類之間的整合需要使用open關鍵字,重寫方法必須使用override關鍵字,不想被重寫用final關鍵字。

介面可以寫實體方法,介面可以宣告屬性,但是沒有get和set方法,預設全部是open修飾,抽象類實體方法預設是final狀態。

巢狀類不是內部類,內部類需要使用innter關鍵字,密封類是所有的直接子類都要是巢狀類。

主構造方法和從構造方法一般不會同時出現,過載多個構造方法,繼承super或this。

可見性修飾符public 全部可見,private 類或者檔案內可見,propected子類可見,還有一個模組可見。



相關文章