Kotlin 自學筆記

下位子發表於2017-12-23

專案未來可能需要使用kotlin開發,所以特此記錄一下學習筆記,僅供參考,方便後期查詢。已同步到GitHub上:KotlinTest

Kotlin 簡介

kotlin 的目標是成為一門全棧語言,主要有以下的特點:

  • 已經成為Android的官方推薦語言
  • 百分百的和java相容,兩者可以相互轉換
  • JSJVMNative多平臺開發

資料型別

1. 基本型別

Boolean true/false
Double 64
Float  32
Long   64
Int    32
Short  32
Byte   8

val aChar = '0'
val bChar = '我'
val cChar = '\u000f'
複製程式碼

Char型別的轉義字元

\t          製表符
\b          游標後退一個字元
\n          回車
\r          游標回到行首
\'          單引號
\"          雙引號
\\          反斜槓
\$          美元符號,Kotlin 支援美元符號開頭的字串模板
複製程式碼

2. 基本型別的轉換

不可隱式轉換

val anInt: Int = 5
val aLong: Long = anInt.toLong()
複製程式碼

必須得通過.to型別的方式進行資料的轉換

字串

  • 一串Char

  • 用雙引號""引起來

    val aString: String = "Hello World!"

  • 字串比較

    a == b 表示比較內容 類似 Java 中的 equals a === b 表示比較物件是否相同

  • 字串模板

    println("hello, $name") -> "hello, 小明"

3. Koltin 中的類和物件初始化

類的定義

  • 類,一個抽象的概念
  • 具有某些特徵的事物的概括
  • 不特定指代任何一個具體的事物

一般寫法:

/**
* 其中類引數如果加上 var 修飾,那麼他便是成員變數,反之則是普通的引數
*/
class Student(var name: String, var age: Int){
    init {
        // ... 相當於建構函式中的程式碼
    }
}
複製程式碼

物件

  • 是一個具體的概念,與類相對

  • 描述某一個類的具體個體

  • 舉例:

    某些人、領導的車等等

類和物件的關係

  • 一個類通常可以有很多歌具體的物件
  • 一個物件本質上只能從屬一個類
  • 某一個人,他是工程師,但本質上還是屬於人這一類

一般寫法:

val student: Student = Student("xiaweizi", 23)
複製程式碼

類的繼承

  • 提取多個類的共性得到一個更為抽象的類,即父類
  • 子類擁有父類的一切特徵
  • 子類也可以定義自己的特徵
  • 所有的類最終繼承自Any,類似於java中的Object

4. 空型別和智慧轉換

空型別

// 定義
val notNull: String = null // 錯誤,不可能為空
val nullanle: String? = null // 正確,可以為空
// 使用
notNull.length // 正確,不可能為空所以可以直接使用
nullable.length // 有可能為空,不能直接獲取長度
// 要想獲取長度,可以通過以下兩者方式
nullable!!.length // 正確,強制認定 nullable 不可能為空,如果為空則會丟擲空指標異常
nullable?.length // 正確,若 nullable 為空,則返回 null
複製程式碼

智慧型別轉換

val child: Child = parent as Child // 類似於 Java 的型別轉換,失敗則丟擲異常
val child: Child = parent as? Child // 如果轉換失敗,返回 null
複製程式碼

編譯器智慧識別轉換:

val parent: Parent = Child()
if (parent is Child) {
    // parent 直接呼叫子類方法,不需要再進行強制轉換
}

val string: String = null
if (string != null) {
    // string.length 可以直接呼叫length 方法
}
複製程式碼

5. 區間

一個數學上的概念,表示範圍, ClosedRange的子類,IntRange最常用

基本用法:

0..100 --> [0, 100]
0 until 100 --> [0, 100)
i in 0..100 表示 i 是否在區間[0, 100]中
複製程式碼

6. 陣列

基本寫法:

