Kotlin 之類型進階

類的構造器

  • 構造器

    • 主構造器

      //age 全局可用,name 只是普通的構造器參數
      class Person(var age: Int, name: String) {
          
      }
      
    • init 塊

      //age 全局可用,name 只是普通的構造器參數
      class Person(var age: Int, name: String) {
      
          val n: String
          init {
              n = name
          }
      }
      

      init 塊會在構造方法的最前面執行,可用於初始化

    • 屬性初始化

      ​ 可在 init 塊中進行初始化

    • 重寫 get / set

      class Person() {
          var name: String = ""
              get() {
                  return "hello $field"
              }
              set(value) {
                  field = "hello $value"
              }
      }
      
  • 副構造器

    • 不定義主構造器

      open class A {
          constructor(name: String) {
              println("A的 一個參數構造器")
          }
      
          constructor(name: String, age: Int) {
              println("A的 兩個參數構造器")
          }
      
          init {
              println("A的 init")
          }
      }
      
      class B : A {
      
          constructor(name: String) : this(name, 20) {
              println("B的 一個參數構造器")
          }
      
          constructor(name: String, age: Int) : super(name, age) {
              println("B的 兩個參數的構造器")
          }
      }
      
      fun main() {
          var lv = B("張三")
      }
      
      A的 init
      A的 兩個參數構造器
      B的 兩個參數的構造器
      B的 一個參數構造器
      
    • 主構造器默認參

      open class A(age: Int, name: String = "張三") {
      }
      
      fun main() {
          var a = A(20)
      }
      
  • 工廠函數

    open class A(age: Int, name: String = "張三") {
    
        fun A(): A {
            return A(20)
        }
    
        fun A(age: Int, name: String): A {
            return A(age, name)
        }
    
    }
    

    函數的名字可以和類名相同

可見性對比

可見性對比 java Kotlin
public 公開 與 Java 相同,默認
internal X 模塊內可見
default 包內可見,默認 X
protected 包內及子類可見 類內及子類可見
private 類內可見 類或文件可見

​ 模塊的概念:直觀的講,大致可以認爲是一個 Jar包,一個 aar

  • IntelliJ IDEA 模塊

  • Maven 模塊

  • Gradle SourceSet

  • Ant 任務中一次調用 的文件

    如果定義了 internal 的函數,但是不想讓 java 訪問,可以使用 @JvmName("") 註解來實現,在 java 中調用的時候註解測參數將會成爲方法的名字,且報錯,不可以調用。

構造器的可見性

  • 給構造器加註解的時候 constructor 就必須寫出來
  • 如果使用單例,或者工廠等,就需要將構造器弄成私有的

頂級聲明的可見性

  • 指文件內直接定義的屬性,函數,類等
  • 頂級聲明不支持 protected
  • 頂級聲明被 private 修飾後表示文件內部可見

類屬性的延時初始化

  • 類屬性必須在構造時初始化

  • 某些成員只有在類構造後纔會被初始化,例如android 中在 onCrate 中進行 findViewById

解決辦法

  1. 直接判空,或者類型強轉

  2. 使用 lateinit 關鍵字

    lateinit 會讓編譯器忽略變量的初始化,不支持 Int 等基本類型

    開發者必須在完全確定變量值生命週期的情況下使用 lateinit

    不要在複雜的邏輯中使用 lateinit

  3. 使用 lazy

    只有在首次使用的時候被訪問執行

代理Delegate

​ 接口代理:對象 X 代替當前類 A 實現接口 B 的方法

​ 屬性代理:對象 x 代理屬性 a 實現 getter/setter 方法

//接口代理
interface Api {
    fun a()
    fun b()
    fun c()
}

class ApiImpl : Api {
    override fun a() {}

    override fun b() {}

    override fun c() {}
}

class ApiWrapper(val api: Api)
    //對象 api 代替類  ApiWrapper 實現接口 API
    : Api by api {

    override fun c() {
        println("-------- c")
        api.c()
    }

}

