Kotlin 學(xué)習(xí)記錄之 Kotlin 新特性

今天給大家分享的是 Kotlin 這門(mén)語(yǔ)言有別的于 Java 的一些新特性,在 Kotlin 沒(méi)有發(fā)布的時(shí)候,我們使用 Java 進(jìn)行 Android 開(kāi)發(fā),現(xiàn)在呢?Google 已經(jīng)向宣布了,Kotlin 也可以進(jìn)行 Android 開(kāi)發(fā)(畢竟如果可以不用 Java 的話(huà),也就不用和甲骨文有版權(quán)糾紛了),而且學(xué)習(xí)成本也很低,因?yàn)楹?jiǎn)單的語(yǔ)法可以使得開(kāi)發(fā)人員快速上手,且和 Java 百分百兼容,你的項(xiàng)目可以不用從頭開(kāi)始再寫(xiě)一遍,而是可以?xún)煞N語(yǔ)言共存,從而慢慢的過(guò)渡到 Kotlin。

我們可以在哪里學(xué)習(xí) Kotlin 呢?

當(dāng)我們點(diǎn)進(jìn) Kotlin 的官網(wǎng)時(shí),可以看到官網(wǎng)是如何介紹它的呢?


image.png
  • 簡(jiǎn)潔,大大減少樣板代碼的數(shù)量,如數(shù)據(jù)類(lèi),Lambda,單例創(chuàng)建等等。
  • 安全,避免類(lèi)的錯(cuò)誤,如空指針異常,可空類(lèi)型,類(lèi)型檢測(cè)與自動(dòng)轉(zhuǎn)換等等。
  • 互操作,開(kāi)發(fā)時(shí)可利用JVM,Android和瀏覽器的現(xiàn)有庫(kù)。
  • 工具友好,任何 Java IDE 或者命令行都可以進(jìn)行開(kāi)發(fā)。

在介紹今天要講內(nèi)容之前,我想先來(lái)介紹 Kotlin 的一些基本使用,也就是一些基本語(yǔ)法,然后我再講新特性的時(shí)候也就更容易看得懂了。

1.如何定義變量

//  In Java
//  變量
int index = 0;

//  常量
final PI = 3.14;

//  In Kotlin 
//  變量
var index: Int = 0
//   或者使用類(lèi)型推導(dǎo),編譯器會(huì)知道 index 是 Int 類(lèi)型
var index = 0

//  常量
val PI = 3.14

2.如何定義方法

//  In Java 
public int sum(int a, int b) {
    return a + b;
}

//  In Kotlin
fun max(a: Int, b: Int): Int {
    return if (a > b) a else b
}

//  表達(dá)式函數(shù)體
//  這里使用了類(lèi)型推導(dǎo)
fun sum(a: Int, b: Int) = a + b

3.如何定義類(lèi)

In Java  
interface AC {}

class Super {
    public Super() {}
}

class Sub extends Super implements AC{

    public Sub () {
        super();
    }
}

//  In Kotlin

//  默認(rèn)會(huì)自動(dòng)生成一個(gè)無(wú)參構(gòu)造
open class B

//  這里必須顯示的調(diào)用父類(lèi)的構(gòu)造方法
//  如果父類(lèi)的的構(gòu)造帶參那么也必須調(diào)用帶參的構(gòu)造
class RB : B()

interface AC {
    //  帶默認(rèn)實(shí)現(xiàn)的方法
    //  Java 8 之后也支持接口方法的默認(rèn)實(shí)現(xiàn),不過(guò)需要聲明 default 關(guān)鍵字

    //  由于 Kotlin 是以 Java 6 為目標(biāo)設(shè)計(jì)的,所以默認(rèn)方法在轉(zhuǎn)成 Java 代碼后其實(shí)是
    //  接口中有一個(gè)默認(rèn)實(shí)現(xiàn)了接口的靜態(tài)類(lèi)
    fun showOff() = println("Clickable 默認(rèn)實(shí)現(xiàn)")
}

//  沒(méi)有參數(shù)的話(huà)可以省略 ()
//  也可以省略大括號(hào)
//  Kolin 的類(lèi)默認(rèn)是 public 和 final,想要被集成需要改為 open 的
//  (p: Int) 這里被括號(hào)圍起來(lái)的語(yǔ)句就叫做主構(gòu)造方法
open class Super(p: Int)

//  這里需要寫(xiě) Super(10) 是因?yàn)樾枰{(diào)用父類(lèi)的構(gòu)造函數(shù)
//  而接口 AC 并不存在構(gòu)造直接寫(xiě) AC 即可
//  無(wú)論是繼承還是現(xiàn)實(shí)都是通過(guò) : 實(shí)現(xiàn)
//  constructor 用來(lái)聲明一個(gè)構(gòu)造(主構(gòu)造),constructor 關(guān)鍵字可省略
class Sub constructor(s: Int): Super(s), AC {
    //  init 表示引入一個(gè)初始化語(yǔ)句塊,這種語(yǔ)句塊會(huì)在類(lèi)被創(chuàng)建時(shí)執(zhí)行,并且與主構(gòu)造方法一起使用
    init {
        this.s = 0
    }
}