val ints: IntArray = IntArrayOf(1,2,3,5)
var charArray: CharArray = charArrayOf('a', 'b', 'c', 'd', 'e')
var stringArray: Array<String> = arrayOf("aa", "bb", "cc", "dd", "e")
複製程式碼

基本操作:

print(charArray[index])
ints[0] = 2
ints.length
cahrArray.joinToString("") // 講 char 陣列轉換成字串
stringArray.slice(1..4) // 取出區間裡的值
複製程式碼

程式結構

1. 常亮和變數

常量

val a = 2
類似 Java 中的 final
不可被重複賦值
執行時常量:val x = getX()
編譯期常量:const val x = 2
複製程式碼

變數

var a = 2
a = 3 // 可以被再次賦值
複製程式碼

型別推導

val string = "Hello" // 推匯出 String 型別
val int = 5 // 推匯出 Int 型別
var x = getString() + 5 // String 型別
複製程式碼

2. 函式 Function

以特定功能組織起來的程式碼塊

// 最簡單的列印資訊,無返回的方法
fun printMessage(message: String):Unit{
    println("$message")
}
// 擁有返回值得方法
fun sum(first: Int, second: Int):Int {
    return first + second
}
// 可以簡化成:
fun sum(first: Int, second: Int) = first + second
// 或者更簡單的匿名函式
val result = fun(first: Int, second: Int) = first + second
複製程式碼

3. Lambda 表示式

其實又是匿名函式

一般形式

{傳入引數 -> 函式體,最後一行是返回值}
// 例如
val sum = {first: Int, second: Int -> first + second}
val printMessage = {message: String -> println(message)}
複製程式碼

型別標識

() -> Unit // 無參,返回值為 null
(Int) -> Int // 傳入整型,返回一個整型
(String, (String) -> String) -> Boolean // 傳入字串、Lambda 表示式,返回Boolean
複製程式碼

Lambda 表示式的簡化

  • 函式引數呼叫時最後一個Lambda可以移出去
  • 函式引數只有一個Lambda,呼叫時小括號可以省略
  • Lambda只有一個引數可預設為it
  • 入參、返回值與形參一致的函式可以用函式引用方式作為實參傳入

4. 成員變數和成員方法

成員變數的宣告

