9012年,鐵汁你爲何還不上手Kotlin?

What is Kotlin?

科特林島(Котлин)是一座俄羅斯的島嶼,位於聖彼得堡以西約30千米處,形狀狹長,東西長度約14千米,南北寬度約2千米,面積有16平方千米,扼守俄國進入芬蘭灣的水道。科特林島上建有喀琅施塔得市,爲聖彼得堡下轄的城市。java

Kotlin是一門以kotlin島名命名的現代化編程語言。它是一種針對Java平臺的靜態類型的新編程語言。專一於與Java代碼的互操做性,幾乎能夠應用於現今使用Java的任何地方:服務端開發、Android應用等等。Kotlin能夠很好的和全部現存的java庫和框架一塊兒工做,且性能與Java旗鼓至關。git

谷歌開發者社區作過一個問卷調查,大概有40%的Android開發者已使用過Kotlin。github


Kotlin簡史

  • 2011年7月,JetBrains推出Kotlin項目。
  • 2012年2月,JetBrains以Apache 2許可證開源此項目。
  • 2016年2月15日,Kotlin v1.0(第一個官方穩定版本)發佈。
  • 2017 Google I/O 大會,Kotlin 「轉正」。
  • 如今 Kotlin 已發佈至 V1.3。 kotlinlang.org/

1. Kotlin的主要特徵

  • 目標平臺:服務端、Android及任何Java運行的地方。
  • 靜態類型。
  • 函數式編程與面向對象
  • 免費且開源

1.1 靜態類型

Kotlin與Java同樣是一門靜態類型編程語言,在編譯期就已經肯定全部表達式的類型。可是與Java不一樣的一點是Kotlin不須要在源碼中顯示的每一個聲明變量的類型,其所擁有的 類型推導 特性使編譯器經過上下文推斷變量的類型。 以下編程

var xOld: Int = 1 
var x = 1
var yOld: String = "string"
var y = "string"
複製代碼

1.2 函數式編程與面向對象

在討論Kotlin中的函數式編程前,咱們先了解一下函數式編程的核心概念:數組

  1. 頭等函數:把函數看成值來使用和傳遞,可使用變量保存它,也能夠將其做爲其餘函數的參數來進行傳遞,或者將其做爲函數的返回值。
  2. 不可變性:使用不可變對象,保證該對象的狀態在建立以後不會再變化。
  3. 無反作用:使用純函數,此類函數在輸入相同參數時會輸出一樣的結果,且不會改變其餘變量的狀態,也不會與外界有任何交互。

因爲Kotlin的首要目標是提供一種更簡介、高效、安全的替代java的語言,因此其擁有java的面向對象特性。同時其豐富的特性集也讓其支持函數式編程的風格。主要特性以下:安全

  • 函數類型:容許函數接受其餘函數做爲參數,或者返回其餘函數。
  • lambad表達式,使用最少的樣板代碼傳遞代碼塊,同時節省性能開銷。
  • 數據類,提供了建立不可變值對象的簡明語法。
  • 標準庫提供了豐富的API集合,使你能夠用函數式編程風格操做對象和集合。

示例代碼:bash

fun main(args: Array<String>) {
    var hello: () -> Unit = { print("Hello world") }
    test(hello)
}

fun test(f: () -> Unit) {
    f.invoke()
}

>>>
Hello world
複製代碼
fun main(args: Array<String>) {
    var square: (Int) -> Int = { it * it }
    test(10, square)
}

fun test(i: Int, f: (Int) -> Int) {
    print(f(i))
}

>>>
100
複製代碼
//數據類
data class User(val name:String,val age:Int)
複製代碼

1.3 免費且開源

Kotlin徹底開源,能夠自由使用,採用Apache2許可證;開發過程徹底公開在Github上。架構

推薦的開發工具:IntelliJ IDEA、Android Studio、Eclipse。框架

2. Kotlin的設計哲學

務實

Kotlin 不是一門哲學性、研究性語言而是一門實用語言,它的的誕生是爲了解決當前編程世界的許多問題,它的特性也是依據與許多開發者遇到的場景而選擇的。Kotlin開發團隊對於Kotlin能幫助解決實際項目問題的特性頗有自信。編程語言

Kotlin也沒有強制使用某種特定的編程風格和範式。因爲其設計者是JetBrains,Kotlin的開發工具IntelliJ IDEA的插件和編譯器乃是同步開發,且在設計語言特性時就考慮到了對工具的支持,因此毋庸置疑,Kotlin的開發工具對於開發者來講是及其有好的。

