Kotlin入門

這篇文章會列出我認爲入門須要掌握的特性,若是要想應用到項目中去的話能夠先去GitHub上找一些優秀的Kotlin項目學習一下Kotlin的編程思想。html

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

如何在IDE中查看Kotlin編譯事後的Java代碼

不少時候直接看下編譯後的Java代碼比看別人的解釋容易得多。 以Android Studio爲例:Tools -> Kotlin -> Show Kotlin Bytecode -> 會出現一個小窗口,點擊Decompileandroid

基本語法

基本語法能夠看這個java-to-kotlin(這個很重要,必定要先看一下,內容並很少),裏面是關於Java與Kotlin語法上的不一樣,固然只看這些是不夠的git

Lambda 表達式

在Kotlin中你們基本上都會用lambda表達式去寫代碼,這裏不建議直接去看Kotlin lambda表達式的語法,本身剛開始寫Demo的時候編譯器會給出一些警告,指出某些代碼能夠被轉換成lambda表達式,而後讓編譯器去幫你轉換,有了這些基本印象後再去看語法會感受比較友好,具體的語法這裏就不說了,內容仍是比較多的,能夠自行查找。github

訪問修飾符

private :類內部可見編程

protected:類內部及其子類可見安全

internal:Module內可見bash

public :全局可見dom

變量

  • var表示可變變量(variable),val表示不可變變量(value),也就是後者是被final修飾的
  • 變量類型不是必須的,編譯器能夠自動識別,好比val name = "xiaoming"編譯器會自動識別爲String類型
  • Kotlin是空安全的,對象默認是不能爲null的,若是想要賦值爲null在聲明對象類型時須要加上?,且可空類型不能直接調用其方法,須要使用?.!!,若是爲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中能夠直接使用.來調用,編譯器會本身去調用它的getter/setter方法,如user.id = 1會去調用user對象的setId(1) 若是不想讓成員變量被外部訪問,能夠顯示聲明變量爲private,如private val id = 1,這樣編譯器就不會生成getter/setter 固然getter/setter方法是能夠被修改的,操做以下:
class User{
    var id = 0
        get() = field - 1
        set(value) {
            field = value + 1
        }
}
複製代碼

其中field被稱爲」幕後字段「指的就是當前變量,像下面這種寫法是錯誤的,由於id = value + 1會繼續調用idsetId(value + 1)方法,就會產生死循環。ide

class User{
    var id = 0
        set(value) {
            id = value + 1
        }
}
複製代碼

字符串模板

在Kotlin中不須要使用+來拼接字符串,一個字符串中能夠經過$來獲取變量值,以下

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

上面的代碼分別會輸出

name is xiaoming

length is 8

$29.18

函數

  • 全部函數默認是public
  • 全部函數默認是final的(抽象函數和接口函數除外),想要不final須要加上open關鍵字修飾
// 能夠被重寫
    open fun canOverride() {}

    // 沒法被重寫
    fun cannotOverride() {}
複製代碼
  • 重寫父類函數須要在函數聲明前加上overrider關鍵字
override fun toString() = "kotlin"
複製代碼
  • 若是函數只有一個語句能夠直接用賦值的形式
fun plus(x: Int,y: Int) : Int = x + y // 此處返回類型能夠省略
    fun sout(s: String) = print(s)
複製代碼
  • Kotlin中函數的參數是能夠有默認值的,這樣就不用寫重載函數了
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代碼時就會生成對應重載函數

@JvmOverloads
    fun userInfo(id: Int, name: String = "xiaoming", sex: String = "male") {
        println("id: $id, name: $name, sex: $sex")
    }
複製代碼
  • 無返回類型的函數會返回一個Unit對象,Unit能夠理解成Java的Void,能夠做爲泛型對象,若是將Unit類型的函數賦值給一個變量,編譯器會給變量賦值一個Unit單例
val a = test()
複製代碼

編譯爲Java以後就是這樣的