//  實(shí)例化
//  In Java 
Sub sub = new Sub(2);

//  In Kotlin
var sub = Sub(3)

接下來(lái)開(kāi)始介紹今天的重點(diǎn),Kotlin 的一些新特性~

1.擴(kuò)展函數(shù)/成員(給別人的類(lèi)添加方法)

//  在擴(kuò)展函數(shù)中可以直接訪(fǎng)問(wèn)被擴(kuò)展的類(lèi)的其他方法和屬性
//  但擴(kuò)展函數(shù)并不允許打破它的封裝性,所以擴(kuò)展函數(shù)不能訪(fǎng)問(wèn)私有或是受保護(hù)的成員
//  對(duì)應(yīng) Java 代碼中 擴(kuò)展函數(shù)是 static final 的,所以擴(kuò)展函數(shù)是不能被子類(lèi)重寫(xiě)的
//  擴(kuò)展函數(shù)并不是類(lèi)的一部分,因?yàn)閿U(kuò)展函數(shù)是聲明在類(lèi)外部的
//  如果擴(kuò)展函數(shù)與成員函數(shù)簽名相同,往往成員函數(shù)會(huì)被優(yōu)先使用

//  書(shū)中解釋說(shuō)擴(kuò)展函數(shù)無(wú)非就是靜態(tài)函數(shù)的一個(gè)高效的語(yǔ)法糖

//  如:為 String 擴(kuò)展一個(gè) 重復(fù)多次的字符串方法
//  String.multiply 前面為為 String 擴(kuò)展,后面為方法名
//  int 為重復(fù)次數(shù) 返回一個(gè) String
fun String.multiply(int: Int): String {
    val stringBuild = StringBuffer()
    for (i in 0 until int) {
        //  這時(shí)候里面就有了一個(gè) this,而這個(gè) this 就指代調(diào)用的這方法的對(duì)象了
        stringBuild.append(this)
    }
    return stringBuild.toString()
}

//  Kotlin 中的調(diào)用
//  直接用字符串調(diào)用該方法即可
println("Jaaaelu ".multiply(8))

//  Java 中的調(diào)用
//  Java 中如何調(diào)用呢? 類(lèi)名.方法名
//  這里其實(shí)相當(dāng)于靜態(tài)方法
//  如 Expand.multiply("Jaaaelu ", 8)

//  給 String 擴(kuò)展成員常量
//  并不能進(jìn)行初始化,因?yàn)闆](méi)有合適的地方來(lái)存放值
val String.test: String
    get() = "abc"

var StringBuilder.lastChar
    //  var 必須要有 getter 和 setter
    get() = get(length - 1)
    set(value) = this.setCharAt(length - 1, value)

2.默認(rèn)參數(shù)

//  Kotlin 可以給函數(shù)參數(shù)定義默認(rèn)值,這樣大大降低了重載函數(shù)的必要性,
//  而且命名函數(shù)讓多參數(shù)函數(shù)的調(diào)用更加易讀。

//  我們現(xiàn)在為 Collection 寫(xiě)一個(gè)集合拼接成字符串的一個(gè)擴(kuò)展方法
//  拼接時(shí)我們指定前綴、后綴和分隔符
//  這里使用了默認(rèn)參數(shù),為參數(shù)指定默認(rèn)的值,這樣在調(diào)用方法的時(shí)候就
//  可以不指定參數(shù)或者只為某個(gè)參數(shù)指定值
fun <T> Collection<T>.collectionJoinToString(separator: CharSequence = ", ",
                                             prefix: CharSequence = "", 
                                             postfix: CharSequence = ""): String {
    val result = StringBuilder(prefix)

    for ((index, value) in this.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(value)
    }

    result.append(postfix)
    return result.toString()
}

//  調(diào)用
val set = setOf(1, 2, 5)
//  全部使用默認(rèn)值
println(set.collectionJoinToString())
//  為第一個(gè)參數(shù)賦值
println(set.collectionJoinToString("-"))
//  配合命名參數(shù),第一個(gè)參數(shù)值使用默認(rèn)值,只為指定的參數(shù)賦值
println(set.collectionJoinToString(prefix = "[", postfix = "]"))

//  如果在方法上面添加 @JvmOverloads 那么在 Java 中調(diào)用也會(huì)生成該函數(shù)的各個(gè)重載版本

3.object 關(guān)鍵字

//  object 關(guān)鍵字的核心用途就是定義一個(gè)類(lèi)時(shí)同時(shí)創(chuàng)建一個(gè)實(shí)例。

