三篇文章帶你快速入門Kotlin(上)

yaoxin521123發表於2021-01-02

三篇文章帶你快速入門 Kotlin(上)

Kotlin的發展歷程

2011年,JetBrains釋出了Kotlin的第一個版本,並在2012年將其開源。

2016年Kotlin釋出了1.0正式版,代表著Kotlin語言已經足夠成熟和穩定了,並且JetBrains也在自家的旗艦IDE開發工具IntelliJ IDEA中加入了Kotlin的支援。

2017年Google宣佈Kotlin正式成為Android開發一級語言,並且Android Studio也加入了對Kotlin的支援。

2019年Google正式宣佈了Kotlin First,未來提供的官方API也將會以Kotlin版本為主。

Kotlin相比於Java的優勢

語法更加簡潔,對於同樣的功能,使用Kotlin開發的程式碼量可能會比使用Java開發的減少50%甚至更多。

語法更加高階,相比於Java比較老舊的語法,Kotlin增加了很多現代高階語言的語法特性,使得開發效率大大提升。

語言更加安全,Kotlin幾乎杜絕了空指標這個全球崩潰率最高的異常。

和Java是100%相容的,Kotlin可以直接呼叫使用Java編寫的程式碼,也可以無縫使用Java第三方的開源庫。這使得Kotlin在加入了諸多新特性的同時,還繼承了Java的全部財富。

Kotlin的工作原理

Kotlin可以做到和Java 100%相容,這主要是得益於Java虛擬機器的工作機制。

其實Java虛擬機器並不會直接和你編寫的Java程式碼打交道,而是和編譯之後生成的class檔案打交道。

而Kotlin也有一個自己的編譯器,它可以將Kotlin程式碼也編譯成同樣規格的class檔案。

Java虛擬機器不會關心class檔案是從Java編譯來的,還是從Kotlin編譯來的,只要是符合規格的class檔案,它都能識別。

也正是這個原因,JetBrains才能以一個第三方公司的身份設計出一門用來開發Android應用程式的程式語言。

函式和變數

變數

Kotlin中定義一個變數,只允許在變數前宣告兩種關鍵字:val和var。

  • val(value的簡寫的簡寫)用來宣告一個不可變的變數,這種變數在初始賦值之後就再也不能重新賦值,對應Java中的final變數。

  • var(variable的簡寫的簡寫)用來宣告一個可變的變數,這種變數在初始賦值之後仍然可以再被重新賦值複製,對應Java中的非final變數。

    fun main() {
        val a = 10
        var b = 5
        b = b + 3
        println("a = " + a)
        println("b = " + b)
    }
    

內嵌表示式

在Kotlin中,我們可以直接將表示式寫在字串裡面,即使是構建非常複雜的字串,也會變得輕而易舉。

Kotlin中字串內嵌表示式的語法規則如下:

"hello, ${obj.name}. nice to meet you!"

當表示式中僅有一個變數的時候,還可以將兩邊的大括號省略:

"hello, $name. nice to meet you!"

函式

定義一個函式的語法規則如下:

fun methodName(param1: Int, param2: Int): Int {
      return 0
}

當一個函式的函式體中只有一行程式碼時,可以使用單行程式碼函式的語法糖:

fun methodName(param1: Int, param2: Int) = 0

使用這種寫法,可以直接將唯一的一行程式碼寫在函式定義的尾部,中間用等號連線即可

return關鍵字也可以省略,等號足以表達返回值的意思。

Kotlin還擁有出色的型別推導機制,可以自動推匯出返回值的型別。

邏輯控制

if條件語句

fun largerNumber4(num1: Int, num2: Int): Int {
    val value = if (num1 > num2) {
        num1
    } else {
        num2
    }
    return value
}

Kotlin中的if語句相比於Java有一個額外的功能:它是可以有返回值的,返回值就是if語句每一個條件中最後一行程式碼的返回值。

fun largerNumber(num1: Int, num2: Int): Int {
    val value = if (num1 > num2) {
        num1
    } else {
        num2
    }
    return value
}

仔細觀察上述程式碼,你會發現value其實是一個多餘的變數,我們可以直接將if語句返回,這樣程式碼將會變得更加精簡,如下所示:

fun largerNumber(num1: Int, num2: Int): Int {
    return if (num1 > num2) {
        num1
    } else {
        num2
    }
}