Unit a = Unit.INSTANCE;
複製代碼

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

override fun toString() = "kotlin.Unit"
複製代碼

嵌套函數

在Kotlin中函數是能夠嵌套的,內部函數能夠訪問到外部函數的局部變量,且其自己只能被外部函數訪問。

fun outFun(name: String) {
        fun nestFun() {
            print(name)
        }
        nestFun()
    }
複製代碼

擴展函數

這是Kotlin的一個很是好用的特性,它能夠爲一個類添加函數,在這個函數中能夠訪問到對象的公有屬性和方法,聲明完擴展函數以後,該類及其子類的對象就能夠直接經過.來調用這個函數,好比爲CharSequence類添加一個toList方法,將字符逐個添加到一個列表中而後返回

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

複製代碼

聲明完上面這段代碼以後,就能夠直接調用了

val s = "asdf"
    val list = s.toList()
複製代碼

除了擴展函數以外,Kotlin還支持擴展屬性,可是擴展屬性不能直接賦值,只能設置它的getter/setter方法,實際上編譯成Java代碼後仍是擴展了兩個方法

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

高階函數

在Kotlin中函數能夠做爲參數傳入到另外一個函數中

fun run(block: () -> Unit) {
        block()
    }
複製代碼

上面就是一個簡單的高階函數,能夠這樣使用,運行以後就會輸出run,這裏用到了Lambda 表達式,不懂的能夠再去看下

run {
        print("run")
    }
複製代碼

經過::來獲取某個對象的方法來傳入(::也能夠用來獲取變量)

val runnable = Runnable {
        print("run")
    }
    run(runnable::run)
複製代碼

內聯函數

經過inline修飾的函數爲內聯函數

當咱們使用高階函數時,傳入的函數對象會被編譯成一個對象,而後再調用該對象的方法,這樣會增長內存和性能的開銷,而若是使用內聯函數的話就能夠將方法的調用轉換爲語句的調用

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

    fun testRun() {
        run {
            print("run")
        }
    }
複製代碼

上面的代碼編譯成Java代碼大體是這樣的,能夠看到在testRun方法中,直接被編譯成了語句調用

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

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

使用內聯函數能夠避免產生多餘的對象,可是會增長編譯後代碼量,因此要避免內聯代碼塊過大的函數,若是一個函數中有包含多個函數參數,能夠經過noinline關鍵字來避免內聯

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {}
複製代碼

  • 全部類都繼承於Any類,至關於Java中的Object
  • 類與函數同樣默認都是public final的,想要類能夠被繼承一樣須要用open關鍵字來修飾
  • 類的繼承與接口的實現都是經過:來表示的,中間由,隔開且沒有順序要求
open class A()
interface B
class C : A(), B
複製代碼
  • 類的構造函數在init代碼塊中實現,若是不須要對構造函數添加訪問修飾符或者註解,那麼constructor關鍵字能夠省略
class Test constructor(a:Int) {
    init {
        println("Here is the constructor.")
        println("value a= $a")
    }
}
複製代碼

若是隻想經過構造函數給成員變量賦值的話能夠直接這樣

class Test(val a:Int){
    ...
}
複製代碼

若是想讓構造函數私有能夠這樣

class Test private constructor() {
    ...
}
複製代碼
  • 若是須要多個構造函數能夠在類的內部使用constructor關鍵字來聲明,這在Kotlin中叫作次構造函數,而聲明在類名上的叫作主構造函數,次構造函數須要直接或間接的繼承主構造函數,具體能夠看這裏
class Test() {
    init {
        ...
    }
    constructor(a:Int): this() {
        ...
    }
}
複製代碼
  • Kotlin中有兩種class類,一種就是Java中Class,另一種是Kotlin自由的KClass,它們的獲取方式也不同
val kClazz = Person::class
    val clazz = Person::class.java
複製代碼

嵌套類

嵌套類編譯成Java代碼以後就是靜態內部類

