Kotlin基礎(chǔ)2-進(jìn)階入門(mén)

前言

上一節(jié)簡(jiǎn)單了解了 Kotlin 的語(yǔ)法使用,對(duì)一些語(yǔ)法有一些了解后,在面對(duì) Java 轉(zhuǎn) Kotlin 時(shí)或想了解 Kotlin 到底轉(zhuǎn)成了什么樣的 Java?那我們需要知道這兩個(gè)技能,不僅能偷一些懶也能了解一些簡(jiǎn)單原理;

  1. AndroidStudio 中Code → Convert Java File to Kotlin File雙擊 Shift 輸入 Convert Java File to Kotlin File

    一鍵 Java 轉(zhuǎn) Kotlin,無(wú)法從 Kotlin 再轉(zhuǎn)回 Java 哦。雖然能偷懶,但不能過(guò)分依賴(lài),前提你要了解 Kotlin,轉(zhuǎn)的時(shí)候有一些實(shí)現(xiàn)與性能會(huì)有出入。

  2. 雙擊 Shift 輸入 Show Kotlin Bytecode

    Kotlin 有挺多 Java 中沒(méi)有的特性,如果你想看下 Kotlin 這樣實(shí)現(xiàn)在 Java 中到底是怎樣體現(xiàn)的,那你可以去瞧一瞧。

構(gòu)造器

上一節(jié)簡(jiǎn)單上手接觸了 Kotlin 的一些語(yǔ)法,我們用上一節(jié)的知識(shí)知道如何實(shí)現(xiàn)一個(gè)類(lèi)的構(gòu)造器。

class KotlinView : View {
    private var paint = Paint() // 初始化畫(huà)筆
    // 次級(jí)構(gòu)造器 → 調(diào)用 this(主構(gòu)造器)
    constructor(context: Context) : this(context, null)
    // 主構(gòu)造器 → 調(diào)用 super
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
      // 畫(huà)筆樣式設(shè)置  
      paint.style = Paint.Style.STROKE
    }
}

Kotlin 在構(gòu)造器還有更簡(jiǎn)潔的寫(xiě)法,主要有主構(gòu)造器、**次級(jí)構(gòu)造 **以及 init代碼塊構(gòu)造屬性。

主構(gòu)造器

主構(gòu)造器是什么呢?

我認(rèn)為在一個(gè)類(lèi)里面最終消費(fèi)的構(gòu)造器就是主構(gòu)造器;比如上面的代碼里面 constructor(context: Context)調(diào)用的是this,最終流向的還是constructor(context: Context, attrs: AttributeSet?),所以它就是主構(gòu)造器。

那主構(gòu)造器如何更簡(jiǎn)潔呢?

可以將主構(gòu)造器的函數(shù)直接放置類(lèi)聲明的后面就行??聪潞?jiǎn)約后的代碼

class KotlinView constructor(context: Context, attrs: AttributeSet?) : View(context, attrs) {
    // 可以訪問(wèn)到主構(gòu)造函數(shù) context 參數(shù)
    val color = context.getColor(R.color.white)
    private var paint = Paint()
    constructor(context: Context) : this(context, null)
        // paint.style = Paint.Style.STROKE
    fun testParams(){
        context.resources// 不報(bào)紅,但實(shí)際 context 并不是主構(gòu)造函數(shù)里面的參數(shù)
      attrs// 會(huì)報(bào)紅,意味著訪問(wèn)不到
    }
}

倒是簡(jiǎn)潔了蠻多,可是我們主構(gòu)造器里面的邏輯咋辦?別慌,往下看!

另外:

  1. 如果主構(gòu)造器沒(méi)有被可見(jiàn)性修飾符注解標(biāo)記,那么constructor是可以省略的;
  2. 還有成員變量初始化時(shí)是可以直接訪問(wèn)主構(gòu)造參數(shù),例如color變量。注意點(diǎn)看代碼!

次級(jí)構(gòu)造

這個(gè)就不多講了,除了主構(gòu)造器,剩下的不就是次級(jí)構(gòu)造了嗎

init 代碼塊

在上面我們把主構(gòu)造器簡(jiǎn)化了,可是里面的邏輯沒(méi)地方放了,現(xiàn)在就改init上場(chǎng)了

class KotlinView (context: Context, attrs: AttributeSet?) : View(context, attrs) {
    private var paint = Paint()
    constructor(context: Context) : this(context, null)
    init {
        paint.style = Paint.Style.STROKE
    }
}