當一個函式只有一行程式碼時,可以省略函式體部分,直接將這一行程式碼使用等號串連在函式定義的尾部。雖然largerNumber()函式不止只有一行程式碼,但是它和只有一行程式碼的作用是相同的,只是return了一下if語句的返回值而已,符合該語法糖的使用條件。那麼我們就可以將程式碼進一步精簡:

fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) {
    num1
} else {
    num2
}

最後,還可以將上述程式碼再精簡一下,直接壓縮成一行程式碼:

fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2

when條件語句

當需要判斷的條件非常多的時候,可以考慮使用when語句來替代if語句。

類似與switch語句又遠比switch語句強大的多

分支可以時表示式,也可以時具體數值。

fun getScore1(name: String) = when (name) {
    "Tom" -> 86
    "Jack" -> 77
    "Jim" -> 33
    "Lily" -> 44
    else -> 0
}

高階用法:when不帶引數寫法,不常用,但是擴充套件性很強

fun getScore2(name: String) = when {
    name == "Tom" -> 86
    name.equals("Jack") -> 77
    name.endsWith("Jim") -> 33
    name.startsWith("Lily") -> 44
    else -> 0
}

when不帶引數,多引數

fun getScore3(name: String, name1: String) = when {
    name == "Tom" -> 86
    name.equals("Jack") -> 77
    name1.endsWith("Jim") -> 33
    name1.startsWith("Lily") -> 44
    else -> 0
}

for-in迴圈語句

我們可以使用如下Kotlin程式碼來表示一個區間:

val range = 0..10

上述程式碼表示建立了一個0到10的區間,並且兩端都是閉區間,這意味著0到10這兩個端點都是包含在區間中的,用數學的方式表達出來就是[0, 10]。

也可以使用until關鍵字來建立一個左閉右開的區間:

val range = 0 until 10

如果你想跳過其中的一些元素,可以使用step關鍵字:

fun main() {
    for (i in 0 until 10 step 2) {
        println(i)
    }
}

如果你想建立一個降序的區間,可以使用downTo關鍵字:

fun main() {
    for (i in 10 downTo 1) {
        println(i)
    }
}

控制迴圈語句Break

退出當前迴圈

@ 標籤可以指定退出外層迴圈

fun for(){
    out@ for (i in 0..5) {
        println("i$i")
        for (j in 0..3) {
            println("j$j")
            if (j == 1) {
                break@out
            }
        }
    }
}

控制迴圈語句Continue

退出本次迴圈

fun for(){
    out@ for (i in 0..5) {
        println("i$i")
        for (j in 0..3) {
            println("j$j")
            if (j == 1) {
                continue@out
            }
        }
    }
}

物件導向程式設計

類與物件

可以使用如下程式碼定義一個類,以及宣告它所擁有的欄位和函式:

class Person {
    var name = ""
    var age = 0
    fun eat() {
        println("name:" + name + " age:" + age)
    }
}

然後使用如下程式碼建立物件,並對物件進行操作:

fun main() {
    val p = Person()
    p.name = "yx"
    p.age = 66
    p.eat()
}

繼承

Kotlin中一個類預設是不可以被繼承的,如果想要讓一個類可以被繼承,需要主動宣告open關鍵字:

open class Person {}

要讓另一個類去繼承Person類,則需要使用冒號關鍵字:

class Student : Person() {
    var sno = ""
    var grade = 0
}

現在Student類中就自動擁有了Person類中的欄位和函式,還可以定義自己獨有的欄位和函式。

構造器

  • 主構造器

constructor 關鍵字如果沒有任何註解或修飾可以省略

如果沒有為非抽象類定義任何主次構造器,系統會提供一個無引數的主構造器。預設修飾為public

一旦程式設計師為一個類提供了構造器,系統將不再為該類提供構造器

  • 次構造器

任何一個類只能有一個主建構函式,但可以有多個次建構函式

如果定義了主構造器,那麼次構造器必須委託主構造器

如果沒有定義柱構造器則不用委託主構造器

class Student(val sno: String, val grade: Int) : Person() {
    init {
        println("sno:$sno")
        println("grade:$grade")
    }
}

init 關鍵字為初始化時回撥的邏輯塊

介面

Kotlin中定義介面的關鍵字和Java中是相同的,都是使用的interface:

interface Study {
    fun readBooks()
    fun doHomework()
}

而Kotlin中實現介面的關鍵字變數了冒號,和繼承使用的是同樣的關鍵字:

class Artist(var name: String) : Study {
    override fun readBook() {
        println("name:$name readBook")
    }

    override fun doHomework() {
        println("name:$name doHomework")
    }
}

資料類