// 第一種是在建構函式中宣告
class Student(var age: Int, name: String){
    // age 是成員變數 name 是區域性變數
}
// 第二種是在函式體內宣告
var a = 0
    get() {
        field += 1
        return field
    }
    set(value) {
        println("set)
        field = value + 1
    }
// 可以進行對 get 和 set 方法的重新定義

// 屬性的初始化儘量在構造方法中完成
// var 用 lateinit 延遲初始化, val 用 lazy

lateinit var sex: String
val person: Person by lazy {
    Person()
}
複製程式碼

成員方法

在類中直接宣告方法可以直接呼叫,包括lambda表示式

// 方法的宣告
fun sum(a: Int, b: Int) = a + b
val sum1 = {a: Int, b: Int -> a + b}
// 方法的呼叫
println(person.sum(1,2))
println(person.sum1(3,5))
複製程式碼

5. 運算子

java中運算子是不能重新定義過載的,只能按照原先的邏輯進行計算

Kotlin則可以重新定義運算子,使用operator關鍵字,舉了例子:

// 定義一個用於計算複數的類
class Complex(var real: Double, var imaginary: Double) {
    operator fun plus(other: Complex): Complex{
        return Complex(real+other.real, imaginary+other.imaginary)
    }
    
    // 重新 toString 方法
    overrride fun toString(): String {
        return "$real + ${imaginary}i"
    }
}
// 使用
val complex1 = Complex(1, 2)
val complex2 = Complex(2, 3)
println(complex1 + complex2)
// 輸出結果為
"3 + 5i"
複製程式碼

關鍵就是這個方法,方法名必須是plus或者其他官方定義的運算子,引數有且僅有一個,型別自定義,返回值意識可以自定義的.

operator fun plus(other: Complex): Complex{
        return Complex(real+other.real, imaginary+other.imaginary)
}
複製程式碼

6. 表示式

中綴表示式

通過infix關鍵字修復方法,那麼就可以不用通過 物件.方法() 的方式呼叫,而是直接 物件 方法名 引數的方式呼叫。舉了例子

class Student(var age: Int){
    infix fun big(student: Student): Boolean {
        return age > student.age
    }
}
// 如果沒有 infix 的呼叫方式:
println(Student(23).big(Student)(12))
// 如果使用 infix 修飾的呼叫方式:
println(Student(23) big Student(12))
複製程式碼

if表示式

直接來個例子

val a = 20
val b = 30
val flag: Int = if(a > b) a else b
複製程式碼

When 表示式

加強版的 switch,支援任意型別, 支援純粹表示式條件分支(類似if),舉個栗子:

val a = 5
when(a) {
    is Int -> println("$a is Int")
    in 1..6 -> println("$a is in 1..6")
    !in 1..4 -> println("$a is not in 1..4")
    else -> {
        println("null")
    }
}
複製程式碼

for迴圈

基本寫法

for (element in elements)
複製程式碼

while迴圈

基本寫法

while() {
}
do {
} while()
複製程式碼

跳過和終止迴圈

跳過當前迴圈用 continue
終止迴圈用 break
複製程式碼

6. 異常捕獲

同樣也是表示式,可以用來賦值,舉個例子

return try{
            x/y
        }
        catch(e: Exception) {
            0
        } finally {
            //...
        }
複製程式碼

如果沒有異常則返回x/y,否則返回0,finally中的程式碼無論如何還是要執行的。

7. 具名引數、變長引數和預設引數

**具名引數:**給函式的實參附上形參

fun sum(first: Int, second: Int) = first + second
sum(second = 2, first = 1)
複製程式碼

**變長引數:**用varary修飾,使用起來是和陣列一樣,某個引數可以接收多個值,可以不作為最後一個引數,如果傳參時有歧義,需要使用具名引數。

fun hello(vararg ints: Int, string: String) = ints.forEach(println(it))
hello(1,3,4,5,string = "hello")
// 如果最後一個引數也是 Int
fun hello(varary ints: Int, anInt: Int)
// 建立陣列
val arrayInt: IntArray = intArrayOf(1, 2, 3, 4)
hello(ints = *arrayInt, anInt = 2)
複製程式碼

**預設引數:**就是給引數傳入一個預設的值

fun hello(anInt: Int = 1, string: String)
hello(string = "aaa")
複製程式碼

物件導向

1. 繼承

繼承語法要點:

  • 父類需要open才可以被繼承
  • 父類方法、屬性需要open才可以被覆寫
  • 介面、介面方法、抽象類預設為open
  • 覆寫父類(介面)成員需要override關鍵字

語法要點:

  • class A: B(), C, D
  • 繼承類時實際上呼叫了父類的構造方法
  • 類只能單繼承,介面可以多實現

介面代理:

一個類可以直接將自己的任務委託給介面的方法實現,舉個例子:

interface Drive{
    fun drive()
}

interface Sing{
    fun sing()
}

class CarDrive: Drive{
    override fun drive() {
        println("我會開車呦")
    }
}

class LoveSing: Sing{
    override fun sing() {
        println("我會唱歌呦")
    }
}

class Manager(drive: Drive, sing: Sing): Drive by drive, Sing by sing

fun main(args: Array<String>) {
    val carDrive = CarDrive()
    val loveSing = LoveSing()
    val manager = Manager(carDrive, loveSing)
    manager.drive()
    manager.sing()
}
複製程式碼

這樣,manager不用做任何事情,完全交付給介面實現.

介面方法衝突:

介面方法可以有預設實現,通過super<父類名>.方法名

interface A{
    fun a() = 0
}

interface B{
    fun a() = 1
}

interface C{
    fun a() = 2
}

class D(var aInt: Int): A,B,C{
    override fun a(): Int {
        return when(aInt){
            in 1..10 ->{
                super<A>.a()
            }
            in 11..100 ->{
                 super<B>.a()
             }
            else -> {
                println("dd")
                super<C>.a()
            }
        }
    }
}
複製程式碼

2. 類及成員的可見性

java類似,private、protected、public,其中internal代表的是模組內可見

3. Object

相當於Java中的單例模式,有以下特點

  • 只有一個例項的類

  • 不能自定義構造方法

  • 可以實現介面、繼承父類

  • 本質上就是單例模式最基本的實現

      interface getDataSuccess{
          fun success()
      }
      
      abstract class getDataField{
          abstract fun failed()
      }
      
      object NetUtil: getDataField(), getDataSuccess{
          override fun success() {
              println("success")
          }
      
          override fun failed() {
              println("failed")
          }
      
          val state: Int = 0
          fun getData(): String = "請求成功"
      }
    複製程式碼

3. 伴生物件和靜態成員

相當於java中的靜態方法

  • 每個類可以對應一個伴生物件

  • 伴生物件的成員全域性獨一份

  • 如果java中想直接呼叫kotlin中的靜態方法或者靜態變數,可以考慮使用JvmField JvmStatic.

      open class Util private constructor(var anInt: Int) {
          companion object {
              @JvmStatic
              fun plus(first: Int, second: Int) = first + second
      
              fun copy(util: Util) = Util(util.anInt)
              @JvmField
              val tag = "tag"
          }
      }
    複製程式碼

4. 方法的過載

通過給方法的引數配置預設值,即可實現方法的過載,按理說,一切可以擁有預設值的方法過載才是合理的方法過載。

名稱形同、引數不同,跟返回值沒有關係

class OverLoadTest {
    @JvmOverLoads
    fun a(anInt: Int = 0, string: String="") = 1
}

val test = OverLoadTest()
test.a(1, "")
test.a()
test.a(anInt = 2)
test.a(string = "")
複製程式碼

使用JvmOverLoads是為了方便Java中呼叫方法的過載.

5. 擴充套件方法

kotlin中的擴充套件方法,我認為相當於java中的代理模式,拿到被代理的物件,然後進行一系列的操作。

fun String.add(anInt: Int): String {
    var sb = StringBuilder()
    for (i in 0 until anInt) {
        sb.append(this)
    }
    return sb.toString()
}

operator fun String.times(anInt: Int): String {
    var sb = StringBuilder()
    for (i in 0 until anInt) {
        sb.append(this)
    }
    return sb.toString()
}

// 使用
var string = "xiaweizi"
println(string.add(5))
println(string * (3))
複製程式碼

6. 屬性代理

類似之前說的var anInt: Int by lazy{2},懶賦值就是使用的屬性代理,來看個例子:

fun main(args: Array<String>) {
    val a: Int by DelegatesTest()
    println(a)

    var b: Int by DelegatesTest()
    b = 3
    println(b)
}

class DelegatesTest {
    private var anInt: Int? = null
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        println("getValue")
        return anInt?:0
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int): Unit {
        println("setValue")
        this.anInt = value
    }
}
複製程式碼

