Kotlin入門

這篇文章會列出我認(rèn)為入門需要掌握的特性,如果要想應(yīng)用到項目中去的話可以先去GitHub上找一些優(yōu)秀的Kotlin項目學(xué)習(xí)一下Kotlin的編程思想。

Kotlin中文站:里面有一些參考資料以及一些推薦書籍。

如何在IDE中查看Kotlin編譯過后的Java代碼

很多時候直接看下編譯后的Java代碼比看別人的解釋容易得多。
以Android Studio為例:Tools -> Kotlin -> Show Kotlin Bytecode -> 會出現(xiàn)一個小窗口,點擊Decompile

基本語法

基本語法可以看這個java-to-kotlin(這個很重要,一定要先看一下,內(nèi)容并不多),里面是關(guān)于Java與Kotlin語法上的不同,當(dāng)然只看這些是不夠的

Lambda 表達(dá)式

在Kotlin中大家基本上都會用lambda表達(dá)式去寫代碼,這里不建議直接去看Kotlin lambda表達(dá)式的語法,自己剛開始寫Demo的時候編譯器會給出一些警告,指出某些代碼可以被轉(zhuǎn)換成lambda表達(dá)式,然后讓編譯器去幫你轉(zhuǎn)換,有了這些基本印象后再去看語法會感覺比較友好,具體的語法這里就不說了,內(nèi)容還是比較多的,可以自行查找。

訪問修飾符

private :類內(nèi)部可見

protected:類內(nèi)部及其子類可見

internal:Module內(nèi)可見

public :全局可見

變量

  • var表示可變變量(variable),val表示不可變變量(value),也就是后者是被final修飾的
  • 變量類型不是必須的,編譯器可以自動識別,比如val name = "xiaoming"編譯器會自動識別為String類型
  • Kotlin是空安全的,對象默認(rèn)是不能為null的,如果想要賦值為null在聲明對象類型時需要加上?,且可空類型不能直接調(diào)用其方法,需要使用?.!!,如果為null前者會返回null后者會拋出空指針異常,具體如下:
var str1: String = "abc"
str1 = null // 編譯器會報錯
var str2: String? = "abc"
str2 = null // 沒問題

str1.length // 沒問題
str2.length // 編譯器會報錯

val out = str2?.length //沒問題
println(out) // 這里輸出null

str2!!.length // 這里運行時拋出空指針

if (str2 != null )  {
    str2.length // 沒問題
}
  • varval都是private的(就算顯式聲明了public也會被編譯成private),編譯器會自動生成getter/setter方法(val只有getter),在Kotlin中可以直接使用.來調(diào)用,編譯器會自己去調(diào)用它的getter/setter方法,如user.id = 1會去調(diào)用user對象的setId(1)
    如果不想讓成員變量被外部訪問,可以顯示聲明變量為private,如private val id = 1,這樣編譯器就不會生成getter/setter
    當(dāng)然getter/setter方法是可以被修改的,操作如下:
class User{
    var id = 0
        get() = field - 1
        set(value) {
            field = value + 1
        }
}

其中field被稱為”幕后字段“指的就是當(dāng)前變量,像下面這種寫法是錯誤的,因為id = value + 1會繼續(xù)調(diào)用idsetId(value + 1)方法,就會產(chǎn)生死循環(huán)。

class User{
    var id = 0
        set(value) {
            id = value + 1
        }
}

字符串模板

在Kotlin中不需要使用+來拼接字符串,一個字符串中可以通過$來獲取變量值,如下

    val name = "xiaoming"
    println("name is $name") // 獲取變量值
    println("length is ${name.length}") // 使用表達(dá)式
    println("${'$'}29.18") // 打印 $ 符號

上面的代碼分別會輸出

name is xiaoming

length is 8

$29.18

函數(shù)

  • 所有函數(shù)默認(rèn)是public
  • 所有函數(shù)默認(rèn)是final的(抽象函數(shù)和接口函數(shù)除外),想要不final需要加上open關(guān)鍵字修飾
    // 可以被重寫
    open fun canOverride() {}

    // 無法被重寫
    fun cannotOverride() {}
  • 重寫父類函數(shù)需要在函數(shù)聲明前加上overrider關(guān)鍵字
    override fun toString() = "kotlin"
  • 如果函數(shù)只有一個語句可以直接用賦值的形式
    fun plus(x: Int,y: Int) : Int = x + y // 此處返回類型可以省略
    fun sout(s: String) = print(s)
  • Kotlin中函數(shù)的參數(shù)是可以有默認(rèn)值的,這樣就不用寫重載函數(shù)了
    fun userInfo(id: Int, name: String = "xiaoming", sex: String = "male") {
        println("id: $id, name: $name, sex: $sex")
    }

    fun test() {
        userInfo(1) // 輸出:id: 1, name: xiaoming, sex: male
        userInfo(2, "xiaohong") // 輸出:id: 2, name: xiaohong, sex: male
        userInfo(1, sex = "female") // 輸出:id: 1, name: xiaoming, sex: female
    }