//  一些不同的使用場(chǎng)景:
//  1.對(duì)象聲明是定義單例的一種方式。
//  2.伴生對(duì)象可以持有工廠(chǎng)方法和其他與這個(gè)類(lèi)相關(guān),但在調(diào)用時(shí)并不依賴(lài)類(lèi)實(shí)例的方法。
//  它們的成員可以通過(guò)類(lèi)名來(lái)訪(fǎng)問(wèn)。
//  3.對(duì)象表達(dá)式用來(lái)替代 Java 的匿名內(nèi)部類(lèi)。

//  1.創(chuàng)建單例
//  引入 object 后,與類(lèi)一樣依然可以有屬性、方法、初始化語(yǔ)句等聲明,
//  但是唯一不允許的就是構(gòu)造方法(主構(gòu)造、從構(gòu)造)都沒(méi)有
object Patroll :Driver{
    override fun drive() {
        println("日常飆車(chē)")
    }

    private val allEmployees = arrayListOf<Person>()

    fun calculateSalary() {
        for (person in allEmployees) {
            //  ...
        }
        println("Jaaaelu")
    }
}

//  調(diào)用
val p1:Driver = Patroll
val p2 = Patroll

//  2.伴生對(duì)象
//  關(guān)鍵字 companion,使用后可以通過(guò)類(lèi)名訪(fǎng)問(wèn)這個(gè)對(duì)象的方法屬性,看上去非常像是靜態(tài)調(diào)用
//  可以訪(fǎng)問(wèn)私有
class ACompanion {

    private fun privateFun() {
        println("私有方法")
    }

    companion object {
        val PI = 3.14

        fun bar() {
            println("測(cè)試方法")
        }
    }
}

//  調(diào)用
println(ACompanion.PI)

//  3.匿名內(nèi)部類(lèi)的新寫(xiě)法
//  object 除了聲明單例外還能用來(lái)聲明匿名對(duì)象(也就是 Java 中的匿名內(nèi)部類(lèi))
//  于聲明對(duì)象不同,匿名對(duì)象不是單例,比如每次執(zhí)行 other()。都會(huì)創(chuàng)建新的對(duì)象實(shí)例

var count = 0

fun other() {
        //  除了去掉名字外,語(yǔ)法與對(duì)象聲明相同
        val a = object : Driver {

            override fun drive() {
                count++
                //  也可以訪(fǎng)問(wèn)外面的內(nèi)容
                println("匿名內(nèi)部類(lèi) $count")
            }
        }
        println(a)
        a.drive()
}

4.Lambda 表達(dá)式

//  Lambda 表達(dá)式本質(zhì)上就是可以傳遞給其他函數(shù)的一小段代碼(作為函數(shù)參數(shù)的代碼塊)
//  函數(shù)式表層提供了另一種解決方案:把函數(shù)作為值來(lái)看待

//  基礎(chǔ)語(yǔ)法
//  { x: Int, y: Int -> x + y }     其中 x: Int, y: Int 為參數(shù), x + y 為函數(shù)體。
//  Kotlin 中的 Lambda 始終用花括號(hào)包圍,只需箭頭就將參數(shù)列表與函數(shù)體分開(kāi)。

//  可以將 Lambda 賦值給變量或者常量,然后調(diào)用
val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2))

//  正常 sum
//  相當(dāng)于(Int, Int) -> Int
fun sumOld(arg1: Int, arg2: Int): Int {
    return arg1 + arg2
}

//  Lambda 表達(dá)式形式的 sum
val sumLambda = { arg1: Int, arg2: Int -> arg1 + arg2 }

//  Lambda 表達(dá)式形式的 sum 且其中還能有其他語(yǔ)句
//  相當(dāng)于(Int, Int) -> Int
val sumAndPrint = { arg1: Int, arg2: Int ->
    println("$arg1 + $arg2 = ${arg1 + arg2}")
    //  最后一行表示你的返回值
    arg1 + arg2
}

//  沒(méi)有參數(shù)和返回值的時(shí)候,可以直接將其寫(xiě)在大括號(hào)內(nèi)
val printHello = {
    //  相當(dāng)于函數(shù)體
    println("Hello")
}

//  在作用域中訪(fǎng)問(wèn)變量
//  捕捉的原理:當(dāng)你捕捉 final 變量時(shí),它的值和使用這個(gè)值的 Lambda 代碼一起存儲(chǔ)。
//  對(duì)于非 final 變量來(lái)說(shuō),它的值被封裝在一個(gè)特殊的包裝器中,這樣你就可以改變這個(gè)值,
//  這個(gè)包裝器的引用會(huì)和 Lambda
//  代碼一起被存儲(chǔ)。