在咱們編寫Kotlin過程當中,良好的IDE會發現那些能夠用更簡潔的結構來替換的通用代碼模式,咱們同時也能夠經過研究IDE使用的語言特性,將其應用到本身的代碼中。

簡潔

Java中常見的代碼在gettersetter以及將構造函數的參數賦值給變量的操做都被設置爲隱式的。

var name:String = ""
var name: String
    get() {
        return name
    }
    set(value) {
        name = value
    }
複製代碼
class Person(name: String){
    init {
        print(name)
    }
}

class Student{
    constructor(name: String){
        print(name)
    }
}
複製代碼

Kotlin豐富的標準庫也能夠代替不少沒必要要的冗長的代碼。

例:以下所示的list,需求爲:輸出其中第一個包含字母 b 的item,咱們來看看經過Java和Kotlin的分別是如何實現的。

List<String> list = new ArrayList<>();
        list.add("a");
        ...
        list.add("a");
        list.add("ab");
複製代碼

Java

for (String s : list) {
            if (s.contains("b")) {
                System.out.print(s);
                break;
            }
        }
複製代碼

Kotlin

print(list.find { it.contains("b")})
複製代碼

僅需一行代碼就輕鬆搞定了。

還有一個細節須要提醒你們,與不少現代語言同樣,Kotlin中 沒有;號。

越簡潔的代碼寫起來花的時間越短,更重要的是,讀起來耗費的時間更短,便於提升你的生產力, 使你更快的達到目標。

安全

一般,咱們聲稱一門語言是安全的,安全的含義是它的設計能夠防止程序出現某些類型的錯誤。Kotlin借鑑於Java,提供了一些設計,使其使用起來更加安全。

  • 在Kotlin中,沒必要要去指定全部類型聲明,編譯器會自動推斷出來,也下降了程序運行時的出錯概率。
  • Kotlin中定義了可空符號?,將其用來定義變量是否爲 null
val s: String= ""  //不可爲空
   val s1: String? = null  //可爲空
複製代碼
  • 有效避免ClassCastException。在Java中,咱們把一個對象轉換成某一個類型時,須要先對其進行類型檢查,檢查成功後再將其轉換成該類型。一旦忘了檢查操做,就有可能會發生上述異常。在Kotlin中,檢查和轉換被融合成了一個操做,當你檢查符合標準時,再也不須要額外的轉換,就能夠調用屬於該類型的成員。
if (value is String){
          print(value.toUpperCase())
    }
複製代碼

互操做性

在Java項目中添加Kotlin代碼時,不會影響咱們的任何Java代碼。同時,咱們能夠在Kotlin中使用Java的方法、庫。能夠繼承Java的類,實現Java的接口,在Kotlin上使用Java的註解。

另外,Kotlin的互操做性使Java代碼像調用其餘Java類和方法同樣輕鬆的調用Kotlin代碼,在項目任何地方均可以混用Java和Kotlin。

Kotlin最大程度的使用Java庫,使其互操做性更強。好比,Kotlin沒有本身的集合庫,它徹底依賴的Java標準庫,並使用額外的函數來擴展集合功能,使它們在Kotlin中能夠更加便捷的使用。

Kotlin的工具也對互操做性提供了全面支持。能夠編譯任意混合的Java和Kotlin源碼。兩種源文件自由切換,重構某一種語言時,在另一種語言中也會獲得正確的更新。

兩種語言能夠隨意拼接,就像呼吸同樣天然。

3. Kotlin基礎

3.1 變量

在Java中聲明變量時:

int a = 10
複製代碼

在Kotlin中聲明變量時,參數的名稱和類型使用 分隔,類型在以後,稍後咱們會看到函數聲明也是如此。

val a: Int = 10 //顯示指定變量類型
val a = 10 //類型聲明省略
複製代碼

在Kotlin中全部的變量必須有初始值,若是沒有也必須經過延遲初始化或者懶加載方式,在調用變量以前完成賦值操做。

lateinit var name: String
複製代碼
val name by lazy { "liuxi" }
複製代碼