Kotlin中使用data關鍵字可以定義一個資料類:

data class Cellphone(val brand: String, val price: Double)

Kotlin會根據資料類的主建構函式中的引數將equals()、hashCode()、toString()等固定且無實際邏輯意義的方法自動生成,從而大大簡少了開發的工作量。

data class CellPhone(val brand: String, val price: Double)

class Cellphone1(val brand: String, val price: Double)

fun main() {

    val str = "{'userCode':'demo','userName':'Demo Group'}"
    val a = EncryptUtils.decryptBase64AES(
        str.toByteArray(),
        "PHA".toByteArray(),
        "CBC",
        "".toByteArray()
    )
    println(a)


    val cellPhone1 = CellPhone("apple", 1000.0)
    val cellPhone2 = CellPhone("apple", 1000.0)
    println(cellPhone1)
    println(cellPhone1 == cellPhone2)

    val cellPhone3 = Cellphone1("apple", 1000.0)
    val cellPhone4 = Cellphone1("apple", 1000.0)
    println(cellPhone3)
    println(cellPhone3 == cellPhone4)
}

單例類

Kotlin中使用object關鍵字可以定義一個單例類:

object Singleton {
    fun singletonTest() {
        println("singletonTest is called.")
    }
}

而呼叫單例類中的函式比較類似於Java中靜態方法的呼叫方式:

Singleton.singletonTest()

這種寫法雖然看上去像是靜態方法的呼叫,但其實Kotlin在背後自動幫我們建立了一個Singleton類的例項,並且保證全域性只會存在一個Singleton例項。

集合

集合的建立

使用如下程式碼可以初始化一個List集合:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")

使用如下程式碼可以初始化一個Set集合:

val set = setOf("Apple", "Banana", "Orange", "Pear", "Grape")

使用如下程式碼可以初始化一個Map集合

val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)

集合的取值

fun mutableList() {
    val list = mutableListOf("apple", "banana", "orange", "pear", "grape")
    list.add("other")
    for (fruit in list) {
        println(fruit)
    }
}

SetOf集合,去重的集合,底層是map 所以可以去重

fun setList() {
    val list = setOf("apple", "banana", "orange", "pear", "grape")

    for (fruit in list) {
        println(fruit)
    }
}

map 集合 java寫法:鍵值對一對一

fun mapList() {
    val map = HashMap<String, Int>()
    map.put("apple", 1)
    map.put("banana", 2)
}

kotlin 可以用[]

fun mapListKotlin() {
    val map = HashMap<String, Int>()
    map["apple"] = 2
    map["banana"] = 1
    val number = map["apple"]
}

最簡潔的寫法 mapOf不可變,mutableMapOf可變

fun mapListKotlin1() {
    val map = mapOf("apple" to 1, "banana" to 2, "orange" to 3)

    val map1 = mutableMapOf("apple" to 1, "banana" to 2, "orange" to 3)
    map1["watermelon"] = 6
    for ((fruit, number) in map1) {
        println("mutable" + fruit + number)
    }
}

Lambda程式設計

Lambda就是一小段可以作為引數傳遞的程式碼。正常情況下,我們向某個函式傳參時只能傳入變數,而藉助Lambda卻允許傳入一小段程式碼。

我們來看一下Lambda表示式的語法結構:

{引數名1: 引數型別, 引數名2: 引數型別 -> 函式體}

首先最外層是一對大括號,如果有引數傳入到Lambda表示式中的話,我們還需要宣告引數列表,引數列表的結尾使用一個->符號,表示引數列表的結束以及函式體的開始,函式體中可以編寫任意行程式碼,並且最後一行程式碼會自動作為Lambda表示式的返回值。

集合的函式式API

集合中的map函式是最常用的一種函式式API,它用於將集合中的每個元素都對映成一個另外的值,對映的規則在Lambda表示式中指定,最終生成一個新的集合。比如,這裡我們希望讓所有的水果名都變成大寫模式,就可以這樣寫:

fun main() {
    val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
    val newList = list.map({ fruit: String -> fruit.toUpperCase() })
    for (fruit in newList) {
        println(fruit)
    }
}
  • 當Lambda引數是函式的最後一個引數時,可以將Lambda表示式移到函式括號的外面。
  • 如果Lambda引數是函式的唯一一個引數的話,還可以將函式的括號省略。
  • 由於Kotlin擁有出色的型別推導機制,Lambda表示式中的引數列表其實在大多數情況下也不必宣告引數型別。
  • 當Lambda表示式的引數列表中只有一個引數時,也不必宣告引數名,而是可以使用it關鍵字來代替。