fun printProblemCounts(responses: Collection<String>) {
    //  默認(rèn)情況下,局部變量的生命周期被限制在聲明這個(gè)變量的函數(shù)中
    //  但如果被 Lambda 捕捉了,那么使用這個(gè)變量的代碼可以被存儲(chǔ)并稍后再執(zhí)行
    var clientErrors = 0
    var serverErrors = 0
    responses.forEach {
        if (it.startsWith("4")) {
            //  Kotlin 中允許在 Lambda 中訪(fǎng)問(wèn)非 final 變量,甚至修改它們
            //  從 Lambda 內(nèi)訪(fǎng)問(wèn)外部變量,我們稱(chēng)這些變量被 Lambda 捕捉
            //  就像例子中的 clientErrors 以及 serverErrors
            clientErrors++
        } else if (it.startsWith("5")) {
            serverErrors++
        }
    }
    println("$clientErrors client errors, $serverErrors server errors")
}

//  Lambda 的實(shí)現(xiàn)細(xì)節(jié):從 Kotlin 1.0 開(kāi)始,每個(gè) Lambda 表達(dá)式都會(huì)被編譯成一個(gè)匿名類(lèi),
//  除非它是一個(gè)內(nèi)聯(lián) Lambda
//  不過(guò)這里所說(shuō)的匿名類(lèi)只對(duì)期望函數(shù)式接口的 Java 方法有效

//  內(nèi)聯(lián)函數(shù):消除 Lambda 帶來(lái)的運(yùn)行時(shí)開(kāi)銷(xiāo)
//  例如 Lambda 表達(dá)式會(huì)被正常的編譯為匿名類(lèi),這樣每次調(diào)用 Lambda 就會(huì)創(chuàng)建一個(gè)類(lèi),
//  這樣會(huì)帶來(lái)額外的開(kāi)銷(xiāo)。
//  Kotlin 提供了 inline 修飾符標(biāo)記一個(gè)函數(shù)
//  內(nèi)聯(lián)函數(shù)被編譯后,它的字節(jié)碼連同傳遞給它的 Lambda 的字節(jié)碼被插入到調(diào)用函數(shù)的代碼中,
//  這使得函數(shù)調(diào)用相比于直接編寫(xiě)相同的代碼,不會(huì)產(chǎn)生額外的運(yùn)行時(shí)開(kāi)銷(xiāo)。

inline fun <T> synchronized(lock: Lock, action: () -> T): T {
    lock.lock()
    try {
        return action()
    } finally {
        lock.unlock()
    }
}

val l = Lock()
synchronized(l) {
    println("J")
}

//  上面代碼會(huì)編譯為
l.lock()
try {
    println("J")
} finally {
    l.unlock()
}

//  內(nèi)聯(lián)函數(shù)的限制
//  由于不是所有的 Lambda 函數(shù)都可以被內(nèi)聯(lián),當(dāng)函數(shù)內(nèi)斂的時(shí)候,
//  作為參數(shù)的 Lambda 表達(dá)式的函數(shù)會(huì)直接替換掉最終生成代碼中。
//  這也限制了函數(shù)體中對(duì)應(yīng)的(Lambda)參數(shù)的使用,如果(Lambda)參數(shù)被調(diào)用,
//  這樣的代碼能被容易地內(nèi)聯(lián)。
//  但如果(Lambda)在某個(gè)地方被保存起來(lái)了,以便后面繼續(xù)使用,Lambda 表達(dá)式的代碼將不能被內(nèi)聯(lián)。

//  例如系統(tǒng)提供的這個(gè)函數(shù),它沒(méi)有直接調(diào)用作為 transform 參數(shù)傳遞進(jìn)來(lái)的函數(shù),
//  而是將這個(gè)函數(shù)傳遞給了一個(gè)類(lèi)的構(gòu)造,構(gòu)造方法將它保存在一個(gè)屬性中,
//  所以 transform 需要被編譯為標(biāo)準(zhǔn)的非內(nèi)聯(lián)的表示法,即一個(gè)實(shí)現(xiàn)了函數(shù)接口的匿名類(lèi)。
//  fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
//      return TransformingSequence(this, transform)
//  }

//  如果一個(gè)函數(shù)期望兩個(gè)或者更多 Lambda 參數(shù),可以選擇只內(nèi)聯(lián)一些參數(shù)。
inline fun foo(inlined: () -> Unit, noinline noinlined: () -> Unit) {

}

//  決定何時(shí)將函數(shù)聲明成內(nèi)聯(lián)
//  使用 inline 關(guān)鍵字只能提高帶有 Lambda 參數(shù)的函數(shù)的性能。
//  對(duì)于普通的函數(shù)調(diào)用,JVM 已經(jīng)提供了強(qiáng)大的內(nèi)聯(lián)支持。
//  在使用 inline 關(guān)鍵字時(shí),應(yīng)該注意代碼的長(zhǎng)度,把一些和 Lambda 無(wú)關(guān)的方法抽取到一個(gè)
//  獨(dú)立非內(nèi)聯(lián)函數(shù)中,從而減少字節(jié)碼拷貝長(zhǎng)度。