若是你細心的話,會發如今上述的例子中出現了兩種聲明變量的關鍵字:

  • var ——(來自variable)可變引用,對應Java普通變量(非final),值能夠任意改變,類型不可再變。
  • val —— (來自value)不可變引用,對應Java中final變量,val聲明的變量在初始化以後不能再次賦值。 Kotlin推薦咱們在默認狀況下使用 val 聲明變量,在須要的時候再使用 var 聲明變量

3.1.1 便捷的字符串模版

Kotlin爲咱們提供了更便捷的字符串拼接方式供咱們使用

val name = "liuxi"
fun age() = 12
val log = "name: $name age: ${age()}"

>>>
name: liuxi age: 12
複製代碼

3.2 函數的定義和調用

咱們先來看一下Kotlin中的Hello world。

fun main(args: Array<String>){ //main 是函數名,()內部是參數列表, {}內部是函數體
    print("Hello world")
}
複製代碼

Kotlin使用fun關鍵字來定義函數。

接下來咱們在看一下帶返回類型的函數:

fun max(a: Int,b: Int): Int{
    retun if (a > b) a else b 
}
複製代碼

函數的返回類型被定義在 以後

Kotlin中 if 是有結果值的表達式,不是語句,同時被用來替換掉Java中三元運算符 (boolean值) ? (表達式1) :(表達式2)。

表達式和語句的區別是表達式有值,且能夠做爲另外一個表達式的一部分來使用。

3.2.1 表達式函數體

若是一個函數的函數體是由單個表達式構成的,那麼這個函數能夠去掉外層的括號{}return,在函數聲明和表達式中間用=鏈接。

fun max(a: Int,b: Int) = if(a > b) a else b
//=後面的內容能夠被稱做表達式體
複製代碼

藉助於Kotlin優秀的類型推導特性,在上述範例中,咱們也省略掉了對函數返回類型的聲明。


從Kotlin到Java,不少概念都是相似的。Kotlin 爲了讓函數更簡潔易讀,借鑑Java並作出了不少改,基本以下:

  • 定義了命名參數、默認參數值以及中綴調用的語法。
  • 經過擴展函數和屬性去適配Java庫。
  • 使用了頂層函數,局部函數和屬性架構代碼。

咱們先來看看在Kotlin 中命名參數,默認參數值的使用

3.2.2 命名參數、默認參數值

命名參數指的是在調用Kotlin定義的函數是,能夠顯示的代表一些參數的名稱。固然若是一個函數由多個參數組成,爲了不混淆,在你給其中一個參數之名名稱以後,其以後的全部參數都須要標明名稱。

fun createAccount(account: String,password: String,desc: String){
    ...
}

create("liuxi","123456","cool")//常規使用

create("liuxi",password = "123456", desc = "cool") //使用命名參數的方式調用

複製代碼

默認參數值,顧名思義就是一個參數支持聲明默認值,用於避免重載的函數。

好比上述的createAccount()函數中的desc是一個非必要參數,這時咱們就能夠給其設置默認值。

fun createAccount(account: String,password: String,desc: String = ""){
    ...
}

createAccount("liuxi","123456","cool")

createAccount("liuxi","123456")//當用戶不想輸入這個參數時,咱們能夠直接最後一個參數省略掉。
複製代碼

固然,默認參數值的寫法在Java中是不被支持的,調用此方法時仍需顯式的指定全部參數。爲了Java也能夠體會到此方法的便捷性,咱們可使用@JvmOverloads註解此方法。這樣在Java中會自動生成此方法的重載方法。

3.2.3 頂層函數和屬性

頂層函數和屬性的誕生是爲了消除Java中靜態工具類的寫法。即Java中各式各樣的Util工具類。

在Kotlin中,咱們能夠將一些函數和屬性(它們的功能類型使得它們很難歸屬到某一個具體的類中)直接寫在代碼文件(.kt)的頂層,不屬於任何的類。這些函數和屬性依舊是包內的成員。若是從包外想訪問它,則須要Import。 咱們來建立一個Log.kt文件

package com.liuxi.fun

val KEY = "123456"

fun log(msg:String){  //不須要放置在類體中
    Log.d("liuxi_test", msg) 
}
複製代碼

在Kotlin中使用此方法就比較便捷:

log("測試頂層函數")
複製代碼

而在Java中,這個文件會被編譯成一個以文件名命名的類,頂層函數會被編譯成這個類的靜態函數。咱們也能夠利用@JvmName註解去自定義類的名稱。

/*Java*/
package com.liuxi.fun

public class LogKt{
    
    public static final String KEY = "123456"
    