val 對應 getValuevar對應getValue和setValue方法,這個時候宣告的屬性就全權交付給DelegatesTest類中的anInt代理,當anInt為空的時候返回0,否則返回anInt.

7. JavaBean

使用data修飾類,類似java中的javaBean,預設實現了set get toString等方法,並擁有componentN方法.

不過有個缺點就是,無法被繼承,沒有無參建構函式,可以通過安裝allOpennoArg外掛解決這個問題.

data class UserBean(var name: String, var age: Int)

val userBean: UserBean = UserBean("小芳", 23)
println(userBean.name)
println(userBean.toString())

println(userBean.component1())
println(userBean.component2())

val (name, age) = userBean
println("name: $name")
println("age: $age")
複製程式碼

至於這種寫法val (name, age) = userBean,是因為定義了component1的運算子

class Complex{
    operator fun component1() = "你好呀"
    operator fun component2() = 2
    operator fun component3() = 'a'
}

val complex = Complex()
val (a, b, c) = complex
println(a + b + c)
複製程式碼

使用起來也是很簡單的

8. 內部類

  • 定義在類內部的類
  • 與類成員有相似的訪問控制
  • 預設是靜態內部類,非靜態用 inner 關鍵字
  • this@Outter this@Inner 的用法
  • 匿名內部類
    • 沒有定義名字的內部類
    • 類名編譯時生成,類似Outter$1.class
    • 可繼承父類,實現多個介面,與Java注意區別