class Out {
    ...
    class Nest {
       ... 
    }
}
val nestClass = Out.Nest()
複製代碼

內部類

Kotlin中內部類須要使用inner關鍵字修飾

class Out {
    ...
    inner class Inner {
       ...
    }
}
val innerClass = Out().Inner()
複製代碼

object關鍵字

object關鍵字能夠簡單地建立一個單例類,其中的變量和方法均可以直接經過類名來調用

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關鍵字,若是須要靜態變量或是靜態方法的話就要使用伴生對象,使用companion object來聲明,伴生對象的命名能夠省略,編譯器會使用默認的命名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類的靜態成員變量,而create()方法其實不是一個真正的靜態方法,它是屬於伴生對象類的一個普通的public成員方法,伴生對象其實是外部類的一個靜態單例內部類,雖然能夠直接經過Test類來調用create(),但其實編譯事後是這樣的

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

    // java code
    Test test = Test.Companion.create();
複製代碼

若是想要生成一個真正的靜態方法,可使用@JvmStatic註解來實現 下面使用objectcompanion object寫一個延時加載的單例類

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

數據類

數據類相似於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的三個數據分別賦值給了idnameage三個變量,這個被稱爲解構,之因此能夠這樣寫是由於數據類還實現了componentN(),後面在運算符重載會講到

密封類

密封類自己是個抽象類,主要特色是它的構造方法是私有的,直接繼承它的子類只能定義在密封類所在的文件中,沒法定義在別的文件中,也就是說它限制了外部繼承,直接子類就是肯定的那幾個,因此密封類也能夠理解爲功能更多的枚舉類,由於它能夠有更多的屬性以及方法

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語句也作了優化,能夠不寫else,由於子類是肯定的那幾個

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

操做符重載

Kotlin中的各類操做符都對應着一種方法,好比+-*/分別對應着plusminustimesdiv,這些方法都是能夠被重載的,編寫時須要在方法前面加上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()其實也是操做符對應的方法,val (id, name, age) = user編譯以後就是

int id = user.component1();
      String name = user.component2();
      int age = user.component3();
複製代碼

常量

使用const val來聲明一個常量,常量只能在objectcompanion object或是類的外部聲明,且只能是基本數據類型或是String類型,由於常量要求在編譯期就能肯定它的值

const val pi = 3.14

object A {
    const val pi = 3.14
}

class B {
    companion object {
        const val pi = 3.14
    }
}
複製代碼

前面說過val是不可變變量,它和常量的區別是const val編譯以後是public的而valpirvate的,訪問val只能經過它的getter方法,而getter方法又是能夠修改的(以下),對於開發者來講,常量應該是一個肯定的值,因此val不是常量

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

其餘

下面的這些仍是屬於Kotlin入門的範疇,並非不重要,只是內容較多,就不詳細講了,網上相關的文章也有不少

  • Kotlin標準庫

    Kotlin標準庫提供了一些好用的函數,能夠看下那些函數的實現,對學習Kotlin也有很大幫助

  • 委託/委託屬性

    Kotlin語言是原生支持委託的,其中還包括了委託屬性

  • 泛型

    Kotlin的泛型和Java的泛型大體上是相同的,可是寫法上仍是有點區別的,並且Kotlin可使用inlinereified來支持真泛型

  • 協程

    Kotlin是有協程庫來支持協程的,協程能夠理解爲運行在線程中的線程,比線程更輕量,使用協程必定程度上能夠簡化代碼

  • 集合操做符

    Kotlin爲集合提供了一系列操做符(其實是集合的擴展函數),相似於RxJava,能夠鏈式調用

  • 與Java交互

    通常使用Kotlin開發避免不了與Java交互,對於調用Java代碼或是讓Java調用Kotlin代碼均可能會存在一些問題

  • Ankoktx

    Anko和ktx是爲android設計的一個kotlin代碼庫,對於android開發能夠了解一下

相關文章
相關標籤/搜索