    public static void log(String msg){
        Log.d("liuxi_test", msg) 
    }
}
複製代碼

3.2.4 擴展函數和屬性

Kotlin的一個特點就是能夠平滑的與現有代碼集成。當咱們在項目中遇到Java和Kotlin混合使用的時候,爲了便捷的使用一些基於Java的功能,咱們能夠利用擴展函數來對其進行優化。

理論上來講,擴展函數就是類的成員函數,只不過它定義在類的外面。咱們來看一個TextView的擴展函數:結合SpannableStringTextView賦值包含一個Image的文本:

/*文件名SpanHelper.kt*/

/**
 * 將目標String的替換爲Image
 *
 * @param mainBody
 * @param targetBody
 * @param drawableRes
 * @return
 */
fun TextView.setTextWithImageSpan(mainBody: String?, targetBody: String?, drawableRes: Int) {
    if (mainBody.isNullOrEmpty() || targetBody.isNullOrEmpty() || drawableRes <= 0) {
        return
    }
    val spannableStr = SpannableString(mainBody)
    val start = mainBody.indexOf(targetBody)
    val end = start + targetBody.length
    spannableStr.setSpan(ImageSpan(context, drawableRes), start, end, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)
    text = spannableStr
}
複製代碼

此擴展方法的調用示例:

textView.setTextWithImageSpan("酷酷的", "酷酷", R.drawable.icon_diamond_small)
複製代碼

簡短的一行代碼,咱們就實現了給TextView設置一段包含圖片的文本。這個功能真的很酷~~~

接下來,咱們來看一下在Java中如何調用擴展函數。

SpanHelperKt.setTextWithImageSpan(textView, "酷酷的", "酷酷", R.drawable.icon_diamond_small)
複製代碼

由於這個函數被聲明爲頂層函數,因此它被編譯成爲靜態函數。textView被當作靜態函數的第一個參數來使用。

擴展函數不存在重寫,Kotlin會把其看成靜態函數來看待。

接下來咱們來看一下擴展屬性:

//文件名IntUtil.Kt
val Int.square: Int
    get() = toInt() * toInt()

fun main(args:Array<String>){
    print(5.square)
}

>>> 25
複製代碼

與擴展函數同樣,擴展屬性也像是接收者的一個普通成員屬性同樣。可是必須定義 getter 函數。由於沒有支持的字段,因此沒有默認的getter 。同理由於沒有存儲的地方,因此也沒法初始化。我的感受擴展屬性和擴展函數相似,可是使用的便捷性比擴展函數略遜一籌。

//擴展屬性在Java中的調用
IntUtilKt.getSquare(5)
>>> 25
複製代碼

3.2.5 局部函數

一種支持在函數內部再定義一個函數的語法:

