1. 程式人生 > 實用技巧 >Kotlin基礎學習

Kotlin基礎學習

寫在前面

在剛開學的時候,買了一本《第一行程式碼Android》,但一直在上課沒有機會看,這幾天剛好寫完了上一個專案,這段時間就對這本書進行了學習。在這本書中,由於谷歌大力推廣kotlin語言,將其作為android開發的首推語言,本書也遵循了谷歌的推廣,因此我就跟著書本學習了基本的Kotlin語法,當作記錄筆記了。

變數和函式

變數

在Koltin中的變數定義方式與java有很大不同,在kotlin中要定義一個變數,只允許在變數前宣告兩種關鍵字:val和var,val是value的簡寫,表示一個不可變的變數,對應java中的final關鍵字定義的變數。var是variable的簡寫,表示一個可變的變數,對應java中的非final關鍵字定義的變數。這時可能學過java的都會冒出一個疑惑,只靠這兩個怎麼能知道具體的型別呢?Kotlin裡有型別推導機制,如:

  val a = 37
  println("a = " + a)

這裡可以看出,Kotlin不需要行尾分號了,這裡我們將10賦值給了a,那麼a就會被推導成整型變數。如果你把一個字串賦值給a變數,那麼a就會被自動推導為字串變數。但需要注意的是,型別推導機制並不總是好用的,因此我們可以對一個變數進行顯式宣告,如:

val a:Int = 10

Kotlin裡摒棄了java中的int,double,float等,全部都改用了物件資料型別,即首字母大寫的型別,如Int,Double,Float。此時我們嘗試對a變數進行一個擴大的操作:

a = a * 10

可以看到編譯器報錯了,因為val型別的變數無法被重新賦值,我們把a的型別變成var就可以了:

var a : Int = 10

函式

首先要注意的是,方法和函式其實說的都是一個東西,只是叫法不同而已。接下來我們看看如何在kotlin中定義一個函式:

fun largerNumber(num1:Int,num2:Int):Int{
    return max(num1,num2)
}

fun表示function,即宣告函式,後面就是函式名了。函式名後的括號裡為變數引數列表,引數的宣告格式為:"引數名:引數型別"。引數後面的冒號內容可以省略,表示返回值型別,這裡我們宣告為返回一個Int型的資料。這裡使用了一個max函式,表示取二者的最大值。

這裡我們提一句語法糖,當返回值只有一句程式碼時候,我們可以將大括號省略,用等號連線,且return也可以省略。如下:

fun largerNumber(num1:Int,num2:Int):Int = max(num1,num2)

而我們剛才提到了kotlin的型別推導機制,返回值肯定是一個Int值,因此我們不需要顯式宣告返回值型別了。就可以寫成:

fun largerNumber(num1:Int,num2:Int) = max(num1,num2)

邏輯控制

if條件語句

Kotlin的if和java幾乎沒有任何區別,但需要注意的是,kotlin中的if條件語句是有返回值的,返回值為if語句每個條件中的最後一行程式碼的返回值。意味著我們可以這麼寫:

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

這裡又使用了上面提到的語法糖,因為這裡放到大括號裡也就是return 一句話,所以符合語法糖的規則。

when條件語句

kotlin中的when有點像java中的switch,但比起switch要強大的多。switch裡面只能傳入整型和短於整型的變數作為條件,JDK1.7後還引入了字串變數的支援。但如果我們傳入的型別不是這些,那麼switch語句就無法使用了。同時,switch語句的最後需要加一個break,否則會順序執行每個case。而kotlin不僅解決了以上的痛點,還添加了很多好用的功能。

比如我們先看一個簡單的通過姓名輸出學生分數的功能:

fun getScore(name:String) = if(name =="Tom"){
    86
}else if(name == "Jim"){
    77
}else if(name == "Jack"){
    95
}else if(name == "Lily"){
    100
}else{
    0
}

這裡又再次使用了單行程式碼函式的語法糖,但寫了這麼多if和else顯得程式碼十分冗餘,這裡我們改用when的寫法:

fun getScore(name: String) = when (name) {
    "Tom" -> 86
    "Jim" -> 77
    "Jack" -> 95
    "Lily" -> 100
    else -> 0
}

when語句可以傳入一個任意型別的變數,然後在when的結構體定義一系列的條件,格式為: 匹配值 -> {執行邏輯},當執行邏輯只有一行的時候,大括號就可以省略了。

除了精確匹配外,when還支援型別匹配,比如我們定義一個checkNumber()函式:

// when型別匹配
fun checkNumber(num: Number) {
    when (num) {
        is Int -> println("number is Int")
        is Double -> println("number is Double")
        else -> println("number not support")
    }
}

在上面的程式碼中,is相當於java中的instance of,而Number是kotlin中的一個抽象類,像Int,Float,Double等與數字有關的類都是他的子類,我們可以通過型別匹配來判斷是什麼型別。比如:

val num = 10.0
checkNumber(num)

就會輸出Double型別了。

需要注意的是,kotlin中判斷字串或者物件是否相等用 == 就可以了,不再需要呼叫equals方法。

迴圈語句

kotlin中有兩種迴圈,while和for迴圈,而while迴圈和java基本一模一樣,因此不再提了,而java中常用的for-i迴圈被kotlin捨棄,java中的另一種for-each迴圈則被大幅加強,變成了for-in迴圈。在學習迴圈前,要先學習區間的概念,這也是java種沒有的。如下程式碼:

val range = 0..10

這段程式碼的意思其實就是[0,10]的意思,有了區間,我們就可以用for-in來遍歷區間了:

 for (i in range){
     println(i)
}