因此,Lambda表示式的寫法可以進一步簡化成如下方式:

val newList = list.map { it.toUpperCase() }

推導過程

fun lambda1() {
    val list = listOf("apple", "banana", "orange", "pear", "grape", "other")
    val max = list.maxByOrNull { it.length }
    println(max)
}

maxByOrNul實際上它一個普通函式,只不過它接受的是一個Lambda型別引數,每次集合遍歷時把遍歷的值作為引數傳遞給lambda表示式,工作原理就是找到最長值並返回

fun lambda2() {
    val list = listOf("apple", "banana", "orange", "pear", "grape", "other")
    val lambda = { fruit: String -> fruit.length }
    val max = list.maxByOrNull(lambda)
    println(max)
}

可直接將lambda表示式傳入maxByOrNull函式中

fun lambda3() {
    val list = listOf("apple", "banana", "orange", "pear", "grape", "other")

    val max = list.maxByOrNull({ fruit: String -> fruit.length })
    println(max)
}

Kotlin規定,當lambda引數是函式的最後一個引數時,可以將Lambda表示式移到函式括號外邊

fun lambda4() {
    val list = listOf("apple", "banana", "orange", "pear", "grape", "other")

    val max = list.maxByOrNull() { fruit: String -> fruit.length }
    println(max)
}

Kotlin規定,如果Lambda引數是函式的唯一一個引數的話,還可以講函式的括號省略

fun lambda5() {
    val list = listOf("apple", "banana", "orange", "pear", "grape", "other")

    val max = list.maxByOrNull { fruit: String -> fruit.length }
    println(max)
}

Kotlin有出色的類推導機制,大多數的情況下不必宣告引數的型別

fun lambda6() {
    val list = listOf("apple", "banana", "orange", "pear", "grape", "other")

    val max = list.maxByOrNull { fruit -> fruit.length }
    println(max)
}

Kotlin規定,當lambda表示式的引數列表中只有一個引數時,也不必宣告引數名,而是可以使用it關鍵字來代替

fun lambda7() {
    val list = listOf("apple", "banana", "orange", "pear", "grape", "other")

    val max = list.maxByOrNull { it.length }
    println(max)
}

其他集合函式API:

使用map函式轉換為大寫

fun lamdabMap() {
    val list = listOf("apple", "banana", "orange", "pear", "grape", "other")
    var newList = list.map { it.toUpperCase() }
}

filter函式過濾集合中的元素,這裡注意呼叫順序 先map 在filter 效率會低

fun lamdabMapFilter() {
    val list = listOf("apple", "banana", "orange", "pear", "grape", "other")
    var newList = list.filter { it.length <= 5 }.map { it.toUpperCase() }
}

any函式用於判斷集合中至少存在一個元素滿足條件

all函式判斷集合中所有元素滿足條件

這樣是不是比每次迴圈方便多了

fun lamdabAnyAll() {
    val list = listOf("apple", "banana", "orange", "pear", "grape", "other")
    val any = list.any { it.length <= 5 }
    val all = list.all { it.length <= 5 }
}

Java函式式API

如果我們在Kotlin程式碼中呼叫了一個Java方法,並且該方法接收一個Java單抽象方法介面引數,就可以使用函式式API。Java單抽象方法介面指的是介面中只有一個待實現方法,如果介面中有多個待實現方法,則無法使用函式式API。

舉個例子,Android中有一個極為常用的點選事件介面OnClickListener,其定義如下:

public interface OnClickListener {
     void onClick(View v);
}

可以看到,這是一個單抽象方法介面。假設現在我們擁有一個按鈕button的例項,就可以使用函式式API的寫法來註冊這個按鈕的點選事件:

button.setOnClickListener { v ->
}

推導過程

fun lmClass() {
    Thread(object : Runnable {
        override fun run() {
            println("Thead is run")
        }
    }).start()
}

Runnable只有一個待實現方法,這裡沒有顯示地重寫run方法kotlin自動明白runnable後面的lambda表示式就是在run中的實現內容

fun lmClass1() {
    Thread(Runnable {
        println("Thead is run")
    }).start()
}

java方法的引數列表不存在一個以上java單抽象方法介面引數,還可以將介面名省略

fun lmClass2() {
    Thread({
        println("Thead is run")
    }).start()
}

當lambda表示式是方法的最後一個引數時,可以將lambda表示式移到方法括號外面


fun lmClass3() {
    Thread() {
        println("Thead is run")
    }.start()
}