如果想要提供給Java使用可以加上@JvmOverloads,這樣編譯成Java代碼時就會生成對應(yīng)重載函數(shù)

    @JvmOverloads
    fun userInfo(id: Int, name: String = "xiaoming", sex: String = "male") {
        println("id: $id, name: $name, sex: $sex")
    }
  • 無返回類型的函數(shù)會返回一個Unit對象,Unit可以理解成Java的Void,可以作為泛型對象,如果將Unit類型的函數(shù)賦值給一個變量,編譯器會給變量賦值一個Unit單例
    val a = test()

編譯為Java之后就是這樣的

    Unit a = Unit.INSTANCE;

Unit直接繼承于Any類(即Java中的Object類),只重寫了toString方法

    override fun toString() = "kotlin.Unit"

嵌套函數(shù)

在Kotlin中函數(shù)是可以嵌套的,內(nèi)部函數(shù)可以訪問到外部函數(shù)的局部變量,且其本身只能被外部函數(shù)訪問。

    fun outFun(name: String) {
        fun nestFun() {
            print(name)
        }
        nestFun()
    }

擴(kuò)展函數(shù)

這是Kotlin的一個非常好用的特性,它可以為一個類添加函數(shù),在這個函數(shù)中可以訪問到對象的公有屬性和方法,聲明完擴(kuò)展函數(shù)之后,該類及其子類的對象就可以直接通過.來調(diào)用這個函數(shù),比如為CharSequence類添加一個toList方法,將字符逐個添加到一個列表中然后返回

fun CharSequence.toList(): List<Char> {
    val list = ArrayList<Char>()
    for (char in this) {
        list.add(char)
    }
    return list
}

聲明完上面這段代碼之后,就可以直接調(diào)用了

    val s = "asdf"
    val list = s.toList()

除了擴(kuò)展函數(shù)之外,Kotlin還支持?jǐn)U展屬性,但是擴(kuò)展屬性不能直接賦值,只能設(shè)置它的getter/setter方法,實際上編譯成Java代碼后還是擴(kuò)展了兩個方法

var StringBuilder.lastChar: Char
    get() = get(length - 1)
    set(value) = setCharAt(length - 1, value)

高階函數(shù)

在Kotlin中函數(shù)可以作為參數(shù)傳入到另一個函數(shù)中

    fun run(block: () -> Unit) {
        block()
    }

上面就是一個簡單的高階函數(shù),可以這樣使用,運行之后就會輸出run,這里用到了Lambda 表達(dá)式,不懂的可以再去看下

    run {
        print("run")
    }

通過::來獲取某個對象的方法來傳入(::也可以用來獲取變量)

    val runnable = Runnable {
        print("run")
    }
    run(runnable::run)

內(nèi)聯(lián)函數(shù)

通過inline修飾的函數(shù)為內(nèi)聯(lián)函數(shù)

當(dāng)我們使用高階函數(shù)時,傳入的函數(shù)對象會被編譯成一個對象,然后再調(diào)用該對象的方法,這樣會增加內(nèi)存和性能的開銷,而如果使用內(nèi)聯(lián)函數(shù)的話就可以將方法的調(diào)用轉(zhuǎn)換為語句的調(diào)用

    fun run(block: () -> Unit) {
        block()
    }

    fun testRun() {
        run {
            print("run")
        }
    }

上面的代碼編譯成Java代碼大致是這樣的,可以看到在testRun方法中,直接被編譯成了語句調(diào)用

   public final void run(@NotNull Function0 block) {
      block.invoke();
   }

   public final void testRun() {
      String var = "run";
      System.out.print(var);
   }

使用內(nèi)聯(lián)函數(shù)可以避免產(chǎn)生多余的對象,但是會增加編譯后代碼量,所以要避免內(nèi)聯(lián)代碼塊過大的函數(shù),如果一個函數(shù)中有包含多個函數(shù)參數(shù),可以通過noinline關(guān)鍵字來避免內(nèi)聯(lián)

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {}

  • 所有類都繼承于Any類,相當(dāng)于Java中的Object
  • 類與函數(shù)一樣默認(rèn)都是public final的,想要類可以被繼承同樣需要用open關(guān)鍵字來修飾
  • 類的繼承與接口的實現(xiàn)都是通過:來表示的,中間由,隔開且沒有順序要求