//  對(duì)于 Koltin 集合來(lái)說(shuō),提供了更多高階函數(shù)
//  高階函數(shù)就是以另一個(gè)函數(shù)作為參數(shù)或者返回值的函數(shù)
//  在 Kotlin 中函數(shù)可以用 Lambda 或者函數(shù)引用來(lái)表示
//  因此,任何以 Lambda 或者函數(shù)引用作為參數(shù)貨返回值的函數(shù)(或者兩者都是)都是高階函數(shù)

//  以集合為例:
//  檢查集合中所有元素是否都符合某個(gè)條件,可以通過(guò) all 或 any 來(lái)實(shí)現(xiàn)
//  all 判斷所有元素是否都滿(mǎn)足該條件
//  any 判斷元素列表中是否存在至少一個(gè)匹配元素
//  count 對(duì)滿(mǎn)足該表達(dá)式的元素計(jì)數(shù)
//  find 找到一個(gè)滿(mǎn)足 Lambda 的第一個(gè)元素
//  filter 會(huì)從集合中一處你不想要的元素
//  map 函數(shù)對(duì)集合的每一個(gè)元素應(yīng)用給定的函數(shù),并把結(jié)果收集到一個(gè)新集合
//  flatMap 可將多列表平鋪
//  groupBy 把列表轉(zhuǎn)為分組的 map
//  等等 
//  不過(guò)對(duì)于某些高階函數(shù)來(lái)說(shuō),如做 map 或 filter 操作時(shí),這些函數(shù)都會(huì)創(chuàng)建中間集合
//  如果元素?cái)?shù)量過(guò)多,這種鏈?zhǔn)秸{(diào)用就會(huì)變得十分低效
//  所以這時(shí)候就引入序列,將上述操作變成使用序列,而不是直接返回集合
//  序列:惰性集合操作,由于序列中的元素求值是惰性的,因此,
//  可以使用序列更高效的對(duì)集合元素執(zhí)行鏈?zhǔn)讲僮?,而不需要?jiǎng)?chuàng)建額外中間集合來(lái)保存中間過(guò)程中產(chǎn)生的結(jié)果
//  惰性操作是對(duì)元素逐個(gè)處理

//  先將集合轉(zhuǎn)為序列,然后進(jìn)一步進(jìn)行函數(shù)操作,最后再將序列轉(zhuǎn)為集合
//  這時(shí)就不會(huì)創(chuàng)建中間集合
//  下面的計(jì)算中.map(Person::name).filter { it.startsWith("A") } 就是中間操作
//  而 toList() 是末端操作
//  由于中間操作時(shí)惰性的,所以 map 和 filter 變換被延期了
//  末端操作才會(huì)觸發(fā)被延期的計(jì)算
//  序列的執(zhí)行順序是按照元素來(lái)的,所以打印出來(lái)的內(nèi)容是 map 第一個(gè)元素,filter 第一個(gè)元素,如此往下
//  而我們正常集合操作的話(huà)就是先全部 map,然后再全部 filter
println(people
            .asSequence()
            .map(Person::name)
            .filter { it.startsWith("A") }
            .toList())

5.可空性

//  Kotlin 對(duì)可空類(lèi)型的支持,可以幫助我們?cè)诰幾g期,檢測(cè)出潛在的 NullPointerException 錯(cuò)誤。
//  Kotlin 提供了像安全調(diào)用(?.)、Elvis 運(yùn)算符(?:)、
//  非空斷言(!!)及 let 函數(shù)這樣的工具來(lái)簡(jiǎn)潔的處理可空類(lèi)型。

//  String = String,不能存儲(chǔ) null 引用
//  所以默認(rèn)情況下類(lèi)型都是非空的
fun strLen(s: String) = s.length

//  String? = String or null
//  這里是顯式的標(biāo)記出使用可空的 String
//  s?.length 表示如果 s 不為空會(huì)返回 s.length 否則返回 null
//  相當(dāng)于 if (s != null) s.length else null
fun strLenCanNullNoException(s: String?) = s?.length

//  帶有默認(rèn)值 ?. 后面跟的就是默認(rèn)值
fun strLenCanNullNoExceptionAndWithDefault(s: String?) = s?.length ?: 0

//  雖然可以用作默認(rèn)值,當(dāng)然也可以用于其他功能,如拋出異常
fun strLenThrowException(s: String?) = s?.length ?: throw NullPointerException("字符串為空")

//  s!!.length 表示如果 s 為 null 會(huì)直接拋出空指針異常,不為 null 則可以返回 s.length
//  一般情況下,只有我們知道 s 一定不為 null 的時(shí)候使用
//  !! 也叫做非空斷言,盡量不在一行代碼中使用多個(gè)非空斷言,
//  因?yàn)槟愫茈y分清除是哪個(gè) !! 讓你程序拋出空指針異常
fun strLenCanNullWillException(s: String?) = s!!.length