同時lambda表大會還是方法的唯一一個引數,還可以方法的括號省略


fun lmClass4() {
    Thread {
        println("Thead is run")
    }.start()
}

空指標檢查

空指標是一種不受程式語言檢查的執行時異常,只能由程式設計師主動通過邏輯判斷來避免,但即使是最出色的程式設計師,也不可能將所有潛在的空指標異常全部考慮到。

public void doStudy(Study study) {
    study.readBooks();
    study.doHomework();
}

這段Java程式碼安全嗎?不一定,因為這要取決於呼叫方傳入的引數是什麼,如果我們向doStudy()方法傳入了一個null引數,那麼毫無疑問這裡就會發生空指標異常。因此,更加穩妥的做法是在呼叫引數的方法之前先進行一個判空處理,如下所示:

public void doStudy(Study study) {
    if (study != null) {
        study.readBooks();
        study.doHomework();
    }
}

可空型別系統

Kotlin中引入了一個可空型別系統的概念,它利用編譯時判空檢查的機制幾乎杜絕了空指標異常。

fun doStudy(study: Study) {
    study.readBooks()
    study.doHomework()
}

這段程式碼看上去和剛才的Java版本並沒有什麼區別,但實際上它是沒有空指標風險的,因為Kotlin預設所有的引數和變數都不可為空,所以這裡傳入的Study引數也一定不會為空,可以放心地呼叫它的任何函式。

Kotlin提供了另外一套可為空的型別系統,就是在類名的後面加上一個問號。比如,Int表示不可為空的整型,而Int?就表示可為空的整型;String表示不可為空的字串,而String?就表示可為空的字串。

使用可為空的型別系統時,需要在編譯時期就把所有的空指標異常都處理掉才行。

判空輔助工具

Kotlin提供了一系列的輔助工具,使開發者能夠更輕鬆地進行判空處理。

?. 操作符表示當物件不為空時正常呼叫相應的方法,當物件為空時則什麼都不做。比如:

if (a != null) {
    a.doSomething()
}

這段程式碼使用?.操作符就可以簡化成:

a?.doSomething()

?: 操作符表示如果左邊表示式的結果不為空就返回左邊表示式的結果,否則就返回右邊表示式的結果。比如:

val c = if (a ! = null) {
    a
} else {
    b
}

這段程式碼的邏輯使用?:操作符就可以簡化成:

val c = a ?: b

kotlin並非總是那麼智慧如下

我們發現一個可空的全域性變數,在contextNull已經判斷為空卻報錯kotlin識別不了

!!:我們使用強制判斷不為空!!非空斷言工具

var content: String? = "hello"
fun contextNull() {
    if (content != null) {
        upCase()
    }
}
fun upCase() {
    val upcase = content!!.toUpperCase()
    println(upcase)
}

結合使用?.操作符和let函式也可以對多次重複呼叫的某個變數統一進行判空處理

fun doStudy(study: Study?) {
    study?.let {
        it.readBooks()
        it.doHomework()
    }
}

取文字長度的函式

fun getTextLength(text: String?): Int {
    if (text != null) {
        return text.length
    }
    return 0
}

簡寫為:由於text可為空?.判斷返回null藉助?:返回0

fun getTextLength1(text: String?) = text?.length ?: 0

函式預設值

Kotlin允許在定義函式的時候給任意引數設定一個預設值,這樣當呼叫此函式時就不會強制要求呼叫方為此引數傳值,在沒有傳值的情況下會自動使用引數的預設值。語法格式如下:

fun printParams(num: Int, str: String = "hello") {
    println("num is $num , str is $str")
}

這裡給printParams()函式的第二個引數設定了一個預設值,這樣當呼叫printParams()函式時,可以選擇給第二個引數傳值,也可以選擇不傳,在不傳的情況下就會自動使用預設值。

示例:

fun params(num: Int, str: String = "Hello") {
    println("num $num str $str")
}

fun params1(num: Int = 100, str: String) {
    println("num $num str $str")
}

fun params2(num: Int = 100, str: String = "Hello") {
    println("num $num str $str")
}

給建構函式傳入預設入參,可以預設不傳入引數


class Student(val sno: String = "", val grade: Int = 0, name: String = "", age: Int = 0) :
    Person3(name, age) {
    init {
        println("sno:$sno")
        println("grade:$grade")
    }

}

fun main() {
    params(123)
    //params1(123)
    params1(str = "wordla")
    params2(str = "11", num = 55)
    val s = Student()
}

相關文章