一天入門Kotlin學習筆記(四)-面向對象

前言

本章將講解一些管對對象和基礎編程的知識。如:類,接口,抽象類,數據類,擴展方法等

接口

其實這部分的設計思路和Java基本一致。這裏主要說下Kotlin特有的屬性

abstract class Manager : Driver, Writer {
    override fun driver() {
    }

    override fun writer() {
    }

}

interface Driver {
    fun driver(){
        println("driver")
    }
}

class CarDriver : Driver {
    override fun driver() {
        println("開車")//接口的默認實現
    }

}

interface Writer {
    fun writer()
}

class PPTWriter : Writer {
    override fun writer() {
        println("寫PPT")
    }

}

class SeniorManager(val driver: Driver, val writer: Writer) : Driver by driver, Writer by writer {//實現忌口但是通過by關鍵字可以不用去實現裏面的接口方法


}

/**
 * 不加by關鍵字那麼就要實現接口的方法
 */
class SeniorManager1(val driver: Driver, val writer: Writer) : Driver, Writer {
    override fun driver() {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun writer() {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }


}

fun main(args: Array<String>) {
    val driver = CarDriver()
    val writer = PPTWriter()
    val seniorManager = SeniorManager(driver, writer)
    seniorManager.driver()
    seniorManager.writer()
}
  • interface代理用 by關鍵字,可以不用複寫實現的interface的方法,同時不實現會執行by後面對象裏的方法
  • interface中的抽象方法可以有默認實現。如果不去實現將執行默認方法
抽象類

接口是約定,抽象類本是是類的一種:

abstract class Person(val age: Int) {
    open fun work() {
    }
}

abstract class Person1(open val age: Int = 3) {

    abstract fun work()
}


class MaNong(age: Int) : Person(age) {
    override fun work() {
        super.work()
        println("我是碼農,我在寫代碼")
    }
}

class Doctor(age: Int) : Person1(age) {
    override val age: Int
        get() = 6//5.傳入後又重寫

    override fun work() {
        println("我是醫生,我給人看病")
    }
}
  1. 默認的類和方法都是final,如果想要被子類繼承,需要加open關鍵字修飾;
  2. 子類覆寫父類方法時需要加關鍵字override修飾
  3. 用open表示父類方法有默認的實現 子類可以有super.work 用abstract表示不可以有默認實現 他是一個抽象的無實現方法
  4. 不止可以重寫方法方法,還可以重寫屬性
  5. 內部重寫是優先級高於外部傳入的
可見性修飾符
Kotlin Java
private private
protected↑ protected
- default (包內可見)
internal (模塊Module內可見) -
public public
object類關鍵字
  • object修飾的類只有一個實例的類
  • 本質上就是單例模式最基本的實現(static代碼塊)
  • 不能自定義構造方法(因爲是單例)
  • 可以實現接口、繼承父類

例子:

interface Listener {
    fun start()
    fun stop()
}

abstract class Player

object MusicPlayer : Player(), Listener {//可以實現接口,繼承父類
    override fun start() {
    }

    override fun stop() {
    }

    val state = 0//可以有成員屬性

    fun continuePlay() {//可以有方法
    }
}

fun main(args: Array<String>) {
    val music: MusicPlayer = MusicPlayer//後面沒有括號,也就是說明不是調用構造方法創建的對象
}
伴生對象與靜態成員

kotlin 中時沒有static 這種方法修飾的靜態方法 所以要實現 類似於java中的靜態屬性就要用到伴生對象
例子:

fun main(args: Array<String>) {
    /**
     * 這時候調用伴生對象就相當於調用java靜態方法 格式相同
     */
    val value = Latitude.ofDouble(3.0)
    println(Latitude.TAG)
}

/**
 * 私有的構造方法  companion伴生對象關鍵字
 */
class Latitude private constructor(val latitude: Double) {
    companion object {
        fun ofDouble(double: Double): Latitude {
            return Latitude(double)
        }

        @JvmStatic//加上這個註解可以在Java中如static一樣調用
        fun ofLatitude(latitude: Latitude): Latitude {
            return Latitude(latitude.latitude)
        }

        @JvmField//加上這個註解可以在Java中如static一樣調用
        val TAG = "Latitude"
    }
}
  • 每個類可以對應一個伴生對象
  • 伴生對象的成員全局獨一份
  • 伴生對象的成員類似Java的靜態成員
  • 靜態成員考慮用包級函數、變量替代
  • JvmField 和JvmStatic使用後,可以用相同的語法在Java中調用
方法重載

與Java相同,,需要注意一下幾點

  • 方法的重載與默認參數 返回值類型不能作爲方法簽名的一部分 只有參數列表和方法名
  • 重載時如果不能用默認參數解決的重載 不是一個好的設計 例如 list.remove
  • 默認參數 可以參數 不傳參數用默認值 這個方法java要調用 需要加上 @JvmOverloads否則必須傳參數
擴展方法

爲現有類添加方法、屬性:

  • fun X.y):Z{…}
  • val X.m 注意擴展屬性不能初始化,類似接口屬性
  • Java 調用擴展成員類似調用靜態方法

例子:


//自己定義一個擴展方法 方法的名字是  類型.方法名
fun String.copy(int: Int): String {
    val buildStr = StringBuilder()
    for (i in 0 until int) {
        buildStr.append("abc")
    }
    return buildStr.toString()

}