val p: Person? = Person("Gzw", 23)
//  sendEmail 接收一個(gè)非空類(lèi)型的 Person,所以需要先進(jìn)行空判斷,判斷通過(guò)再繼續(xù)
if (p != null) sendEmail(p, "啦啦啦啦啦啦")
//  還可以使用另一種方式 let
//  如果 p 不為空,那么 it 就不為空,如果 p 為空什么都不會(huì)發(fā)生
//  這里必須是 ?. 的調(diào)用方式,否則會(huì)報(bào)錯(cuò)
p?.let { sendEmail(it, ",,,,,") }

//  Kotlin 中所以泛型類(lèi)和泛型函數(shù)的類(lèi)型參數(shù)默認(rèn)都是可空的
fun <T> printlnHashCode(t: T) {
    println(t?.hashCode())
}

//  T 被推導(dǎo)為 Any?
printlnHashCode(null)

//  為類(lèi)型參數(shù)添加非空上界后,現(xiàn)在的 T 就不是可空的了
fun <T: Any> printlnHashCodeNotNull(t: T) {
    println(t.hashCode())
}

//  Java 中的類(lèi)型在 Kotlin 中被解釋成平臺(tái)類(lèi)型,允許開(kāi)發(fā)者把他們當(dāng)做可空或非空來(lái)對(duì)待。
//  Java 中 @Nullable + Type = Type? / @NotNull + Type = Type
//  Java 中的 Type = Kotlin 中的 Type? or Type
//  如 Java 中的 ArrayList<String> 在 Kotlin 中被當(dāng)做了 ArrayList<String?>?

//  表示基本數(shù)字的類(lèi)型(如 Int)看起來(lái)用起來(lái)都像普通的類(lèi),但通常會(huì)被編譯成 Java 基本數(shù)據(jù)類(lèi)型。
//  可空基本類(lèi)型(如 Int?)對(duì)應(yīng)著 Java 中的裝箱基本數(shù)據(jù)類(lèi)型(如 Integer)。
//  Any 類(lèi)型是所有其他類(lèi)型的超類(lèi)型,類(lèi)型于 Java 中的 Object。而 Unit 類(lèi)比于 void。

6.集合

//  創(chuàng)建可空性的集合時(shí),需要注意的時(shí)候,要小心決定什么是可空的,
//  是元素可空還是集合本身可空,還是兩者都可為空?
//  List<Int?>、List<Int>?、List<Int?>?

//  Kotlin 中把訪(fǎng)問(wèn)集合數(shù)據(jù)的接口和修改集合數(shù)據(jù)的接口分開(kāi)了
// (只讀集合 Collection 與可變集合 MutableCollection)
//  kotlin.collections.Collection 只讀集合提供了 size、iterator、contains 等操作來(lái)查看讀取數(shù)據(jù)
//  kotlin.collections.MutableCollection 可變的集合提供了 add、remove、clear 等修改集合的操作,
//  不過(guò) MutableCollection 繼承自 Collection,所有也擁有那些讀取操作
//  如果函數(shù)接收 Collection 而不是 MutableCollection,
//  那么就很容易知道這個(gè)方法不會(huì)修改集合,只會(huì)讀取集合數(shù)據(jù)。

//  只讀集合并不一定是不可變的,當(dāng)只讀集合和可變集合指向同一個(gè)集合對(duì)象的時(shí)候,可變可以進(jìn)行操作,
//  然后只讀讀取操作后的集合。
//  只讀集合并不總是線(xiàn)程安全的。
//  即使在 Kotlin 中將集合聲明成只讀,Java 代碼也能夠修改這個(gè)集合,
//  因?yàn)?Java 中并不區(qū)分只讀集合和可變集合。

//  List 創(chuàng)建集合函數(shù),只讀 -> listOf,可變 -> mutableListOf、arrayListOf
//  Set 創(chuàng)建集合函數(shù),只讀 -> setOf,可變 -> mutableSetOf、hashSetOf、linkedSetOf、sortedSetOf
//  Map 創(chuàng)建集合函數(shù),只讀 -> mapOf,可變 -> mutableMapOf、hashMapOf、linkedMapOf、sortedMapOf

//  只讀集合并一定是不可變的
var mutable: MutableCollection<Int> = mutableListOf(1, 2, 3, 4, 5)
val c: Collection<Int> = mutable
var m: MutableCollection<Int> = mutable

//  Array<Int> 將會(huì)是一個(gè)包含裝箱整型的數(shù)組
//  IntArray 是基礎(chǔ)類(lèi)型 int 的數(shù)組,也就是 Java 中的 int[],這樣效率會(huì)更高一些

//  通過(guò) Java 方法操作了只讀集合

7.運(yùn)算符

//  Kotlin 允許使用對(duì)應(yīng)名稱(chēng)的函數(shù)來(lái)重載一些標(biāo)準(zhǔn)的數(shù)學(xué)運(yùn)算,但是不能定義自己的運(yùn)算符

