Kotlin基礎入門

1. 簡介

1.1. 歷史發展

  • 2011年7月JetBrains推出Kotlin項目,這是一個面向JVM的新語言,它已被開發一年之久。
  • 2012年2月JetBrains以Apache 2許可證開源此項目,Jetbrains希望這個新語言能夠推動IntelliJ IDEA的銷售。
  • 2016年2月15日發布1.0版,被認為是第一個官方穩定版本,并且JetBrains已準備從該版本開始的長期向后兼容性。
  • 2017年3月,JetBrains發布Kotlin 1.1版本,Kotlin在全球范圍內成長顯著。
  • 2017年5月18日Google I/O大會上宣布Kotlin正式成為Android的官方一級開發語言,同時AS3.0默認集成Kotlin Plugin。
  • 2017年11月,JetBrains發布了Kotlin 1.2版本,推出了多平臺項目特性,可以將原始代碼編譯成多個平臺的目標代碼,目前支持JVM和JavaScript。
  • 2018年2月,Google發布了Android KTX擴展庫,目前已屬于Android Jetpack系列中的一員,簡單的說Android KTX旨在讓我們利用Kotlin語言功能(例如擴展函數/屬性、lambda、命名參數和參數默認值),以更簡潔、更愉悅、更慣用的方式使用Kotlin進行Android開發。甚至可以簡單理解為Google為Kotlin準備的適配Android的一系列xxxUtils工具。這個庫由Jake Wharton負責維護。
  • 2018年10月,JetBrains發布了Kotlin 1.3版本,這個版本最重要的特性就是協程,它使得非阻塞代碼易于讀寫。此外在多平臺方面,支持了支持 JVM、Android、JavaScript和Native。這意味著Kotlin能進行前端、移動端以及后端代碼的開發了。
  • 目前已有非常多的開源庫推出Kotlin版本:RxKotlinkotterknifeleakcanarymaterial-dialogskotlin-web-sitekotlin-dslSwiftKotlinkotlinconf-appDesign-Patterns-In-Kotlinankorecyclerview-animatorsglide-transformationsretrofit2-kotlin-coroutines-adapter

1.2. APP集成現狀

使用 未使用
Pinterest ?? Evernote ?? Uber ?? Facebook ?? twitter ?? 微信讀書 ?? 豆瓣 ?? 釘釘 ?? 京東 ?? 百度 ?? 抖音 ?? 今日頭條 ?? 愛奇藝 YouTube ?? Instagram ?? 迅雷 ?? 微信 ?? 快手 ?? 手機QQ ?? 手機淘寶

1.3. 優勢

1.4. 學習資料

2. 基本語法

下面介紹Kotlin的最重要的基礎語法,里面的某些示例代碼參考github上的倉庫:SampleOfKotlin-Basic

2.1. Defining functions

  • 調用的時候可以顯式的標示參數名。
  • 可以提供默認參數值,減少重載有奇效。
  • 【示例見SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.definingFunctions】
private fun double(x: Int = 100, y: Int = 200): Int {
    return (2 * x) + y
}

2.2. Defining variables

  • Kotlin具有可推倒特性。
  • val:只讀變量用val關鍵字,它僅能被賦值一次。val表示可讀的變量(read-only,注意不是immutable)。
  • var:可被重復賦值的變量用var關鍵字。var表示變量,是可變的(mutable)。
    • val與var的區別在于:val是無法提供set函數的,只有get函數。
  • const:用于定義常量,準確的說const所定義的常量叫編譯時常量
    • 如果它的值無法在編譯時確定,則編譯不過,這個常量跟Java的static final是有所區別的。這里就是說你定義的const val常量要即時賦值,而不能稍后再賦值。
    • const無法定義局部變量,因為局部變量會存放在棧區,它會隨著調用的結束而銷毀,這點跟Java的static final一致。
    • 所以const的使用必須滿足:1. 頂層屬性或者object的成員,2. String或者基本類型的值,3. 沒有自定義 getter。
    • const的使用是一個容易出錯的點,我們應該好好理解它的規則。
  • 【示例見SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic】