屬性代理:

​ 屬性代理是藉助於代理設計模式,把這個模式應用於一個屬性時,他可以將訪問器的邏輯代理給一個輔助對象

​ 可以簡單理解爲屬性的 setter,getter 訪問器內部實現是交給一個代理對象來實現,相當於一個代理對象替換了原來字段的讀寫過程,而暴露在外部屬性的操作還是不變的,照樣是屬性賦值和讀取,只是 setter,getter內部實現變了

1,Lazy

​ 當變量第一次使用是進行初始化,可以實現懶加載

//屬性代理
class Person(val name: String) {

    //lazy返回的對象代理了屬性 firstName 的 getter
    val firstName by lazy {
        println("初始化-----")
        "345"
    }
}

fun main() {
    val p = Person("")
    println(p.firstName)
    println(p.firstName)
}
初始化-----
345
345

只有在第一次調用的時候纔會執行。感覺比較適合安卓

初始化默認是線程安全的,通過 synchronized 鎖來保證

不過還是會影響性能,如果 lazy 的初始化不會涉及到多線程,那麼可以傳入 LazyThreadSafetyMode.NONE 來取消同步鎖

LazyThreadSafetyMode有三種模式:SYNCHRONIZED(默認模式)、PUBLICATION、NONE

其中PUBLICATION模式使用了AtomicReferenceFieldUpdater(原子操作)允許多個線程同時調用初始化流程。

2,自定義屬性代理

class Example {
    var p: String by Delegate()
}


class Delegate {
    operator fun getValue(thisRef: Any, property: KProperty<*>): String {
        return "代理 get"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("代理 set")
    }
}


fun main() {
    val e = Example()
    println(e.p)
    e.p = "345"
}
代理 get
代理 set

3,observable

變量被賦值時會發出通知

Delegates.observable 可以傳入兩個參數,一個是初始值,另一個是變量被賦值時的 handle 方法

class User {
    var name: String by Delegates.observable("345") { property, oldValue, newValue ->
        println("$oldValue -> $newValue")
    }
}

fun main() {
    val user = User()
    user.name = "first"
    user.name = "second"
}

輸出

345 -> first
first -> second

只要 user.name 被賦值,監聽就會觸發

類似的還有 vetoable ,只不過 vetoable 是在賦值前觸發,observable 是在賦值後觸發

4,vetoable

使用 vetoable 進行攔截

class User {
    var name: String by Delegates.vetoable("345") { property, oldValue, newValue ->
        if (newValue == "first") {
            return@vetoable true    //返回 true 表示first 可以賦值給 name
        }
        return@vetoable false //返回false 表示攔截其他賦值操作
    }
}

fun main() {
    val user = User()
    user.name = "first"
    println(user.name)
    user.name = "second"
    println(user.name)
}

first
first

5,對 map 的代理

fun main() {

    val map = mutableMapOf("name" to "張三", "age" to 1)

    var name: String by map
    println(name)

    name = "李四"
    println(map["name"])

}
張三
李四

6,局部變量代理

fun main() {

    a {
        345
    }
}


fun a(sum: () -> Int) {
    val s by lazy(sum)

    println(s) //345
}

案例

:使用屬性代理讀寫 Properties

class PropertiesDelegate(private val path: String, private val defaultValue: String = "") {


    private lateinit var url: URL

    private val properties: Properties by lazy {

        val prop = Properties()
        url = try {
//            查找具有給定名稱的資源
            javaClass.getResourceAsStream(path).use {
                //從輸入流中讀取屬性列表(鍵值對)
                prop.load(it)
            }
            javaClass.getResource(path)
        } catch (e: Exception) {
            try {
                ClassLoader.getSystemClassLoader().getResourceAsStream(path).use {
                    prop.load(it)
                }
                ClassLoader.getSystemClassLoader().getResource(path)!!
            } catch (e: Exception) {
                FileInputStream(path).use {
                    prop.load(it)
                }
                URL("file://${File(path).canonicalPath}")
            }
        }
        prop
    }


    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        //獲取屬性信息
        return properties.getProperty(property.name, defaultValue)
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        //設置屬性信息
        properties.setProperty(property.name, value)
        //寫入到文件,第二個參數爲註釋信息
        File(url.toURI()).outputStream().use {
            properties.store(it, "Hello!!l")
        }
    }

}