這樣就把主構(gòu)造器里面的邏輯安置好了,值得注意的一點(diǎn):在初始化的時(shí)候初始化塊會(huì)按照它們?cè)?strong>文件中出現(xiàn)的順序執(zhí)行。如果paint變量在init的下面聲明,那paintinit里面就會(huì)報(bào)紅,在編譯的時(shí)候,init代碼會(huì)按照文件出現(xiàn)的順序插入到實(shí)際的構(gòu)造函數(shù)中,聲明放在init下面的話,那就會(huì)被插入在paint.style的下面。

PS:注意到主構(gòu)造器的`constructor沒(méi)?沒(méi)見(jiàn)了是吧,因?yàn)樗葲](méi)有被可見(jiàn)性修飾也沒(méi)有被注解,所以是可以省略的。

構(gòu)造屬性

構(gòu)造屬性是什么呢,我給他翻譯過(guò)來(lái)就是:構(gòu)造函數(shù)的參數(shù)屬性化。

常規(guī)保守寫(xiě)法是這樣的

class KotlinInfo {
    var name: String? = null
    var age: Int? = null

    constructor(name: String?, age: Int?) {
        this.name = name
        this.age = age
    }
}

可是代碼太多,不是 Kotlin 的風(fēng)格,那我們這樣來(lái)

// 簡(jiǎn)化方式一:直接使用主構(gòu)造函數(shù)參數(shù)初始化。
class KotlinInfo constructor(name: String?, age: Int?) {
    var name: String? = name
    var age: Int? = age
}
// 簡(jiǎn)化方式二:emmm~還是不夠簡(jiǎn)潔,再來(lái)
class KotlinInfo constructor(var name: String?, var age: Int?) {
}

是吧,這樣才對(duì),主構(gòu)造函數(shù)的入?yún)⒓词菂?shù)又變量,所以這就是構(gòu)造屬性。

data class

在 Java 中重寫(xiě)hashCode()、toString()等類(lèi)時(shí),需要我們一個(gè)個(gè)去重寫(xiě)出來(lái),在 Kotlin 中我們只需要在class前聲明為data「數(shù)據(jù)類(lèi)」就會(huì)自動(dòng)為我們生成hashCode()、toString()、equals()、copy()componentN()方法。

// 聲明類(lèi)為 data 類(lèi)型
data class KotlinInfo constructor(var name: String?, var age: Int?) {}

fun main() {
    val kotlinInfo = KotlinInfo("Kotlin", 25)
    kotlinInfo.hashCode()
    kotlinInfo.toString()
    kotlinInfo.equals(null)
    kotlinInfo.copy()// 淺拷貝
    kotlinInfo.component1()// 對(duì)應(yīng)第一個(gè)變量 name
    kotlinInfo.component2()// 對(duì)應(yīng)第二個(gè)變量 age
}

但是生成數(shù)據(jù)類(lèi)還是有前提的

  • 主構(gòu)造函數(shù)需要至少有一個(gè)參數(shù)
  • 主構(gòu)造函數(shù)的所有參數(shù)需要標(biāo)記為 valvar;相當(dāng)于必須是構(gòu)造屬性
  • 數(shù)據(jù)類(lèi)不能是抽象、開(kāi)放、密封或者內(nèi)部的

component屬性的順序?qū)?yīng)主構(gòu)造參數(shù)的聲明順序

相等性

  • == 結(jié)構(gòu)相等,調(diào)用了equals()
  • === 引用相等

注意:與 Java 不同

解構(gòu)

Kotlin 的一大特點(diǎn)解構(gòu)聲明,表示一個(gè)對(duì)象一次賦值給多個(gè)變量。

data class KotlinInfo constructor(var name: String?, var age: Int?) {}

fun main() {
    val (name, age) = KotlinInfo("Kotlin", 25)
}

這個(gè)就有一個(gè)前提,必須是數(shù)據(jù)類(lèi)才能做解構(gòu)聲明,當(dāng)然不是數(shù)據(jù)類(lèi)我們也可以自定義解構(gòu)聲明,比如:

class KotlinInfo constructor(var name: String?, var age: Int?) {
    operator fun component1(): String? {
        return name
    }

    operator fun component2(): Int? {
        return age
    }
}

fun main() {
    val (name, age) = KotlinInfo("Kotlin", 25)
}

Elvis 操作符

通過(guò)?:操作簡(jiǎn)化if null的操作

// kotlinInfo.name 為空返回"沒(méi)有姓名"
val name = kotlinInfo.name ?: "沒(méi)有姓名"
// kotlinInfo.age 為空提前返回
val age = kotlinInfo.age ?: return
// kotlinInfo.age 為空拋出異常
val age = kotlinInfo.age ?: thorw IllegalArgumentException("age is null") 
// 對(duì)應(yīng) if 代碼是這樣的
var name = kotlinInfo.name
if (kotlinInfo.name == null){
    name = "沒(méi)有姓名"
}

when 操作符

when不僅條件分支上支持條件表達(dá)式,分支上也可以多個(gè)值,通過(guò),分割;還可以有返回值;默認(rèn)分支條件都是布爾表達(dá)式。

// num 大于 0 哦~
val str = when (num) {
    in 0..99 -> "< 100"http:// 大于等于 0 小于100;返回字符串
    100 -> "100"http:// 等于 100;返回字符串
    200 -> "200"http:// 等于 200;返回字符串
    300, 301 -> "300,301"http:// 等于 300 或 301;返回字符串
    else -> ">300"http:// 其他情況全部返回字符串
}

operator

一般用的時(shí)候找下就行,與 Java 的大致差不多,列舉下加減乘除

操作符 函數(shù)
a+b a.plus(b)
a-b a.minus(b)
a*b a.times(b)
a/b a.div(b)

Lambda

Lambda 在 Java 中也接觸過(guò),這里主要說(shuō)下哪些可以省略

val kotlinInfos: List<KotlinInfo> = ArrayList()
// 1、常規(guī)寫(xiě)法
for (kotlinInfo in kotlinInfos) {
    if (kotlinInfo.name.equals("Kotlin")) {
        return
    }
}
// 2、使用 Lambda 的常規(guī)寫(xiě)法
kotlinInfos.forEach({ kotlinInfo: KotlinInfo ->
    if (kotlinInfo.name.equals("Kotlin")) {
        return
    }
})
// 3、函數(shù)最后一個(gè)參數(shù)是 Lambda ,那么可以將 Lambda 表達(dá)式移至外面,如下:
kotlinInfos.forEach() { kotlinInfo: KotlinInfo ->
    if (kotlinInfo.name.equals("Kotlin")) {
        return
    }
}
// 4.1、如果傳入?yún)?shù)只有一個(gè) Lambda,小括號(hào)可以省略;forEach()
// 4.2、Lamdda 表達(dá)式只有一個(gè)參數(shù),類(lèi)型也可以省略;kotlinInfo: KotlinInfo
kotlinInfos.forEach{ kotlinInfo ->
    if (kotlinInfo.name.equals("Kotlin")) {
        return
    }
}
// 5、Lambda 表達(dá)式只有一個(gè)參數(shù)還可以通過(guò)隱式的 it 來(lái)訪問(wèn)參數(shù),如下:
kotlinInfos.forEach {
    if (it.name.equals("Kotlin")) {
        return
    }
}

循環(huán)

repeat(100){
    // do something
}
for (i in 0..99){
    // do something
}
for (i in 0 until 100) {
    // do something
}

infix

前提:

  • 必須是成員函數(shù)擴(kuò)展函數(shù)
  • 必須只能接收一個(gè)參數(shù)且不能有默認(rèn)值;

下面是until的源碼,函數(shù)是Int的擴(kuò)展函數(shù)且只有一個(gè)參數(shù)無(wú)默認(rèn)值

public infix fun Int.until(to: Int): IntRange {
    if (to <= Int.MIN_VALUE) return IntRange.EMPTY
    return this .. (to - 1).toInt()
}

嵌套函數(shù)

Kotlin 的函數(shù)中是可以在嵌套函數(shù)的,而且在內(nèi)部函數(shù)里面可以訪問(wèn)外部函數(shù)的參數(shù);但是有一點(diǎn)需要注意,每次調(diào)用外部函數(shù)時(shí)不僅會(huì)產(chǎn)生一個(gè)外部函數(shù)的對(duì)象而且內(nèi)部函數(shù)也會(huì)產(chǎn)生一個(gè)函數(shù)對(duì)象,所以用的時(shí)候需要謹(jǐn)慎。

fun external(string: String) {
    fun internal() {
        print(string)
    }
    print(string)
}

函數(shù)簡(jiǎn)化

var string: String? = null
fun printText() {
    print(string)
}
fun getText(): String? {
    return string
}
var string: String? = null
fun printText() = print(string)
fun getText(): String? = string

函數(shù)參數(shù)默認(rèn)值

fun toast(context: Context, text: CharSequence) {
    toast(context, text, Toast.LENGTH_SHORT)
}
fun toast(context: Context, text: CharSequence, duration: Int) {
    Toast.makeText(context, text, duration).show()
}

fun toast(context: Context, text: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
        Toast.makeText(context, text, duration).show()
}

在 Java 中調(diào)用就只能使用兩個(gè)參數(shù)的方法,這時(shí)候就需要用到@JvmOverloads

@JvmOverloads

toast函數(shù)上標(biāo)記@JvmOverloads注解后,Java 就能用了

擴(kuò)展

// 正常寫(xiě)法
fun dp2px(dx: Float): Float { ...}
// 擴(kuò)展寫(xiě)法
fun Float.dp2px(): Float { ...}
// 兩者使用區(qū)別
dp2px(10f)
10f.dp2px()

函數(shù)類(lèi)型

函數(shù)類(lèi)型傳入?yún)?shù)類(lèi)型返回值類(lèi)型組成,傳入?yún)?shù)需要用(),用->連接返回值,返回值為Unit不可省略;

函數(shù)類(lèi)型實(shí)際是一個(gè)接口,傳遞函數(shù)的時(shí)候可通過(guò)::函數(shù)名匿名函數(shù)或者使用lambda

class View {
    interface OnClickListener {
        fun OnClick(view: View)
    }

    fun setOnClickListener(listener: (View) -> Unit) { }
}

fun onClick(view: View) {
    print("click")
}

fun main() {
    val view = View()
    // 方式一:函數(shù)傳入
    view.setOnClickListener(::onClick)
    // 方式二:匿名函數(shù)
    view.setOnClickListener(fun(view: View) { print("click") })
    // 方式三:Lambda
    view.setOnClickListener { print("click") }
}

內(nèi)聯(lián)函數(shù)

內(nèi)聯(lián)函數(shù)

inline關(guān)鍵字聲明內(nèi)聯(lián)函數(shù),編譯時(shí)內(nèi)聯(lián)函數(shù)的函數(shù)體會(huì)被插入至調(diào)用處,因此實(shí)現(xiàn)內(nèi)聯(lián)函數(shù)時(shí)注意,函數(shù)體內(nèi)的代碼行數(shù)盡量減少。

內(nèi)聯(lián)函數(shù)有什么作用呢?看看代碼

inline fun log(text: String) {
  Log.d("TAG", text)
}
fun main() {
  log("log text")
}
// 用 inline 修飾聲明內(nèi)聯(lián)函數(shù) log
// 實(shí)際編譯后的代碼就變成這樣了
fun main() {
  log.d("TAG", "log text")
}
// 將內(nèi)聯(lián)函數(shù) log 里面的代碼直接放到了調(diào)用的地方
// 到底有什么用呢?
// 從調(diào)用棧的角度來(lái)看看
// 不使用內(nèi)聯(lián)函數(shù)大致是這樣的
// main 入棧 → log 入棧 → log.d() → log 出棧 → main 出棧
// 使用內(nèi)聯(lián)函數(shù)是這樣的
// main 入棧 → log.d() → main 出棧

所以?xún)?nèi)聯(lián)函數(shù)能減少一層調(diào)用棧,但是如果函數(shù)體里面內(nèi)容較多時(shí),編譯器會(huì)變得比較辛苦,因此我們?cè)趯?shí)現(xiàn)內(nèi)聯(lián)函數(shù)時(shí)一定要注意。

思考:示例代碼里面的內(nèi)聯(lián)函數(shù)loginline有警告!

部分禁用內(nèi)聯(lián)

noinline可以禁止部分參數(shù)參與編譯

具體化的類(lèi)型參數(shù)

通過(guò)配合inline + reified達(dá)到真泛型的效果

interface Api{ ... }
val retrofit = Retrofit.Builder()
        .baseUrl("https://api.com")
        .build()

inline fun <reified T> create(): T {
    return retrofit.create(T::class.java)
}

val api = create<Api>()

委托

屬性委托

常見(jiàn)的屬性操作,通過(guò)委托的方式,讓其只實(shí)現(xiàn)一次

  • lazy:延遲屬性,值僅在首次訪問(wèn)時(shí)計(jì)算
  • observable:可觀察屬性:屬性發(fā)生改變時(shí)通知

類(lèi)委托

通過(guò)類(lèi)委托的模式減少繼承

標(biāo)準(zhǔn)函數(shù)

  • apply
    • 返回自身:作用域中使用this做為參數(shù)
    • 適合對(duì)一個(gè)對(duì)象做附加操作
  • also
    • 返回自身:作用域中使用it做為參數(shù)
  • run
    • 無(wú)需返回自身:作用域中使用this做為參數(shù)
  • let
    • 無(wú)需返回自身:作用域中使用it做為參數(shù)
    • 適合配合空判斷時(shí)
  • with
    • 使用對(duì)同一個(gè)對(duì)象進(jìn)行多次操作時(shí)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容