open class A()
interface B
class C : A(), B
  • 類的構(gòu)造函數(shù)在init代碼塊中實現(xiàn),如果不需要對構(gòu)造函數(shù)添加訪問修飾符或者注解,那么constructor關(guān)鍵字可以省略
class Test constructor(a:Int) {
    init {
        println("Here is the constructor.")
        println("value a= $a")
    }
}

如果只想通過構(gòu)造函數(shù)給成員變量賦值的話可以直接這樣

class Test(val a:Int){
    ...
}

如果想讓構(gòu)造函數(shù)私有可以這樣

class Test private constructor() {
    ...
}
  • 如果需要多個構(gòu)造函數(shù)可以在類的內(nèi)部使用constructor關(guān)鍵字來聲明,這在Kotlin中叫做次構(gòu)造函數(shù),而聲明在類名上的叫做主構(gòu)造函數(shù),次構(gòu)造函數(shù)需要直接或間接的繼承主構(gòu)造函數(shù),具體可以看這里
class Test() {
    init {
        ...
    }
    constructor(a:Int): this() {
        ...
    }
}
  • Kotlin中有兩種class類,一種就是Java中Class,另外一種是Kotlin自由的KClass,它們的獲取方式也不一樣
    val kClazz = Person::class
    val clazz = Person::class.java

嵌套類

嵌套類編譯成Java代碼之后就是靜態(tài)內(nèi)部類

class Out {
    ...
    class Nest {
       ... 
    }
}
val nestClass = Out.Nest()

內(nèi)部類

Kotlin中內(nèi)部類需要使用inner關(guān)鍵字修飾

class Out {
    ...
    inner class Inner {
       ...
    }
}
val innerClass = Out().Inner()

object關(guān)鍵字

object關(guān)鍵字可以簡單地創(chuàng)建一個單例類,其中的變量和方法都可以直接通過類名來調(diào)用

fun main(args: Array<String>) {
    val string = Singleton.str;
    Singleton.printMessage(string)
}

object Singleton {
    val str = "Singleton"
    fun printMessage(message: String) = println("$str $message")
}

可以用來寫工具類

object StringUtils {
    fun isEmpty(str: String?) = str == null || str == ""
}

伴生對象

Kotlin中并沒有static關(guān)鍵字,如果需要靜態(tài)變量或是靜態(tài)方法的話就要使用伴生對象,使用companion object來聲明,伴生對象的命名可以省略,編譯器會使用默認(rèn)的命名Companion,一個類只能擁有一個伴生對象

fun main(args: Array<String>) {
    val test = Test.create()
    println(Test.TAG)
}

class Test {
    companion object {
        val TAG = "KotlinTest"
        fun create() = Test()
    }
}

上面的代碼會將TAG編譯成Test類的靜態(tài)成員變量,而create()方法其實不是一個真正的靜態(tài)方法,它是屬于伴生對象類的一個普通的public成員方法,伴生對象實際上是外部類的一個靜態(tài)單例內(nèi)部類,雖然可以直接通過Test類來調(diào)用create(),但其實編譯過后是這樣的

    // kotlin code
    val test = Test.create()

    // java code
    Test test = Test.Companion.create();

如果想要生成一個真正的靜態(tài)方法,可以使用@JvmStatic注解來實現(xiàn)
下面使用objectcompanion object寫一個延時加載的單例類

class Singleton private constructor() {
    companion object {
        fun getInstance() = Holder.instance
    }
    object Holder {
        val instance = Singleton()
    }
}

數(shù)據(jù)類

數(shù)據(jù)類類似于lombok的@Data注解,可以自動生成toString()equals(),hashcode()copy()等方法,具體可以去看一下編譯成的Java代碼

data class User(val id:Int, val name:String, val age:Int)

除了常用的getter/setter之外還可以這樣用

    val user1 = User(1, "Ben", 25)
    val user2 = user1.copy(id= 2, age = 23)
    val (id, name, age) = user1

第二行代碼將user1拷貝給了user2并修改了idage,第三行代碼將user1的三個數(shù)據(jù)分別賦值給了id,name,age三個變量,這個被稱為解構(gòu),之所以可以這樣寫是因為數(shù)據(jù)類還實現(xiàn)了componentN(),后面在運算符重載會講到

密封類

密封類本身是個抽象類,主要特點是它的構(gòu)造方法是私有的,直接繼承它的子類只能定義在密封類所在的文件中,無法定義在別的文件中,也就是說它限制了外部繼承,直接子類就是確定的那幾個,所以密封類也可以理解為功能更多的枚舉類,因為它可以有更多的屬性以及方法