abstract class AbsProperties(path: String) {
    protected val prop = PropertiesDelegate(path)
}


class Config : AbsProperties("Config.properties") {
    var author by prop
    var version by prop
    var desc by prop
}

fun main() {
    val config = Config()
    print(config.author)    //getValue
    config.author = "345"   //setValue
    println(config.author)
}

單例

//單例的定義,默認是惡漢模式
object Signleton {
    var x = 2

    //模擬 java 中靜態方法,只有在 object 中可以使用
    @JvmStatic
    fun y() {
        println("---------")
    }
}
// Signleton.INSTANCE.getX(); 從 java 中調用該類
//        Signleton.y();  JvmStatic :靜態的

class Foo {
    //伴生對象,與普通類同名的 object
    //相當於java中的 public static void y(){}
    companion object {
        @JvmStatic
        fun y() {
            println("y")
        }

        //生成靜態 Field
        @JvmField
        var y: Int = 2
    }

    //屬性可以使用 JvmField,生成非靜態 Field,
    @JvmField
    var x: Int = 2
}

fun main() {

    Signleton.x
    Signleton.y()


    println(Foo.y)
    println(Foo.y())
}

內部類

fun main() {


    //創建非靜態內部類對象
    val inner = Outer().Inner()
    //創建靜態內部類對象
    val staticInner = Outer.StaticInner()


    //匿名內部類,可以實現多個接口,並且繼承一個類
    object : Runnable, Cloneable {
        override fun run() {

        }
    }


}


class Outer {

    //非靜態內部類
    inner class Inner {

    }

    //靜態內部類
    class StaticInner {

    }


    //一旦定義出來,就會被初始化
    object HH {

    }
}

數據類

data class Book(val id: Long, val name: String, val author: String) {
    
}
JavaBean data class
構造方法 默認無參構造 屬性作爲參數
字段 字段私有,get/set 公開 屬性
繼承性 可繼承,可被繼承 不可被繼承
component 相等性,解構等

data class 爲我們做了什麼

​ 編譯器會根據我們在構造函數中聲明的屬性自動導出下列成員:

  • equals / hashCode

  • toString

  • componentN()

  • copy()

    如果在類中明確定義或繼承了上面的基礎方法,則不會自動生成

限制:

  • 構造函數至少要一個參數
  • 所有構造函數的參數都必須使用 val 或者 var 標記
  • data class 不可以是 abstract,open sealed or inner
  • 不可以實現接口

數據類自帶的結構方法:componentN()

fun main() {

    val book = Book(1L, "百科", "張三")
    val (id, name, author) = book
    println("$id  $name $author")

}

實際上,結構是調用了 Book 類的 component1() 和 component2() ,component3()。

如何作爲 Java Bean 使用呢

需要解決兩個限制:

​ 至少一個構造器,和 final。data class 是不可被繼承的。

​ 如果解決呢:NoArg 插件,AllPoen 插件

在 build.gradle 中添加插件

plugins {
	......
    id("org.jetbrains.kotlin.plugin.noarg").version("1.3.50")
    id("org.jetbrains.kotlin.plugin.allopen").version("1.3.50")
}


noArg {
    //執行 init 塊中的代碼
    invokeInitializers = true
    annotation("com.kotlin.study.five.PoKo")
}

allOpen {
    annotation("com.kotlin.study.five.PoKo")
}

注意build.gradle 使用的是 kt 的代碼

還需要定義一個註解:就是 annotation指定路徑下的 PoKo 註解

public @interface PoKo {
}