//也可以用操作符
operator fun String.times(int: Int): String {
    val buildStr = StringBuilder()
    for (i in 0 until int) {
        buildStr.append("abc")
    }
    return buildStr.toString()

}

輸出:

fun main(args: Array<String>) {
    println("abc".copy(5))
    println("abc" * 5)//操作符 times對應* 具體參照文檔 https://kotlinlang.org/docs/reference/operator-overloading.html
}
屬性代理

代理的場景 比如定義一個屬性外界去訪問,可以在getValue去讀取一個文件setValue去寫入一個文件那麼就相當於與讀取一個就可以文件,調用處代碼變得非常簡潔

class Delegates4_9 {
    val hello by lazy {
        //懶加載 只有第一次使用的時候纔回去初始化
        "helloWorld"
    }

    val hello2 by LazyX()//代理 //不可變代理 使用2處代碼
    var hello3 by LazyX()//代理

}

class LazyX {
    var value: String? = null

    //仿寫Lazy   2
//    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
//        return "Hello Lazy"
//    }

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("getValue: $thisRef ->${property.name}")
        return "Hello Lazy"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("setValue: $thisRef ->${property.name} = $value")
        this.value = value
    }
}

fun main(args: Array<String>) {
    val delegates = Delegates4_9()
    println(delegates.hello)
    println(delegates.hello2)
    println(delegates.hello3)
    delegates.hello3 = "value of hello3"
    println(delegates.hello3)
}
數據類

主要是講解data關鍵字,data主要是幫助生成copy,toString,componentN(對應返回定義的參數) hasCode,equals等方法,默認是沒有無參數的構造方法並且生成的類是final的,需要用allOpen去掉final,noArg創建無參數構造函數
allOpen/noArg

appModule下build.gradle 
apply plugin: 'kotlin-noarg'

apply plugin: 'kotlin-allopen'

noArg {
    annotation("packageName.Poko")
}

allOpen {
    annotation("packageName.Poko")
}

項目下的build.gradle:

 dependencies {
        classpath "org.jetbrains.kotlin:kotlin-noarg:1.3.21"
        classpath "org.jetbrains.kotlin:kotlin-allopen:1.3.21"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }

使用:

@Poko
data class Country4_10(val id: Int, val name: String)
內部類

Kotlin的內部類與Java內部類概念相同。存在以下幾種不同點:

  • Kotlin內部類定義時如果沒有用inner修飾的話就是靜態內部類,用inner修飾後是非靜態內部類
  • 匿名內部類:
    1. 沒有定義名字的內部類
    2. 類名編譯時生成,類似0utter$1. class .
    3. 可繼承父類,實現多個接口,與Java注意區別.
  • kotlin內部類與java 內部類的區別
    1. java的內部類可以直接訪問外部類的成員, kotlin的內部類不能直接訪問外部類的成員
    2. 必須用inner標記之後才能訪問外部類的成員(非靜態內部類持有外部類的引用,而靜態內部類無法持有外部類的引用,這是因爲靜態內部類優先於非實例對象而存在)
  • 內部類和靜態內部類的區別:
    1. 是否持有外部類的狀態(也就是非靜態內部類中可以調用 外部類.this.屬性/方法 靜態內部類做不到)
  • 匿名內部類和內部類的區別:
    1. 內部類沒有定義名字直接new出來 但是在編譯後也是有的類似0utter$1. class .

例子:

class Outter {
    val a = 0

    class Inner {
        fun main(args: Array<String>) {
//            println(a)//訪問不到 說明kotlin中默認是使用靜態static內部類的
            println(Outter().a)
        }
    }

    inner class Inner1 {
        //inner關鍵字 變成非靜態 這樣就可以訪問到外部類的屬性的
        val a = 6

        fun main(args: Array<String>) {
//            println(a)//訪問不到 說明kotlin中默認是使用靜態static內部類的
            println([email protected])//當內外部類重載相同的屬性或方法時 通過this@Outter訪問
            println([email protected])
            println(a)
        }
    }
}


interface OnClickListener {
    fun click()
}

class View {
    var onClickListener: OnClickListener? = null
}

open class Text

fun main(args: Array<String>) {
    val view = View()
    view.onClickListener = object : Text(), OnClickListener {
        //java 匿名內部類是不能繼承的 kotlin可以
        //用object關鍵字來實例化內部類
        override fun click() {

        }

    }
}
枚舉類

基本與Java一致。例子:

enum class LogcatLevel(val id: Int) {

    VERBOSE(5), DEBUG(6), INFO(7), WARN(8), ERROR(9), ASSERT(10);//枚舉類可以定義方法

    fun getTag(): String {
        return "$name , $id"
    }

    override fun toString(): String {
        return "$name , $ordinal"
    }
}

Kotlin枚舉類中定義方法,那麼要在枚舉對象最後加上; 這基本是Kotlin中唯一一個需要強制寫;的地方

密封類
  • 密封類與枚舉的區別:前者是子類可數,後者是實例可數
  • 密封類(Sealed Class)的子類必須和父類定義在同一個文件中,或者作爲它的內部類。
  • 密封類的子類是可數,因爲子類只能在父類內部或者和父類處於同一個文件,在其他地方是無法創建子類的。這個可數的定義就是有限的 一目瞭然知道的

結語

記過這4篇文章的學習,基本已經掌握Kotlin的基本語法和對Java的對比,也順帶複習了一下Java的知識,下一篇我們來學習一些關於Kotlin的高階函數

Github源碼直接運行,包含全部詳細筆記

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