fun createAccount(name:String,password:String){

    fun lengthNotEnough(string:String) = string.length > 10
    
    if(lengthNotEnough(name) return
    if(lengthNotEnough(password) return
}
複製代碼

這個功能的意義在於解決代碼重複問題,儘量的保持清晰的代碼結構,提升可讀性。

小知識點:局部函數能夠訪問外層函數的參數。

3.3 對集合的支持:可變參數,中綴調用,庫的支持

3.3.1 可變參數

以下是Collections.kt中的一個方法

public fun <T> listOf(vararg elements: T): List<T> = if (elements.size > 0) elements.asList() else emptyList()
複製代碼

vararg修飾符在Kotlin中被用來聲明能夠接受任意數量的參數。

3.3.2 中綴調用

一個頗有趣的語法糖。 一下是我隨手寫的一個例子

infix fun TextView.copy(view: TextView) {
    this.text = view.text
} 
複製代碼

咱們使用infix 添加在只有一個參數的函數前面。上述例子是在一個擴展函數前面添加上了infix。 使用範例以下:

val textView1 = ...
 val textView2 = ...
 textView1 copy textView2  //中綴調用式寫法,這樣textView2的內容就被複制到了textView1 中
 textView1.copy(textView2)//常規寫法,與中綴調用式寫法等價。
複製代碼

中綴調用是一種特殊的函數調用,在使用過程當中沒有添加額外的分隔符,函數名稱(copy)直接放置在目標對象和參數之間。

3.3.3 集合庫的支持

Kotlin中的集合與Java的類相同,可是對擴展函數、內聯函數等對API作了大量的擴展,此處就再也不一一闡述了。

4. 類

Kotlin中和Java中同樣,使用關鍵字 class 聲明類

class Person{
    
}
複製代碼

若一個類沒有類體,可上述花括號能夠省略

class Person
複製代碼

另外一個須要注意的地方是:Kotlin中類聲明中沒有了public。在Kotlin中,類默認爲public和final的

4.1 構造方法

如你所知,在Java中,一個類能夠聲明一個或者多個構造函數,在Kotlin中一樣如此。可是Kotlin對構造函數作了一些修改,引入的主構造方法(在類體外聲明,爲了簡潔的初始化類的操做而被創造出來)和從構造方法(與Java構造方法相似,在類體中聲明)併爲此新增了兩個關鍵字 initconstructor

關鍵字constructor用來聲明 構造方法

下面是一個用Java式思想和kotlin定義的新語法寫出來的Person類

class Person{
    val name: String 
    constructor(name: String){
        this.name = name
    }
}
複製代碼

接下來咱們對其引入主構造函數的概念和 init關鍵字:

class Person constructor(name: String){ //帶一個參數的主構造方法
    val name: String
    init {   //初始化語句塊
        this.name = name
    }
}
複製代碼

如上代碼所示,init用來標示初始化語句塊,在類被建立的時候被執行,也可搭配主構造h函數使用。一個類中也能夠聲明多個初始化語句塊,它們按照出如今類體中的順序執行。

接下來,咱們在對Person類進行簡化:

class Person constructor(name: String){
    val name: String = name
}
複製代碼

主構造函數的參數同樣可使用 varval 聲明(從構造函數不能夠),咱們可使用以下方式繼續簡化咱們類的屬性定義:

//val 表示相應的屬性會用構造方法的參數來初始化
class Person constructor(val name: String)
複製代碼

若是主構造函數沒有註解或者可見性修飾符,則咱們能夠將constructor省略。

class Person (val name: String)
複製代碼

在 Kotlin 中的一個類能夠有一個主構造函數以及一個或多個從構造函數。當一個類有主構造函數時,其類體中聲明的從構造函數須要經過直接或者間接的方式委託給主構造函數,此時就須要使用到this關鍵字。

class Person(val name: String = "liuxi"){
    constructor(name: String,age: Int): this(name)
    constructor(name: String,age: Int,gender:String): this(name, age)
}
複製代碼

若是你想你的類不被其餘代碼實例化,則能夠給構造函數添加可見型修飾符private

class Person private constructor()
複製代碼
  • 建立的實例
val p = Person("liuxi")
複製代碼

注:Kotlin中聲明對象不須要使用 new 關鍵字

4.2 類的繼承結構

咱們先來認識幾個基礎概念:

接口 : 使用 inerface 定義

interface OnClickListener{
    fun onClick()
    fun test(){ //帶有默認方法體的方法
        print("Hello")
    }
}
複製代碼

抽象類: 使用abstract修飾

abstract class Game{
  abstract fun play()
  
  open fun record(){ //關鍵字open用來表示這個方法能夠被重寫,open也能夠被用來修飾類。
      print("score")
  }
  
  fun test(){
      print("test")
  }
}
複製代碼

接下來咱們來展現一下如何繼承一個抽象類

class A: Game(){
    override fun play() = print("Go play")
    
    override fun record(){
        super.record() //super 關鍵字用來標示對父類或者接口的默認方法體的引用,相似於Java
    }
}
複製代碼

接下來咱們在讓類A實現接口OnClickListener

class A: Game(), OnClickListener{
    override fun play() = print("Go play")
    
    override fun record(){
        super.record() //super 關鍵字用來標示對父類或者接口的默認方法體的引用,相似於Java
    }

   override fun onClick()
   
   override fun test(){
       super.test()
   }
}

複製代碼

如上所示,不管是繼承仍是實現接口,咱們都統一經過 : 來實現,沒有使用Java中的 extendsimplements

其中還須要注意下類的繼承,和接口的實現有一個區別,被繼承的類名後帶了(),它值得是類的構造方法。若是你的類聲明瞭主構造方法且含有參數,則子類也被強制要求實現主構造方法。此定義相似於Java的子類必須實現父類構造方法,且將父類構造所須要的參數傳遞給父類。

open class A(name:String) //一個類若是非抽象類,則默認是final,不能夠被繼承,可是用open修飾後就能夠了。

class B(name: String): A(name)
複製代碼

openfinal 是兩個互斥定義的關鍵字。

還有一個細節也須要咱們注意,當你override父類或者接口的方法後,它們默認是open的,若是你不但願你的子類再去修改它,須要用final修飾

class A: Game(), OnClickListener{
    final override fun play() = print("Go play")
   ...
}

複製代碼

4.3 數據類、內部類、嵌套類、密封類

數據類

data class Person(val name)
複製代碼

內部類和嵌套類

Java:

class A{
        public String name = "liuxi";

       static class B{
            void test(){
                System.out.print(name);//此操做被拒絕
            }
        }

        class C{
            void test(){
               System.out.print(name); 
            }
        }
    }
複製代碼

Kotlin:

class A{
    var name = "liuxi"
    
    class B{
        fun test(){ //爲嵌套類
            //print(name),此操做被拒絕,不持有外部類的實例,至關於Java 中的 static class B
        }
    }
    
    inner class C{
        fun test(){ //爲嵌套類
            print(name) //持有外部類的實例 
        }
    }
}

複製代碼

密封類 : 定義受限的類繼承結構。這是爲了解決when結構必需要實現一個else分支(相似於Java switch case中的default)而提出的解決方。給父類添加 sealed 修飾符且全部的直接子類必須嵌套在父類中。

sealed class Children{
    class Boy: Children()
    class Girl:Children()
}
複製代碼

4.4 object關鍵字

object 關鍵字在Kotlin中有主要有三個使用場景,其核心理念是:object定義一個類同時建立一個實例。 接下來,看一下三個場景各是什麼:

  1. 在Kotlin中使用對象聲明去去建立單例:引入object 關鍵字,經過一句話去定義一個類和一個該類的變量。在編譯過程當中,該變量的名字始終都爲INSTANCE
object Utils{     //因爲類在定義的時候就當即建立了該類的變量,因此該類不被容許擁有構造函數,除此以外,其餘地方都和普通類同樣。
    fun test(){
        ...
    }
    val TEST = "test"
}
複製代碼

沒有在Java中的各類樣式書寫的單例模式,Kotlin僅需一行代碼,輕鬆搞定。

kotlin中調用以下:

Utils.test()
Utils.TEST
複製代碼

在Java中調用以下:

Utils.INSTANCE.test()
Utils.INSTANCE.TEST
複製代碼

須要經過.INSTANCE引用對象聲明中的方法,相似於咱們在Java單例模式中習慣去建立的mInstance

  1. 伴生對象在Kotlin中的使用

Kotlin沒有保留Java中的static關鍵字,爲了補足靜態函數和屬性的功能,Kotlin提出了伴生對象,頂層函數和屬性(也叫包級函數和屬性),咱們先來說解伴生對象,後者在以後會被提到。

伴生對象是一個聲明在類中的普通對象,能夠擁有名字,也能夠實現接口或者有擴展函數或屬性。

class Person(val name: String){
    
    companion object Loader{
        fun test(){
           ... 
        }
        
        val type:String = "simple"
    }
}
複製代碼

如上範例所示:companion關鍵字被用來標記伴生對象,伴生對象的名字能夠被省略。簡易寫法以下:

class Person{
    companion object{
        ...
    }
}
複製代碼

咱們能夠把伴生對象看成Java中的靜態對象來看待,被定義爲伴生對象後,咱們就能夠經過容器類的名稱來獲取這個對象的調用了。示例以下:

Person.companion.test()
複製代碼
  1. object 來實現匿名內部類

object 在類體內部,能夠被用來聲明匿名對象 (替代了Java中的匿名內部類用法)。

常見使用場景有:將一個接口或者抽象類聲明爲匿名對象。

以下是將咱們常見的 OnClikListener 聲明爲匿名對象:

view.setOnClickListener(object: OnClickListener{
    override onClick(v:View){
        
    }
})
複製代碼

5. 一些頗有特點的語法

5.1 when

Java中使用swich語句來完成對一個值可能出現的不一樣狀況的做出對應處理。在Kotlin中咱們使用when來處理。它比switch擁有更多的使用場景,功能更增強大,使用的也更加頻繁,且還能夠和智能轉換 完美的結合使用。

when和if同樣,都是表達式,能夠擁有返回值。

舉個例子來講:當下有一個需求,咱們須要根據不一樣vip等級返回不一樣的drawable,供給ImageView使用。

val imageView: ImageView = ...
    val level: Int = ...
    var imageView = imageView.setImageResource(
    when (level) {
        1 -> R.drawable.icon_level_1
        2 -> R.drawable.icon_level_2
        3 -> R.drawable.icon_level_3
        4 -> R.drawable.icon_level_4
        5 -> R.drawable.icon_level_5
        else -> {
            R.drawable.icon_level_default
        }
    })
複製代碼

when語句對於level的不一樣狀況,when返回不一樣分支上的值交給setImageResource()去使用。

值得一提的是,when 支持在一個分支上合併多個選項,好比咱們對上述例子作出修改,將一、2定義爲上,3定義爲中,四、5定義爲下,咱們使用 when寫出的代碼以下:

...
    when (level) {
        1,2 -> R.drawable.icon_level_top
        3 -> R.drawable.icon_level_middle
        4,5 -> R.drawable.icon_level_bottom
        else -> {
            R.drawable.icon_level_default
        }
    }
複製代碼

whenswitch強大的地方在於: 前者容許使用任何對象做爲分支條件,甚至包括無參。可是後者必須使用枚舉常量、字符串或者數字字面值。

open class BaseActivity{
  ...  
}

class MainActivity: BaseActivity(){
    ...
    fun mainPrint(){
        print("main")
    }
}

class SettingActivity: BaseActivity(){
    ...
}

複製代碼
fun getTitle(activity: BaseActivity):String = when(activity){
    is MainActivity -> {
        activity.mainPrint()
        "main"        //if 和 when 均可以使用代碼塊做爲分支體,在示例狀況下,代碼塊中最後一個表達式就是返回的結果
    }
    is SettingActivity -> "Setting"
     
     else -> {
         ""
     }
}
複製代碼

is :用來檢查判斷一個變量是不是某種類型,檢查事後編譯器會自動幫你把該變量轉換成檢查的類型,你能夠直接調用該類型下的方法和變量,此功能能夠被稱做 智能轉換 。它替咱們省略掉了Java 中的 instanceOf 檢查後的類型強制轉換這一步,類型判斷和類型轉換二合一的操做也使咱們的代碼更加安全。固然,若是你仍然須要顯式的類型轉換,Kotlin提供了 as 來表示到特定類型的顯示轉換。

var a = activity as Maintivity
複製代碼

接下來咱們從一個例子來認識一下 when 的無參寫法。

咱們須要根據兩人團中成員的性別分別給出類型定義,如果都爲男性則稱做男團、都爲女性則成爲女團、男女混合則稱做二人轉

/*常量*/
const val BOY = 0
const val GIRL = 1
const val BOY_GROUP = "男團"
const val GIRL_GROUP = "女團"
const val MIX_GROUP = "二人轉"
複製代碼

咱們先給出 if 語句的寫法:

fun getGroupName(gender1: Int, gender2: Int) =
        if (gender1 != gender2) {
            MIX_GROUP
        } else if (gender1 == BOY) {
            BOY_GROUP
        } else {
            GIRL_GROUP
        }
複製代碼

咱們先給出 when 語句的寫法:

fun getGroupName2(gender1: Int, gender2: Int) =
        when {
            gender1 != gender2 -> MIX_GROUP

            gender1 == BOY -> BOY_GROUP

            else -> GIRL_GROUP
        }
複製代碼

when語句的功能特別豐富,相似於Java中的switch可是功能更增強大。

5.2 Kotlin的可空性

可空性 是Kotlin類型系統中幫你避免NullPointerException錯誤的特性。

在Java中咱們遇到過太多太多的 java.lang.NullPointerException,Kotlin解決這類問題的方法是將運行時的異常轉換爲編譯時的異常。經過支持做爲類型學系統的一部分的可控性,編譯器就能在編譯期間發現不少潛在的錯誤,從而減小運行時拋出空指針異常的可能性。

以下是一個獲取字符串長度的方法

fun getLength(str: String?){  //咱們須要在那些可能爲null的實參後面加上問號來標記它
    if(str == null){
        return 0
    }
    return str.length
}
複製代碼

問號 ? 能夠加在任何類型的後面,來表示這個類型的變量能夠存儲null引用。相反沒有被 ?

標記的變量不能存儲null引用。若是你對一個不接收null引用的參數賦值爲null,程序會直接拋出異常 Null can not be a value of a non-null type

可空類型和非空類型在Kotlin中有着很嚴格的區分。一旦你的變量被定義爲可空類型,對它的操做也將收到限制,你將沒法調用它的方法,除非你能證實它不爲空;也不能將它賦值給非空類型的變量。也不能將可空類型的參數傳給擁有非空類型參數的函數。

那麼咱們能夠對可空類型參數作什麼呢?咱們對於可空類型參數,最重要的就是和null進行比較。一旦你進行了比較操做,編譯器就會記住。而且在這次比較發生的做用域內把這個值看成非空來對待(咱們就能夠調用非空類型下的參數和函數了),如上述例子。

可空的和非空的對象在運行時沒有什麼區別。可空類型並非非空類型的包裝。全部的檢查都發生在編譯器。即便用Kotlin的可空類型在運行時不會帶來額外的開銷。

5.2.1 安全調用運算符:?.

安全調用運算符?.容許把一次null檢查和一次方法調用合併成一個操做。

str?.toUpperCase()

if(str != null) str.toUpperCase() else null     //等效於str?.toUpperCase()
複製代碼

安全運算調符只會調用非空值的方法。 安全調用符的結果類型也是可空的。在上述例子中返回類型爲String?

5.2.2 Elvis 運算符 ?:

在引入可空性概念後,就表明了Kotlin中會出現不少默認值爲null的可空類型。爲了不默認值weinull帶來的麻煩,Kotlin提供了 Elvis運算符 ?: (null合併運算符)

下面展現了它是怎麼使用的:

fun toStr(s: String?){
    val str =  s ?: "" // 若是s爲null,則賦值爲""
    
    var length = s?.length ?: 0  //常見使用場景,和安全調用運算符搭配使用
}
複製代碼

5.2.3 非空斷言!!

一個很暴力的語法!非空斷言是最簡單直率的處理可空類型的工具。它使用!!雙歎號表示。

var length = s!!.length 
複製代碼

當一個使用非空斷言的變量爲null時,會顯式的拋出空指針異常。除非你對你的邏輯頗有自信,不要輕易使用這個工具。 常見使用場景是你在一個函數使用一個值時對其進行了非空判斷,而在另外一個函數中使用這個值時,不想重複進行非空判斷,這時你就可使用非空斷言。

5.2.3 let函數

let 函數讓可空表達式的處理變得更加容易。它會把調用它的對象變成一個lambda表達式。

和安全調用運算符一塊兒使用時,lambda中的it爲該對象的非空值,若是該對象爲null則什麼都不會發生,不爲空時會執行lambda表達式

str?.let{ print(it.length)}

/* 如下是爲了增長可讀性,簡單格式化後的寫法*/
str?.let{ it-> 
    print(it.length)
}

/*常規寫法的等效語句*/
if(str != null){
     print(it.length)
}
複製代碼

let生成的lambda語句中的it表明着調用對象的非空值

6. 總結

本文到此也基本告一段落了,寫下這邊文章的主要目的是想讓更多的對Kotlin有興趣的開發人員、在Kotlin和Java中徘徊糾結的人們還有那些對Kotlin抱有各類觀念的人簡單瞭解一下Kotlin,激發起你們學習Kotlin的興趣,也拋開對Kotlin的偏見。看完此篇文章你應該也能夠簡單上手Kotlin了,快快寫起來吧~

文章的定義是入門講解,所以不少地方講解的不夠完善,也不夠詳細。但願那些對Kotlin有興趣的人在學習Kotlin過程當中最好仍是可以系統的學習一邊Kotlin的基礎知識以及進階語法。文章絕大部分知識點參照於《Kotlin實戰》,推薦入門學者翻閱。

一些想寫但沒加入到文章中的知識點(有些部分是我的掌握不熟練,怕描述不許確,還有一點是基於篇幅考慮,怕寫的太長嚇跑新人)

  1. Kotlin中的lambda表達式,這部分的內容十分豐富,還有不少簡單便捷的語法糖。
  2. Kotlin 基本數據類型,有點懶就沒寫,但願能夠自行翻閱。
  3. Kotlin中的集合和數組 以及迭代相關知識(for 、區間、數列、map迭代、in關鍵字)。
  4. Kotlin的高階函數和泛型。
  5. 協程(coroutines)是在Kotlin1.3版本中正式發佈,必定要學習~

歡迎你們多多討論,講的晦澀或描述的有問題的地方也懇請你們指正,一塊兒進步~~~~///(^v^)\~~~

相關文章
相關標籤/搜索