空的即可

使用

//使用該註解後會在字節碼中生成一個無參構造,
//可被繼承
@PoKo
data class Book(val id: Long, val name: String, val author: String) {
    init {
        println("------")
    }
}
fun main() {    val b = Book::class.java.newInstance()}

枚舉

fun main() {

    S1.Busy.run()
    S1.Idle.run()

    val state = State.Busy

    //條件分支
    val value = when (state) {
        State.Idle -> {
            0
        }
        State.Busy -> {
            1
        }
    }

    //枚舉的比較,因爲是有順序的,所以可以。需要重載運算符

    //枚舉的區間,因爲有順序
}

//枚舉,父類是 Enum,所以不能繼承其他類
enum class State {
    Idle,
    Busy
}

//帶參數的構造
enum class S(val id: Int) {
    Idle(0),
    Busy(1)
}

//實現接口的枚舉
enum class S1 : Runnable {
    Idle {
        //實現字節的 run 方法
        override fun run() {
            println("For Idle")
        }
    },
    Busy;

    override fun run() {
        println("For every state.")
    }

}

//爲枚舉定義擴展
fun State.next(): State {
    return State.values().let {
        val next = (ordinal + 1) % it.size
        it[next]
    }
}
  • 枚舉
    • 枚舉的基本知識:定義方法,屬性,構造器,接口實現
    • 爲枚舉定義擴展
    • 分支表達式
    • 比較與區間

密封類

  • 密封類是一種特殊的抽象類,不能被實例化,但是可以有抽象成員
  • 密封類的子類定義在與自身相同的文件中
  • 密封類的子類個數是有限的

fun main() {

    //只能通過子類實例化對象
    val play: PlayerState = Start("小鳥")
    
}


//首先是一個抽象類,其次是一個密封類,構造器是私有的
sealed class PlayerState {

}

class Start(val song: String) : PlayerState() {

}

class Stop : PlayerState() {

}

  • 密封類
    • 概念:抽象,子類可數,子類封閉
    • 構造器私有:無法在外部文件中繼續密封類
    • 子類定義:必須定義在當前文件中
    • 與枚舉對比:enum:實例可數,sealed:子類可數

內聯類

  • 內聯類是對某一個類型的包裝
  • 內聯類是類似於 Java 裝箱類型的一種類型
  • 編譯器會盡可能使用被包裝的類型進行優化
fun main() {

    var x = BoxInt(3)

    val newValue = x.value * 200
    println(newValue) //600
    x++
    println(x)  //BoxInt(value=4)

}

//內聯類
//對 Int 做了包裝,Int 必須定義在主構造器,且必須是 val 類型 和 public
//可實現接口。且不能繼續和被繼承
inline class BoxInt(val value: Int) {

    //內聯類屬性:不應改包含狀態。也就是隻能定義方法或者函數

    //重寫自增運算符
    operator fun inc(): BoxInt {
        return BoxInt(value + 1)
    }

}

內聯類目前還在公測。

限制

  • 主構造器必須有且只有一個只讀屬性。因爲要對其進行包裝

  • 不能定義有 backing-field 的其他屬性。不能有狀態

  • 被包裝的類型不是是泛型

  • 不能繼續且被繼承

  • 內聯類不能被定義爲其他類的內部類

  • 定義的方法在編譯的時候會被替換爲靜態方法

數據類的 Json 化、

依賴

kotlin("kapt") version ("1.3.50")
implementation("com.squareup.moshi:moshi:1.8.0")
implementation("com.squareup.moshi:moshi-kotlin:1.9.2")
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.8.0")
fun main() {

    //Gson
    val gson = Gson()
    println(gson.toJson(Person("Gson", 20)))
    println(gson.fromJson("""{"name":"Gson"}""", Person::class.java))


    println("----------------------")
    //MoShi
    val moshi = Moshi.Builder().build()
    val jsonAdapter = moshi.adapter(Person::class.java)
    println(jsonAdapter.toJson(Person("MoShi", 20)))
    println(jsonAdapter.fromJson("""{"name":"Gson"}"""))
    
}