但很多時候,我們一般不會用左右閉區間,而會使用左閉右開區間,kotlin也提供了until關鍵字來實現這個功能:

  for (i in 0 until 10){
    println(i)
  }

這時生成的就是[0,10)區間了。

預設情況下,會在區間範圍內遞增1,我們可以用step來遞增更多,如:

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

這時就是遞增2了,相當於i+=2的意思。

但需要注意的是,..和until都只能左邊小右邊大,也就是隻能建立升序區間,而建立降序區間可以用downTo,如:

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

downTo也可以使用step,不再多提了。

類與物件

基本用法

在面嚮物件語言中,類和物件是很重要的概念。kotlin自然也有這樣的概念。

我們先來通過IDE來定義一個類:

class Person() {
}

可以看到,kotlin也是使用class關鍵字來宣告類的,這點與java一致。我們定義幾個變數和函式:

class Person() {
    var name = ""
    var age = 0
    fun eat(){
        println(name + " is eating.He is " + age + " years old")
    }
}

這裡用var是因為我們要在建立物件後對其進行重新賦值,eat函式就不再解釋了,很簡單。

接下來我們把這個類例項化一下:

val p = Person()
p.name = "Jack"
p.age = 19
p.eat()

可以看到,與java不同的是,我們不再需要new關鍵字了,因為你呼叫一個類的構造方法只可能是為了對類進行例項化。接下來我們執行這段程式碼,發現結果和我們想的一致。

繼承與建構函式

面向物件的一個重要特性就是繼承,我們先定義一個Student類:

class Student {
}

要讓Student類繼承Person類,我們要幹兩件事:

  1. 使Person類可以被繼承。這話聽起來似乎很怪,java中所有類預設都是可以被繼承的,但Kotlin卻不是,在Kotlin中任何一個非抽象類預設都是不可以被繼承的,而抽象類如果不繼承就無任何意義,所以必須被繼承。想要讓一個類可以被繼承,其實很簡單,加上一個open關鍵字就可以了:

    open class Person() {
        var name = ""
        var age = 0
        fun eat(){
            println(name + " is eating.He is " + age + " years old")
        }
    }
    
  2. 第二件事,自然就是讓Student類繼承Person類了,如下寫法:

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

    繼承的寫法首先,不再需要extends關鍵字,加上冒號即可。之後如果仔細觀察,會發現多了一對括號。而要理解括號,就要解釋另一個概念——構造函數了。

在Kotlin中,有兩類建構函式,主建構函式與次建構函式,主建構函式只能有一個,且沒有函式體。直接定義在類名的後面。而次建構函式則是可以有任意多個,且有函式體。如下例子:

class Student(var sno:String,var grade:Int) 

這種就是典型的主建構函式定義了。如果我們要在主建構函式裡寫一些邏輯,就需要用到init結構體了:

init{
    println("sno is " + name)
    println("grade is " + age)
}

而根據繼承的特性,子類的建構函式必須呼叫父類的建構函式。這時我們要怎麼呼叫父類的主建構函式呢?自然是在繼承上寫了:

因此,如果我們再看上面的最開始的定義就會更加明瞭了。其實就是呼叫了父類的無參構造方法,就算是無參的也不能省略括號。如果我們對Person函式改造一下,將Person函式改成有參的構造方法:

open class Person(val name:String,val age:Int) {
}

此時的Student類要繼承Person類,就必須實現他的主建構函式,即必須傳入name和age兩個值。我們的Student類就可以寫成這樣:

class Student(val sno:String,val grade :Int, name: String , age :Int):Person(name,age){
}

這裡定義name和age時不能再宣告成val,因為在主建構函式中宣告的引數都會預設變成該類的欄位。這會導致與同名的name和age發生衝突。因此不用再加任何關鍵字。

至於次建構函式,當一個類有主建構函式和次建構函式時,所有的次建構函式必須呼叫主建構函式。具體的例子就不再寫了。

接下來我們看一個特殊情況,當一個類沒有顯式定義主建構函式而且定義了次建構函式時,它就是沒有主建構函式的。此時繼承Person類就不再需要加上括號了,因為沒有主建構函式。

介面

kotlin是繼承自java的語言,自然很多方面都很像了。在介面方面,與java有很多相似。我們先定義一個介面:

interface Study {
    fun readBooks()
    fun doHomework(){
        println("do homework default")

    }
}

這裡kotlin和java一樣,支援為介面定義預設函式體,使得實現類不需要實現這個方法。我們讓Student類實現這個介面:

class Student( name:String, age:Int) :Person(name,age),Study {
    override fun readBooks() {
        println(name + "is reading")
    }
}

這裡可以看到,kotlin中介面的關鍵字也是冒號,且只需要複寫方法即可。我們在main函式呼叫該方法即可。

這裡還需要提一下,函式的可見性修飾符。見下表:

修飾符 Java Kotlin
public 所有類可見 所有類可見(預設)
private 當前類可見 當前類可見
protected 當前類、子類、同一包路徑下的類可見 當前類、子類可見
default 同意包的路徑下的類可見(預設)
internal 同一模組中的類可見

這裡的模組概念暫時不深究。

資料類與單例類

在寫web時候,model是很常用的一種類。我們在kotlin中如何定義一個數據類呢?如下:

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

只需要加上data關鍵字,所有需要的方法就全部都實現了,如toString(),equals等等方法。

關於單例模式,也是我們很常見的一種設計模式。要使用單例類,如下:

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

只需要定義為object類即可。要呼叫裡面的方法,如下:

 Singleton.singletonTest()

即可。這樣看上去是靜態方法的呼叫,但其實kotlin在背後為我們自動建立了一個Singleton類的例項,並保證全域性只會存在一個Singleton例項。