//  可重載的二元算數(shù)運(yùn)算符
//  a * b -> times
//  a / b -> div
//  a % b -> mod
//  a + b -> plus
//  a - b -> minus

//  特殊運(yùn)算符
//  shl -> 帶符號(hào)左移
//  shr -> 帶符號(hào)右移
//  ushr -> 無(wú)符號(hào)右移
//  and -> 按位與
//  or -> 按位或
//  xor -> 按位異或
//  inv -> 按位取反

//  一元運(yùn)算符
//  +a -> unaryPlus
//  -a -> unaryMinus
//  !a -> not
//  ++a, a++ -> inc
//  --a, a-- -> dec

data class Point(val x: Int, val y: Int) {
    //  重載 plus 運(yùn)算符
    //  且并不要求兩個(gè)運(yùn)算數(shù)是相同類(lèi)型,返回值也可以不同
    //  不支持交換性
    operator fun plus(other: Point): Point {
        return Point(x + other.x, y + other.y)
    }

    operator fun unaryMinus(): Point {
        return Point(-x, -y)
    }
}

//  也相當(dāng)于 Point(10, 20).plus(Point(20, 10))
println(Point(10, 20) + Point(20, 10))

//  復(fù)合運(yùn)算符
//  += 可以被轉(zhuǎn)換為 plus 或者 plusAssign(但最好不要同時(shí)重載這兩個(gè)方法)
//  point = point + Point(10, 20)
point += Point(10, 20)
println(point)

//  把運(yùn)算符定義為擴(kuò)展方法
operator fun Point.minus(other: Point): Point {
    return Point(x - other.x, y - other.y)
}

//  等號(hào)運(yùn)算符:equals,在 Kotlin 中使用 == 會(huì)被轉(zhuǎn)成 equals
//  而 != 也會(huì)被轉(zhuǎn)成 equals,只是返回的結(jié)果是相反的
//  == 與 != 可用于空運(yùn)算數(shù),因?yàn)檫@些運(yùn)算符實(shí)際上會(huì)檢查運(yùn)算數(shù)是否為 null
//  如 a == b,會(huì)先檢查 a 是否為空,如果不是,就會(huì)調(diào)用 a.equals(b),
//  否則兩個(gè)都是 null 的時(shí)候才會(huì)返回 true  a == b -> a?.equals(b) ?: (b == bull)

//  Comparable 便于比較值
class P(val x: Int, val y: Int) : Comparable<P> {

    //  用于比較一個(gè)對(duì)象是否大于另一個(gè)對(duì)象
    //  比較運(yùn)算符 >, <, >=, <= 將被轉(zhuǎn)換為 compareTo
    //  即 a > b -> a.compareTo(b) >= 0
    override fun compareTo(other: P): Int {
        return compareValuesBy(this, other, P::x, P::y)
    }

    //  重寫(xiě) equals 方法
    //  這里我們雖然是重載了比較運(yùn)算符 equals,但是方法前綴不是 operator 而是 override,
    //  因?yàn)?equals 是 Any 中定義的方法,equals 不能實(shí)現(xiàn)擴(kuò)展函數(shù),
    //  因?yàn)榧勺?Any 類(lèi)的實(shí)現(xiàn)始終優(yōu)先于擴(kuò)展方法
    override fun equals(other: Any?): Boolean {
        //  先檢查是否為同一個(gè)對(duì)象
        //  這里 === 與 Java 中的 == 完全相同
        // (檢查兩個(gè)參數(shù)是否是同一個(gè)對(duì)象的引用,基本類(lèi)型的話(huà)會(huì)比較值)
        //  === 不能被重載
        if (other === this) return true
        //  檢查參數(shù)類(lèi)型
        if (other !is P) return false
        //  檢查值
        return other.x == x && other.y == y
    }
}

//  in 運(yùn)算符會(huì)被轉(zhuǎn)為 contains
//  a in b -> a.contains(b)

//  rangeTo 是 Comparable 的擴(kuò)展函數(shù)
//  rangeTo 運(yùn)算符的優(yōu)先級(jí)低于算數(shù)運(yùn)算符
//  start..end -> start.rangeTo(end)
//  日期區(qū)間
val now = LocalDate.now()
//  未來(lái)十天
val vacation = now..now.plusDays(10)
println(now.plusDays(1) in vacation)
(1..10).forEach(::println)

//  for 循環(huán)中也可以使用 in 運(yùn)算符,但這種情況下和上面的表示含義不同,它被用來(lái)執(zhí)行迭代
//  for (x in list) {...} 實(shí)際上被轉(zhuǎn)為 list.iterator(),然后就像 Java 中一樣,
//  反復(fù)調(diào)用 hasNext 和 next
for (x in 1..10) {
    print("$x ")
}

