Kotlin 學習記錄之 Kotlin 新特性

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

我們可以在哪里學習 Kotlin 呢?

當我們點進 Kotlin 的官網(wǎng)時,可以看到官網(wǎng)是如何介紹它的呢?


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

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

1.如何定義變量

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

//  常量
final PI = 3.14;

//  In Kotlin 
//  變量
var index: Int = 0
//   或者使用類型推導,編譯器會知道 index 是 Int 類型
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
}

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

3.如何定義類

In Java  
interface AC {}

class Super {
    public Super() {}
}

class Sub extends Super implements AC{

    public Sub () {
        super();
    }
}

//  In Kotlin

//  默認會自動生成一個無參構(gòu)造
open class B

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

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

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

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

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

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

//  In Kotlin
var sub = Sub(3)

接下來開始介紹今天的重點,Kotlin 的一些新特性~

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

//  在擴展函數(shù)中可以直接訪問被擴展的類的其他方法和屬性
//  但擴展函數(shù)并不允許打破它的封裝性,所以擴展函數(shù)不能訪問私有或是受保護的成員
//  對應 Java 代碼中 擴展函數(shù)是 static final 的,所以擴展函數(shù)是不能被子類重寫的
//  擴展函數(shù)并不是類的一部分,因為擴展函數(shù)是聲明在類外部的
//  如果擴展函數(shù)與成員函數(shù)簽名相同,往往成員函數(shù)會被優(yōu)先使用

//  書中解釋說擴展函數(shù)無非就是靜態(tài)函數(shù)的一個高效的語法糖

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

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

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

//  給 String 擴展成員常量
//  并不能進行初始化,因為沒有合適的地方來存放值
val String.test: String
    get() = "abc"

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

2.默認參數(shù)

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

//  我們現(xiàn)在為 Collection 寫一個集合拼接成字符串的一個擴展方法
//  拼接時我們指定前綴、后綴和分隔符
//  這里使用了默認參數(shù),為參數(shù)指定默認的值,這樣在調(diào)用方法的時候就
//  可以不指定參數(shù)或者只為某個參數(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)
//  全部使用默認值
println(set.collectionJoinToString())
//  為第一個參數(shù)賦值
println(set.collectionJoinToString("-"))
//  配合命名參數(shù),第一個參數(shù)值使用默認值,只為指定的參數(shù)賦值
println(set.collectionJoinToString(prefix = "[", postfix = "]"))

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

3.object 關鍵字

//  object 關鍵字的核心用途就是定義一個類時同時創(chuàng)建一個實例。

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

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

    private val allEmployees = arrayListOf<Person>()

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

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

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

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

    companion object {
        val PI = 3.14

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

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

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

var count = 0

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

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

4.Lambda 表達式

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

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

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

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

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

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

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

//  在作用域中訪問變量
//  捕捉的原理:當你捕捉 final 變量時,它的值和使用這個值的 Lambda 代碼一起存儲。
//  對于非 final 變量來說,它的值被封裝在一個特殊的包裝器中,這樣你就可以改變這個值,
//  這個包裝器的引用會和 Lambda
//  代碼一起被存儲。

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

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

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

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

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

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

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

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

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

}

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


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

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

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

5.可空性

//  Kotlin 對可空類型的支持,可以幫助我們在編譯期,檢測出潛在的 NullPointerException 錯誤。
//  Kotlin 提供了像安全調(diào)用(?.)、Elvis 運算符(?:)、
//  非空斷言(!!)及 let 函數(shù)這樣的工具來簡潔的處理可空類型。

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

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

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

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

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

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

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

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

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

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

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

6.集合

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

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

//  只讀集合并不一定是不可變的,當只讀集合和可變集合指向同一個集合對象的時候,可變可以進行操作,
//  然后只讀讀取操作后的集合。
//  只讀集合并不總是線程安全的。
//  即使在 Kotlin 中將集合聲明成只讀,Java 代碼也能夠修改這個集合,
//  因為 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> 將會是一個包含裝箱整型的數(shù)組
//  IntArray 是基礎類型 int 的數(shù)組,也就是 Java 中的 int[],這樣效率會更高一些

//  通過 Java 方法操作了只讀集合

7.運算符

//  Kotlin 允許使用對應名稱的函數(shù)來重載一些標準的數(shù)學運算,但是不能定義自己的運算符

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

//  特殊運算符
//  shl -> 帶符號左移
//  shr -> 帶符號右移
//  ushr -> 無符號右移
//  and -> 按位與
//  or -> 按位或
//  xor -> 按位異或
//  inv -> 按位取反

//  一元運算符
//  +a -> unaryPlus
//  -a -> unaryMinus
//  !a -> not
//  ++a, a++ -> inc
//  --a, a-- -> dec

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

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

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

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

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

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

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

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

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

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

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

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

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

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

8.其他

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

class Button : View {

    override fun getCurrentState(): State = ButtonState()

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

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

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

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

//  2.修飾符
//  Kotlin 中的默認可見性是 public 的
//  Java 中的默認可見性是包私有

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

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

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

//  一般寫法
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")
    }

    //  然后存儲邏輯省略
}

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

    //  然后存儲邏輯省略
}

//  通過提取局部函數(shù)來避免重復
fun User.validateBeforeSave() {
    //  聲明一個局部函數(shù)來進行字段校驗
    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.類委托(幫助避免在代碼中出現(xiàn)許多相似的委托方法)
//  實現(xiàn)接口后可以通過 by 關鍵字將接口的實現(xiàn)委托到另一個對象
//  這樣看上去我們需要重寫的方法就都消失了,其實是會去執(zhí)行被委托的對象
class DelegateCollection<T>(private val innerList:ArrayList<T>) : Collection<T> by innerList {

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

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

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

就到這里了...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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