@JsonClass(generateAdapter = true)
data class Person(val name: String, val age: Int = 10)

{"name":"Gson","age":20}
Person(name=Gson, age=0)
----------------------
{"name":"MoShi","age":20}
Person(name=Gson, age=10)

Bean 類中 age 的默認值是 10

在 gson 解析的時候沒有指定 age 則爲 0

而 moshi 解析的時候會按照構造方法中的默認值來進行解析。注意 moshi 用到了註解處理器 @JsonClass

遞歸整形案例

fun main() {

    val l1 = inListOf(0, 1, 3, 4)
    println(l1)
    println(l1.joinToString('-'))
    println(l1.sun())

    val (x, y, z) = l1
    println(x)
    println(y)
    println(z)
}


fun inListOf(vararg ints: Int): IntList {
    return when (ints.size) {
        0 -> IntList.Nil
        else -> {
            //遞歸調用,
            //slice:切割,從第一個到最後一個(注意不是第 0 個)
            //遞歸時,ints[0]永遠是上一次的下一個
            IntList.Cons(ints[0], inListOf(*(ints.slice(1 until ints.size).toIntArray())))
        }
    }
}

sealed class IntList {

    object Nil : IntList() {
        override fun toString(): String {
            return "Nil"
        }
    }


    class Cons(val head: Int, val tail: IntList) : IntList() {
        override fun toString(): String {
            return "$head,$tail"
        }
    }

    fun joinToString(sep: Char = ','): String {
        return when (this) {
            Nil -> "Nil"
            is Cons -> {
                "${head}$sep${tail.joinToString(sep)}"
            }
        }
    }


    operator fun component1(): Int? {
        return when (this) {
            Nil -> null
            is Cons -> head
        }
    }

    operator fun component2(): Int? {
        return when (this) {
            Nil -> null
            //下一個 Cons
            is Cons -> tail
                .component1()
        }
    }

    operator fun component3(): Int? {
        return when (this) {
            Nil -> null
            is Cons -> tail
                .component2()
        }
    }
}


fun IntList.sun(): Int {
    return when (this) {
        IntList.Nil -> 0
        is IntList.Cons -> {
            head + tail.sun()
        }
    }
}
  • 類型進階
    • 類的構造器
      • 主構造器:唯一的路徑
      • 副構造器:提供其他的構造路徑
      • init 快:可寫多個,最後會合成一個執行
    • 可見性
      • public
      • internal:模塊內可見,類似一個 jar,aar
      • protected:沒有包內可見,只有子類看見
      • private
    • 類屬性的延時初始化
      • 可空類型
      • lateinit
      • lazy
    • 代理 Delegate:
      • 接口代理
      • 屬性代理:lazy,observable,vetoable 等
    • 使用屬性代理讀寫 Properties
    • 單例 object
      • 伴生對象
      • 定義 java field 的 @JvmField
      • 定義靜態成員的 @javaStatic
    • 內部類
      • 默認靜態
      • 非靜態用 inner 修飾
      • 匿名內部類可以實現多個接口
      • 本地類和本地函數
    • 數據類
      • component:主構造器中定義的屬性
      • 編譯器生成的方法:equals,hashCode,copy,componentN
      • 解構:定義了 componentN方法,即可通過解構獲取到對應的值
      • 插件:noArg(增加默認構造方法),allOpen(去掉 final)
    • 其他類
      • 枚舉類
      • 密封類:子類只能定義在父類相同的文件中
      • 內聯類:類似於裝箱拆箱
    • 數據類的 json 序列化
      • Gson,Moshi,Kotlinx.serialization
      • 特性:默認參數,init 塊
    • 遞歸的整形列表的簡單實現
      • 遞歸用的非常好
      • 可進行解構

參考自慕課網 Kotlin 從入門到精通

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