sealed class Person(val name: String, var age: Int) {
    class Male(name: String, age: Int) : Person(name, age)
    class Female(name: String, age: Int) : Person(name, age)
}

Kotlin也對密封類使用when語句也做了優(yōu)化,可以不寫else,因為子類是確定的那幾個

fun test(person: Person) {
    when (person) {
        is Person.Male -> println("male")
        is Person.Female -> println("female")
    }
}

操作符重載

Kotlin中的各種操作符都對應(yīng)著一種方法,比如+-,*/分別對應(yīng)著plus,minus,timesdiv,這些方法都是可以被重載的,編寫時需要在方法前面加上operate來修飾

fun main(args: Array<String>) {
    val a = Point(1, 4)
    val b = Point(2, 3)
    val c = a + b
    c.printPoint()
}

class Point(val x: Int, val y: Int) {
    operator fun plus(another: Point): Point {
        return Point(x + another.x, y + another.y)
    }

    fun printPoint() {
        println("x= $x, y= $y")
    }
}

上面的代碼運行之后會輸出"x= 3, y= 7",此外還有很多操作符,這里就不列舉了,前面說到的componentN()其實也是操作符對應(yīng)的方法,val (id, name, age) = user編譯之后就是

      int id = user.component1();
      String name = user.component2();
      int age = user.component3();

常量

使用const val來聲明一個常量,常量只能在object,companion object或是類的外部聲明,且只能是基本數(shù)據(jù)類型或是String類型,因為常量要求在編譯期就能確定它的值

const val pi = 3.14

object A {
    const val pi = 3.14
}

class B {
    companion object {
        const val pi = 3.14
    }
}

前面說過val是不可變變量,它和常量的區(qū)別是const val編譯之后是public的而valpirvate的,訪問val只能通過它的getter方法,而getter方法又是可以修改的(如下),對于開發(fā)者來說,常量應(yīng)該是一個確定的值,所以val不是常量

object A {
    val pi = 3.14
        get() = field + Math.random()
}

其他

下面的這些還是屬于Kotlin入門的范疇,并不是不重要,只是內(nèi)容較多,就不詳細(xì)講了,網(wǎng)上相關(guān)的文章也有很多

  • Kotlin標(biāo)準(zhǔn)庫
    Kotlin標(biāo)準(zhǔn)庫提供了一些好用的函數(shù),可以看下那些函數(shù)的實現(xiàn),對學(xué)習(xí)Kotlin也有很大幫助
  • 委托/委托屬性
    Kotlin語言是原生支持委托的,其中還包括了委托屬性
  • 泛型
    Kotlin的泛型和Java的泛型大致上是相同的,但是寫法上還是有點區(qū)別的,而且Kotlin可以使用inlinereified來支持真泛型
  • 協(xié)程
    Kotlin是有協(xié)程庫來支持協(xié)程的,Kotlin的協(xié)程可以理解為和線程池類似的線程框架,但是使用起來更加方便
  • 集合操作符
    Kotlin為集合提供了一系列操作符(實際上是集合的擴(kuò)展函數(shù)),類似于RxJava,可以鏈?zhǔn)秸{(diào)用
  • 與Java交互
    一般使用Kotlin開發(fā)避免不了與Java交互,對于調(diào)用Java代碼或是讓Java調(diào)用Kotlin代碼都可能會存在一些問題
  • Anko、ktx
    Anko和ktx是為android設(shè)計的一個kotlin代碼庫,對于android開發(fā)可以了解一下
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Kotlin的優(yōu)勢 代碼簡潔高效、強(qiáng)大的when語法,不用寫分號結(jié)尾,findViewById光榮退休,空指針安全...
    Windy_816閱讀 1,400評論 1 6
  • 【Kotlin 入門】 本文介紹了Kotlin入門應(yīng)該知道一些基本語法概念。包括變量、常量、函數(shù)、空安全、類定義、...
    Rtia閱讀 511評論 0 3
  • Kotlin 時間線 2011年7月,JetBrains 推出 Kotlin 項目 2012年2月,JetBrai...
    灰灰鴿閱讀 542評論 0 2
  • 什么是kotlin? Kotlin 是一個用于現(xiàn)代多平臺應(yīng)用的靜態(tài)編程語言 [1],由JetBrains開發(fā)。 K...
    Aimee的抱抱閱讀 2,111評論 0 2
  • 阿里巴巴口碑Android開發(fā)內(nèi)推1.WebRTC 全稱 Web Real-Time Communication。...
    jeffiano閱讀 2,819評論 0 0

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