2.3. Using string templates

  • 字符串模版配合表達式可以輸出豐富的字符串。
  • """中可以包含換行、反斜杠等等特殊字符。
  • 【示例見SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.usingStringTemplates】
val i = 10
println("i = $i") // prints "i = 10"

val s = "abc"
println("$s.length is ${s.length}") // prints "abc.length is 3"

val price = """
            ${'$'}9.99,


            這里展示 / or // or \  or \\
            """

2.4. Using loop

//注意這里Followers是集合類型
for (item in Followers) print(item)

for (i in 1..10) println(i)

for (i in 1 until 10) println(i)

for (i in 10 downTo 1) println(i)

for (i in 1..10 step 2) println(i)

repeat(10) {
 println(it)
}

for ((index, str) in list.withIndex()) {
 println("第${index}個元素$str")
}
while (x > 0) {
    x--
}

do {
    val y = retrieveData()
} while (y != null) // y is visible here!

2.5. Operations

  • 位運算符只可用在Int和Long類型上
  • 左移(in java <<) → shl(bits)
  • 右移(in java >>) → shr(bits)
  • 無符號右移(in java >>>) → ushr(bits)
  • 按位與 → and(bits)
  • 按位或 → or(bits)
  • 按位異或 → xor(bits)
  • 按位取反 → inv()
    • 原碼:符號位加上真值的絕對值。

    • 反碼:正數的反碼是其本身;負數的反碼是在其原碼的基礎上,符號位不變,其余各個位取反。

    • 補碼:正數的補碼就是其本身;負數的補碼是在其原碼的基礎上,符號位不變,其余各位取反,最后+1。(即在反碼的基礎上+1)

    • 求證~8的過程為什么是-9?

    • 求證-8無符號右移2位后為什么是1073741822?

      ~8的求證過程:
      8原碼:0000 0000 0000 0000 0000 0000 0000 1000
      8反碼:0000 0000 0000 0000 0000 0000 0000 1000
      8補碼:0000 0000 0000 0000 0000 0000 0000 1000
      ~后的補碼:1111 1111 1111 1111 1111 1111 1111 0111
      -1后轉成反碼:1111 1111 1111 1111 1111 1111 1111 0110
      再轉成原碼:1000 0000 0000 0000 0000 0000 0000 1001
      即十進制:-9

      -8 >>> 2的求證過程:
      -8原碼:1000 0000 0000 0000 0000 0000 0000 1000
      -8反碼:1111 1111 1111 1111 1111 1111 1111 0111
      -8補碼:1111 1111 1111 1111 1111 1111 1111 1000
      右移兩位后的補碼:0011 1111 1111 1111 1111 1111 1111 1110
      由于是正數,所以補碼、反碼、原碼一直,所以上面的補碼就是原碼,
      所以00111111111111111111111111111110轉成十進制就是1073741822

2.6. Returns and Jumps

  • return/break/continue。
  • 可以根據需求,返回到指定的標簽處。
  • 【示例展示見官網鏈接】

2.7. Object Expressions and Declarations

  • 可用于創建匿名類的對象。
  • 如果匿名類中包含構造函數,則必須傳遞合適的入參給它。
  • 可以創建一個臨時對象,而不用聲明data class(or javabean)。
  • 對象聲明總是在object關鍵字后跟一個名稱,就像變量聲明一樣,但對象聲明不是一個表達式,不能用在賦值語句的右邊。使用的時候直接通過該名稱引用其中的函數即可。
  • 伴生對象(即著名的companion object),伴生對象的成員可通過只使用類名來調用其中的函數(類似Java中的靜態方法)。盡管伴生對象看起來像靜態成員,但實際上在運行時它仍然是真實對象的實例成員。
  • 【示例見SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.object】
//可用于創建匿名類的對象。
window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { …… }

    override fun mouseEntered(e: MouseEvent) { …… }
})

//如果匿名類中包含構造函數,則必須傳遞合適的入參給它。
open class A(x: Int) {
    public open val y: Int = x
}

interface B { ... }

val ab: A = object : A(1), B {
    override val y = 15
}

//對象聲明
object DataProviderManager {
    fun registerDataProvider(provider: String) {
        echo("$provider 注冊成功")
    }
}
DataProviderManager.registerDataProvider("1號數據源")

2.8. Classes and Inheritance

  • 類及構造函數
    • 構造函數分主次: primary constructor、Secondary Constructors
  • 在Koltin中繼承和實現都通過 :
  • 靜態函數的實現:【示例見SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.staticMethod】
    • 在Kotlin中沒有類似java的靜態方法,想要使用可以考慮采用包級函數代替。
    • 還要一種便利的方式就是通過伴生對象:companion object
//類及構造函數
class Person(val firstName: String, val lastName: String, var age: Int) {
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

//繼承的實現
open class Foo {
    open fun f() { println("Foo.f()") }
    open val x: Int get() = 1
}

class Bar : Foo() {
    override fun f() { 
        super.f()
        println("Bar.f()") 
    }
    
    override val x: Int get() = super.x + 1
}

//抽象類
open class Base {
    open fun f() {}
}

abstract class Derived : Base() {
    override abstract fun f()
}

2.9. Data Classes

類似JavaBean,不過我們不需要再寫get/set方法,還有equals()/hashCode()對以及toString()方法都會被自動生成。

data class Customer(val name: String, val email: String)

2.10. Interfaces

  • 實現多個父類,可以靈活解決方法覆蓋的沖突問題。
  • 想要實現一個匿名內部類實例,需要通過object關鍵字。
  • 【示例見SampleOfKotlin-Basic → Interface】
//多父類下,方法覆蓋沖突問題
interface A {
    fun foo() { print("A") }
    fun bar()
}

interface B {
    fun foo() { print("B") }
    fun bar() { print("bar") }
}

class C : A {
    override fun bar() { print("bar") }
}

class D : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
    }

    override fun bar() {
        super<B>.bar()
    }
}

//匿名內部類
D().setClickListener(object : ClickListener {
    override fun countClick() {
        Log.d(this@MainActivity.localClassName, "點擊回調")
    }
})

2.11. Extensions

  • 擴展函數:Kotlin中的擴展函數是靜態的給一個類添加方法的,不具備運行時的多態效應。
    • String.lastChar()
    • Activity.hideKeyboard()
  • 擴展屬性
    • List<T>.lastIndex
  • 【示例見SampleOfKotlin-Basic → Extensions】

2.12. Generics

  • Java中泛型的特點

    • 類型擦除(在編譯期間擦掉類型信息)
      • 泛型類型參數之間沒有繼承關系(ArrayList<Object> arrayList1=new ArrayList<String>(); //編譯錯誤)
    • 不可具體化的類型(運行時包含的信息比它的編譯時包含的信息更少)
    • 通配符(有界通配符和無界通配符)
  • Kotlin中泛型特點

    • 聲明處型變:out和in

      • out:當一個類(List)的類型參數(E)被聲明為out時,我們就可以將子類泛型的對象賦值給使用父類泛型的對象,即通常所說的類List是在參數E上是協變的。但是副作用是它只能出現在該類成員的輸出位置上。
      • in:當一個類(Consumer)的類型參數(E)被聲明為in時,我們就可以將父類泛型的對象賦值給使用子類泛型的對象,即通常所說的類Consumer在參數E上是逆變的。但副作用是它只能出現在該類成員的輸入位置。
      • 協變和逆變關系圖:


        in-out關系圖.png
    • 類型投影

      • 使用處型變
        • Kotlin的使用處型變直接對應Java的有界通配符。Kotlin中的MutableList<out T>和Java中的MutableList<? extends T>是一個意思。in投影的MutableList<in T>對應到Java的MutableList<? super T>。
      • 星號投影
        • Kotlin的MyType<*>相當于Java中的MyType<?>。
    • 同時限定多個類型

    • Kotlin中可以獲取泛型的實際類型

      • inline(內聯函數) + reified(實化類型)能處理方法級別的真泛型
        • 我們都知道內聯函數的原理,編譯器把實現內聯函數的字節碼動態插入到每次的調用點。那么實化的原理正是基于這個機制,每次調用帶實化類型參數的函數時,編譯器都知道此次調用中作為泛型類型實參的具體類型。所以編譯器只要在每次調用時生成對應不同類型實參調用的字節碼插入到調用點即可。
      • 類級別的泛型需要自己改造
        • 通過伴生對象結合重載invoke調用操作符的方式
  • 對比Java與Kotlin中的使用對比

~ 協變 逆變 不型變
Java ? extend E ? super E ArrayList<Apple>
Kotlin out E in E MutableList<Apple>

Java中的泛型是不型變的,這意味著List<RedApple>并不是List<Apple>的子類型。而通過有界通配符extends關鍵字使得類型可以協變,即可以讓Collection<RedApple>表示為Collection<? extends Apple>的子類型。

  • 【示例見SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.generic】

2.13. Null Safety

  • ? 可為空,不會拋出NPE
  • !! 不可為空,會拋出NPE
  • String和String?是兩種完全不同的數據類型,簡單來說就是String?類型包含了String類型,使用的時候務必小心變量是否為空。
  • Java代碼與Kotlin代碼互調注意點:在Kotlin代碼中,接收一個Java對象的時候,如果你不確定是否可能空,務必將其賦值為可空類型的變量,這樣才能保證代碼是安全的,否則的話極有可能拋出NPE。
  • 【示例見SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.nullSafe】

2.14. Lambda

  • 如果Lambda沒有參數,可以省略箭頭符號
  • 如果Lambda是函數的最后一個參數,可以將大括號放在小括號外面
  • 如果函數只有一個參數且這個參數是Lambda,則可以省略小括號
  • 【示例見SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.lambda】
//如果Lambda沒有參數,可以省略箭頭符號
fun main(args: Array<String>){        
    val thread = Thread({ })        
    thread.start()    
}

//如果Lambda是函數的最后一個參數,可以將大括號放在小括號外面
fun main(args: Array<String>){        
    val thread = Thread(){ }        
    thread.start()    
}

//如果函數只有一個參數且這個參數是Lambda,則可以省略小括號
fun main(args: Array<String>){        
    val thread = Thread{ }        
    thread.start()    
}

2.15. Lambda閉包聲明

  • Lambda+閉包
  • 【示例見SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.lambda】
val echo = { name: Any -> println(name) }

val lambdaA = { a: Int, b: Int, c: Int, d: Int, e: Int, f: Int, g: Int, h: Int,
                i: Int, j: Int, k: Int, l: Int, m: Int, n: Int, o: Int, p: Int,
                q: Int, r: Int, s: Int, t: Int, u: Int, v: Int, w: Int ->
    println("23個入參的閉包")
}

2.16. Higher-Order Functions

  • 高階函數指函數或者Lambda的參數又是一個函數或者Lambda。
  • 當Lambda作為函數參數的最后一個時,是可以寫到小括號外面的。
  • 【示例見SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.lambda】
private fun resultByOpt(num1: Int, num2: Int, result: (Int, Int) -> Int): Int {
    return result(num1, num2)
}

2.17. Visibility Modifiers

  • Kotlin中的訪問修飾符internal : 模塊內可以調用,跨模塊則不行。這里的模塊指類似AS中的module。
  • 如果不特意聲明,默認是public
  • classes, objects, interfaces, constructors, functions, properties and their setters can have visibility modifiers。
  • 【示例見SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.visible】

2.18. Delegation

  • Kotlin的動態代理本質上是通過靜態代理去調用的
  • 通過關鍵字by
  • 【示例見SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.delagation】

2.19. Sealed Classes

  • 超級枚舉,可提高程序擴展性。
  • 【示例見SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.seal】

3. 擴展

3.1. 為什么能實現擴展函數和屬性這樣的特性?

3.1.1. 邊界

  • Kotlin語言最后需要編譯為class字節碼,Java也是編譯為class執行。
  • 可以理解為Kotlin需要轉成Java一樣的語法結構, Kotlin就是一種強大的語法糖而已。
  • 在虛擬機規范范圍以內提供外界最便利的支持,虛擬機不具備的功能Kotlin也不能越界。

3.1.2. Kotlin與Java互轉

  • Kotlin轉Java
    • Tools ---> Kotlin ---> Show Kotlin Bytecode
    • Decompile
  • Java轉Kotlin
    • Java代碼 ---> 選擇Code ---> Convert Java File to Kotlin File

3.1.3. 源碼解析

  • 對于擴展函數,轉化為Java的時候其實就是一個靜態的函數,同時這個靜態函數的第一個參數就是該類的實例對象。
  • 擴展函數和擴展屬性內只能訪問到類的公有方法和屬性,私有的和protected是訪問不了的。
  • 擴展函數不是真的修改了原來的類,定義一個擴展函數不是將新成員函數插入到類中,擴展函數的類型是"靜態的",不是在運行時決定類型。

3.2. kotlin-android-extensions插件原理介紹

  • 在示例項目中有使用到插件:apply plugin: 'kotlin-android-extensions'
  • 觀察MainActivity這個頁面中view的引用方式
  • public View _$_findCachedViewById(int var1){}
  • private HashMap _$_findViewCache;

3.3. Kotlin與Swift的對比

Kotlin-Swift.png

4. 總結

4.1. Kotlin基礎語法總結

  • 語法簡單,不啰嗦
  • 空指針安全
  • 支持方法擴展和屬性擴展
  • Lambda, 高階函數,閉包支持,函數式編程支持
  • 字符串模板,密閉類,代理模式,函數默認參數,data class
  • 與Java交互性好

4.2. 原理擴展以及與Swift對比

  • 利用好Kotlin與Java互轉,掌握工具的使用
  • 讓你有種可以寫swift app的“幻覺”

4.3. 關于轉向Kotlin

  • 對于個人的項目來轉向Kotlin,通常不是很難的選擇。
  • 讓團隊轉用Kotlin,困難可能來自學習成本、歷史包袱、編程思維的轉變。
  • 目前Kotlin已推出穩定版1.3,大公司的許多app中也引入了Kotlin,在2019年04月編程語言排行榜中Kotlin排到35位,加上google的加持,引入Kotlin改寫或重構代碼,讓團隊成員自我提升,似乎無需置疑,好處多多。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容