//  這種寫(xiě)法叫字符串模板
//  $ 后面跟著變量就可以直接打印出來(lái)變量的值
//  如果想要打印 $ 直接寫(xiě)出來(lái)就行了
println("Hello $name !")
//  In Java
System.out.println(“Hello ” + name + " !")  
//  但如果 $ 后面還有其他字符,那么 $ 就需要使用轉(zhuǎn)義字符 \$
println("\$name")
//  ${ 里面可以放各種表達(dá)式 }
println("Hello ${if (args.isNotEmpty()) args[0] else "Kotlin"} !")

//  until 用來(lái)構(gòu)建一個(gè)開(kāi)區(qū)間,然后用 in 判斷是否在區(qū)間內(nèi)
//  10..20 表示閉區(qū)間為 10 - 20, 10 until 20 位開(kāi)區(qū)間表示 10 - 19

8.其他

//  1.內(nèi)部類(lèi)和嵌套類(lèi)
//  內(nèi)部類(lèi)和嵌套類(lèi):默認(rèn)是嵌套類(lèi)
//  Kotlin 中的嵌套類(lèi)一般情況下不能訪(fǎng)問(wèn)外部類(lèi)的實(shí)例

class Button : View {

    override fun getCurrentState(): State = ButtonState()

    //  這個(gè)類(lèi)與 Java 中的靜態(tài)嵌套類(lèi)類(lèi)似,而不是內(nèi)部類(lèi)
    //  所以 ButtonState 不會(huì)持有外部引用
    class ButtonState : State {

        override fun toString(): String {
            return "ButtonState"
        }
    }

    //  inner class 與 Java 的內(nèi)部類(lèi)類(lèi)似
    //  所以 OtherButtonState 會(huì)持有外部類(lèi)的應(yīng)用
    inner class OtherButtonState : State {

        //  拿到外部類(lèi)的引用
        fun getOutReference(): Button = this@Button
    }
}

//  2.修飾符
//  Kotlin 中的默認(rèn)可見(jiàn)性是 public 的
//  Java 中的默認(rèn)可見(jiàn)性是包私有

//  Kotlin 中有一個(gè)不同的修飾符 internal,表示模塊內(nèi)可見(jiàn)

//  修飾符 public 對(duì)于類(lèi)成員與頂層聲明都表示所有地方可見(jiàn)
//  修飾符 internal 對(duì)于類(lèi)成員與頂層聲明都表示模塊中可見(jiàn)
//  修飾符 protected 對(duì)于類(lèi)成員表示子類(lèi)可見(jiàn)
//  (與 Java 中不同,Java 還允許同包內(nèi)的文件訪(fǎng)問(wèn) protected)
//  修飾符 private 對(duì)于類(lèi)成員表示類(lèi)中可見(jiàn),對(duì)于頂層聲明表示文件中可見(jiàn)

//  3.局部函數(shù)
class User(val id: Int, val name: String, val address: String)

//  一般寫(xiě)法
fun saveUser(user:User) {
    if (user.name.isEmpty()) {
        throw IllegalArgumentException("Can't save user ${user.id}: empty name")
    }
    if (user.address.isEmpty()) {
        throw IllegalArgumentException("Can't save user ${user.id}: empty address")
    }

    //  然后存儲(chǔ)邏輯省略
}

//  通過(guò)提取局部函數(shù)和擴(kuò)展來(lái)避免重復(fù)
fun saveUserWithOptimize(user:User) {
    user.validateBeforeSave()

    //  然后存儲(chǔ)邏輯省略
}

//  通過(guò)提取局部函數(shù)來(lái)避免重復(fù)
fun User.validateBeforeSave() {
    //  聲明一個(gè)局部函數(shù)來(lái)進(jìn)行字段校驗(yàn)
    fun validate(value:String, fieldName:String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Can't save user $id: empty $fieldName")
        }
    }

    validate(name, "name")
    validate(address, "address")
}

//  4.類(lèi)委托(幫助避免在代碼中出現(xiàn)許多相似的委托方法)
//  實(shí)現(xiàn)接口后可以通過(guò) by 關(guān)鍵字將接口的實(shí)現(xiàn)委托到另一個(gè)對(duì)象
//  這樣看上去我們需要重寫(xiě)的方法就都消失了,其實(shí)是會(huì)去執(zhí)行被委托的對(duì)象
class DelegateCollection<T>(private val innerList:ArrayList<T>) : Collection<T> by innerList {

    //  雖然委托給了別人執(zhí)行,但是仍然可以重寫(xiě),最終會(huì)執(zhí)行你重寫(xiě)的方法
    override fun contains(element: T): Boolean {
        return false
    }
}

// 5.中綴表達(dá)式
//  中綴表達(dá)式(調(diào)用只有一個(gè)參數(shù)的函數(shù)時(shí),使得代碼更簡(jiǎn)練)
//  簡(jiǎn)單的自定義函數(shù)
//  當(dāng)然中綴
infix fun Any.toAny(other:Any) = Pair(this, other)

//  調(diào)用
println(2 toAny 3)

就到這里了...

最后編輯于
?著作權(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)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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