舉個例子:

class Outer{
    var string: String = "outer"
    class Inner1{
        var string: String = "inner1"
        fun sum(first: Int, second: Int) = first + second
    }

    inner class Inner2{
        var string: String = "inner2"
        fun cha(first: Int, second: Int) = first - second
        fun getInnerField() = this.string
        fun getOuterField() = this@Outer.string
    }
}

fun main(args: Array<String>) {
    val inner1 = Outer.Inner1()
    val inner2 = Outer().Inner2()

    println(inner1.sum(1, 2))

    println(inner2.cha(2, 1))
    println(inner2.getInnerField())
    println(inner2.getOuterField())
}
複製程式碼

匿名內部類:

val listener: onClickListener = object : Father(), Mother, onClickListener{
    override fun sing() {
        println("mother sing")
    }

    override fun teach() {
        println("father teach")
    }

    override fun onClick() {
        println("匿名內部類")
    }
}
複製程式碼

使用Object實現匿名內部類

9. 列舉和密封類

列舉是物件可數,每個狀態相當於每個物件,是可以傳構造引數的

密封類時子類可數,在kotlin大於1.1子類只需要與密封類在同一個檔案加,保護子類的位置

sealed class SealedClassTest{
    class sum(first: Int, seocnd: Int): SealedClassTest()
    class cha(first: Int, seocnd: Int): SealedClassTest()

    object Bean: SealedClassTest()
}

enum class HttpStatus(val anInt: Int){
    SUCCESS(0), FAILED(1), LOADING(2)
}

fun main(args: Array<String>) {
    val class1 = SealedClassTest.cha(1, 2)
    println(HttpStatus.SUCCESS)
}
複製程式碼

高階函式

1. 基本概念

  • 傳入或者返回函式的函式
  • 函式引用 ::println
  • 帶有Receiver的引用 pdfPrinter::println

有三種顯示

// 1. 包級函式
intArray.forEach(::print)

// 2. 類.方法
intArray.forEach(Int::addOne)
fun Int.addOne(): Unit {
    println("addOne:$this")
}

// 3. 物件.方法
intArray.forEach(AddTwo()::addTwo)
class AddTwo {
    fun addTwo(anInt: Int): Unit {
        println("addTwo:$anInt")
    }
}
複製程式碼

2. 常用的高階函式

常用的高階函式還是有很多的,會簡單的使用例子即可:

// 遍歷
fun forEachTest() {
    val strings: Array<String> = arrayOf("aa", "ee", "bb", "ll")

    strings.forEach { println(it) } // 遍歷每一個值
    strings.forEachIndexed { index, s -> println("index:$index,String:$s") } // 遍歷 下標和值一一對應

}

// 重新拷貝一個值
fun mapTest() {
    val strings: Array<String> = arrayOf("aa", "ee", "bb", "ll")
    var map = strings.map { "$it-test" }
    map.forEach { print("$it\t") }
}

// 將集合合體
fun flatMapTest() {
    val lists = listOf(1..10,
            2..11,
            3..12)

    var flatMap = lists.flatMap {
        it.map {
            "No.$it"
        }
    }
    flatMap.forEach(::println)
}

fun reduceTest() {
    val ints = listOf(2, 3, 4, 5)
    println(ints.reduce { acc, i ->
        acc + i
    })
}

// 字串連線
fun foldTest(){
    val ints = listOf(2, 3, 4, 5)
    println(ints.fold(StringBuffer(), { acc, i -> acc.append("$i,") }))
    println(ints.joinToString(","))
}

fun filterTest() {
    val ints = listOf(1, 2, 3, 4, 5, 6)
    println(ints.filter { element -> element % 2 == 0 })
}

// 當值不是奇數就去,遇到偶數就停止了
fun takeWhileTest() {
    val ints = listOf(1, 3, 3, 4, 5, 6)
    println(ints.takeWhile { it % 2 != 0 })
}

fun letTest() {
    findPerson()?.let { (name, age) -> println("name:$name, age:$age") }
    findPerson()?.apply { println("name:$name, age:$age") }
    with(findPerson()!!) { println("name:$name, age:$age") }
}

data class Person(val name: String, val age: Int)

fun findPerson(): Person? {
    return Person("aa", 23)
}
複製程式碼

3. 複合函式

有點類似資料中的f(g(x))

fun main(args: Array<String>) {
    val add1 = {int: Int ->
        println("add1")
        int + 1}
    val add2 = {int : Int ->
        println("add2")
        int + 2}
    var add3 = add1 addThen (add2)
    println(add3(4))
}


infix fun <P1, P2, R> Function1<P1, P2>.addThen(function: Function1<P2, R>): Function1<P1, R> {
    return fun(p: P1): R{
        return function.invoke(this.invoke(p))
    }
}
複製程式碼

4. Currying

簡單來說就是多元函式變換成一元函式呼叫鏈式,舉個簡單的例子,這是優化之前:

fun log(tag: String, out: OutputStream, message: String){
    out.write("[$tag], $message".toByteArray())
}
複製程式碼

優化之後

fun log(tag: String)
    = fun(out: OutputStream)
    = fun(message: String)
    = out.write("[$tag], $message".toByteArray())
複製程式碼

5. 計算檔案字串個數的小例子

首先將字串轉換成字串陣列:

val map: HashMap<Char, Int> = HashMap()
var toCharArray = File("build.gradle").readText().toCharArray()
複製程式碼

通過分組的方式,統計每個字串的個數,並列印:

toCharArray.groupBy { it }.map { it.key to  it.value.size }.forEach { println(it) }
複製程式碼

kotlinjava的混合開發

1. 基本的互動操作

屬性讀寫

  • Kotlin自動識別 Java Getter/Setter
  • Java操作Kotlin屬性通過Getter/Setter

空安全型別

  • Kotlin空安全型別的原理
  • 平臺型別Platform Type
  • Java可以通過@Nullable、@NotNull

幾類函式的呼叫

  • 包級函式:靜態方法
  • 擴充套件方法:帶Receiver的靜態方法
  • 運算子過載:帶Receiver的對應名稱的靜態方法

幾個常用的註解

  • @JvmField:將屬性編譯為Java變數
  • @JvmStatic:將物件的方法編譯成功Java靜態方法
  • @JvmOverloads:預設引數生成過載方法
  • @JvmName:制定Kotlin檔案編譯後的類名

NoArg 和 AllOpen

  • NoArg為被標註的類生成無參構造
  • AllOpen為被標註的類去掉final,允許被繼承

正規表示式

  • Raw字串定義正規表示式
  • JavaPattern
  • KotlinRegex

舉個例子:

val source = "Hello This my phone number: 010-12345678."
val pattern = """.*(\d{3}-\d{8}).*"""

Regex(pattern).findAll(source).toList().flatMap(MatchResult::groupValues).forEach(::print)複製程式碼

相關文章