Kotlin基本語法和使用技巧

  • 2019 年 10 月 7 日
  • 筆記

基本語法

val value: String? = "HelloWorld"  val name: String = getName() ?: return  //如果是null就return  println("$arg1 + $arg2 = ${arg1 + arg2}")    //字元串模板  val FINAL_HELLO_CHINA = "HelloChina"   //類型可以不寫,自動推導  val args1 = arrayOf(1,2,3)  val range: IntRange = 0..1024 // [0, 1024]  val range_exclusive: IntRange = 0 until 1024 // [0, 1024) = [0, 1023]    class A {      var b = 0      lateinit var c: String   //var延遲初始化用lateinit,使用 lateinit 關鍵字,變數在定義時不需要初始化,所以在使用questionTextView變數時,不需要加上 ? 和 !! 操作符。在使用第一次變數之前,一定要保證為questionTextView賦值 , 不然會出現空指針異常。      lateinit var d: X      val e: X by lazy {          //val延遲初始化用lazy代理          println("init X")          X()      }        //第一次調用 get() 會執行已傳遞給 lazy() 的 lambda 表達式並記錄結果, 後續調用 get() 只是返回記錄的結果。       private val linearLayoutManager by lazy {          LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)      }        var cc: String? = null //初始化成null不好  }  abstract class Person(open val age: Int) {      //只有open的成員和類才能被繼承,介面和抽象類默認是open的      abstract fun work()  }    //沒有三目運算符,可以使用if 表達式  val max = if (a > b) a else b    //對於bean對象可以這樣寫  data class Forecast(val date: Date, val temperature: Float, val details: String)    //Kotlin 的構造函數可以寫在類頭中,跟在類名後面  class Person( var name:String){        private var description: String? = null        //在主構造函數中不能有任何程式碼實現,如果有額外的程式碼需要在構造方法中執行,你需要放到init程式碼塊中執行      init {          name = "Zhang Tao"      }        internal fun sayHello() {          println("hello $name")      }        fun printName(){          println(name)      }        //這裡我們讓次級構造函數調用了主構造函數,完成 name 的賦值。      // 由於次級構造函數不能直接將參數轉換為欄位,所以需要手動聲明一個 description 欄位,並為 description 欄位賦值。      constructor(name: String, description: String) : this(name) {          this.description = description      }    }    class Latitude private constructor(val value: Double) {          companion object {//伴隨對象              //加上這個註解Java可以直接像靜態那樣調用,否則得Latitude.companion.ofDouble(3.0)              @JvmStatic              fun ofDouble(double: Double): Latitude {                  return Latitude(double)              }                fun ofLatitude(latitude: Latitude): Latitude {                  return Latitude(latitude.value)              }                @JvmField              val TAG: String = "Latitude"          }      }        class Manager : Driver, Writer {          override fun write() {            }            override fun drive() {            }      }        //擴展方法,不用運算符operator的話,用"abc".times(16)這樣來調用,jva可以類名.times("abc", 16)調用      operator fun String.times(int: Int): String {          val stringBuilder = StringBuilder()          for (i in 0 until int) {              stringBuilder.append(this)          }          return stringBuilder.toString()      }    //函數引用      val pdfPrinter = PdfPrinter()          args.forEach(pdfPrinter::println)             class PdfPrinter {          fun println(any: Any) {              kotlin.io.println(any)  //重名了可以用包名調用          }      }       //常見的高階函數          val list = listOf<Int>(1, 2, 3, 5, 10, 8, 2)          val newList = ArrayList<Int>();          list.forEach {              val newElement = it * 2 + 3              newList.add(newElement)          }          //和上面一樣,上面麻煩,map可以對集合進行操作,返回一個修改過得集合          //flatmap,對集合的集合進行操作,省去了倆次遍歷的麻煩          val newList2 = list.map { it * 2 + 3 }          val newList3 = list.map { Int::toDouble }          newList3.forEach(::println)          newList3.map(::println)  //和上面輸出一樣,但是又重新add了一個集合,不好,純粹用於迭代的話會影響性能,實現裡面還有一個數組            //提取開頭指定數量或符合指定條件的子集          list.takeWhile { it <= 3 }.forEach(::println)  //小於的去掉          list.forEach {              if (it % 2 == 0) {                  println(it)              }          }          list.filter { it.isEvent() }.forEach(::println)//過濾            val person = findPerson();          //person是可null的,所以需要?          println(person?.age)          println(person?.name)          //上面太麻煩,findPerson加了?,所以後面不需要了,減少的判空操作。let可以安全調用          findPerson()?.let { person ->              person.work()              println(person.age)          }          //還可以更簡潔,person也不用寫          findPerson()?.apply {              work()              println(age)          }  //使用apply做對象的初始化  return TextView(context).apply {      text = "test"      setOnClickListener(onClickListener)  }        //use不用close了          BufferedReader(FileReader("hello.txt")).use {              var line: String?              while (true) {                  //it表示當前對象BufferedReader,所以可以用它的方法                  line = it.readLine() ?: break                  println(line)              }          }  

使用技巧

take是從集合中取前幾個元素
takeLast是從集合中取後幾個元素
sortedBy 排序
過濾list,符合過濾條件的就是過濾結果
filterNot把符合條件的過濾掉,剩下的是結果。這個操作和 filter 相反
slice,取集合中的某一部分

val numbers = listOf("one", "two", "three", "four", "five", "six")  println(numbers.slice(1..3))  println(numbers.slice(0..4 step 2))  println(numbers.slice(setOf(3, 5, 0)))    [two, three, four]  [one, three, five]  [four, six, one]    val numbers = listOf("one", "two", "three", "four", "five", "six")  println(numbers.takeWhile { !it.startsWith('f') })  println(numbers.takeLastWhile { it != "three" })  println(numbers.dropWhile { it.length == 3 })  println(numbers.dropLastWhile { it.contains('i') })      [one, two, three]  [four, five, six]  [three, four, five, six]  [one, two, three, four]
  • 擴展函數(類似於工具類)
   fun toast(message: String, length: Int = Toast.LENGTH_SHORT) {          Toast.makeText(this, message, length).show()      }       toast("hello")        //擴展函數,我們就可以在每一個Activity中直接使用toast()函數了。      fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {          Toast.makeText(this, message, duration).show()      }

!! 強⾏行行調⽤用符
?. 安全調⽤用符

  • kotlin默認不能空,變數類型後面跟?號定義,表明這是一個可空類型
  • ?. 代表著如果該類型為空的話就返回null不做後續的操作,如果不為空的話才會去訪問對應的方法或者屬性
  • !!. 代表著如果該類型為空的話就拋出NullPointerException,如果不為空就去訪問對應的方法或者屬性, 所以只有在很少的特定場景才用這種符號,代表著程式不處理這種異常的case了,會像java程式碼一樣拋出NullPointerException。 而且程式碼中一定不用出現下面這種程式碼,會讓程式碼可讀性很差而且如果有空指針異常,我們也不能馬上發現是哪空了:
  /*      * 不推薦這樣的寫法:鏈式的連續用!!.      * */      val user = User()      user!!.name!!.subSequence(0,5)!!.length

在 Kotlin 中創建單例不用像 Java 中那麼複雜,只需要把 class 換成 object 就可以了。

object Sample {    val name = "A name"}    //餓漢式的單例,並且實現了執行緒安全  object A {  val number: Int = 1   、  fun method() {  println("A.method()")    }  }  

companion 可以理解為伴隨、伴生,表示修飾的對象和外部類綁定。類似靜態變數
寫在頂級的函數(不需要在class里寫方法)或者變數有個好處:在 Android Studio 中寫程式碼時,IDE 很容易根據你寫的函數前幾個字母自動聯想出相應的函數。這樣提高了寫程式碼的效率,而且可以減少項目中的重複程式碼。
如果想寫工具類的功能,直接創建文件,寫 top-level「頂層」函數。

創建數組,增加很多有用的工具函數
contains()first()find()

val strs: Array<String> = arrayOf("a", "b", "c")

循環通過標準函數 repeat()

repeat(100) {  // todo  }

map也可以這樣創建

val map = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 3)

listOf() 創建不可變的 List,mutableListOf() 創建可變的 List

Kotlin 中集合分為兩種類型:只讀的和可變的。這裡的只讀有兩層意思:
集合的 size 不可變
集合中的元素值不可變
可以轉換

map.toMutableMap()

構造器

class User constructor(var name: String) {                                   // 直接調用主構造器  constructor(name: String, id: Int) : this(name) {    }                //  通過上一個次構造器,間接調用主構造器  constructor(name: String, id: Int, age: Int) : this(name, id) {      }  }

forEach:遍歷每一個元素
filter:對每個元素進行過濾操作,如果 lambda 表達式中的條件成立則留下該元素,否則剔除,最終生成新的集合
map:遍歷每個元素並執行給定表達式,最終形成新的集合
flatMap:遍歷每個元素,並為每個元素創建新的集合,最後合併到一個集合中

Elvis 操作符
通過 ?: 的操作來簡化 if null 的操作

fun validate(user: User) {  val id = user.id ?: return  // 驗證 user.id 是否為空,為空時 return  }  // 等同於  fun validate(user: User) {  if (user.id == null) {  return  }  val id = user.id  }

== :可以對基本數據類型以及 String 等類型進行內容比較,相當於 Java 中的 equals
=== :對引用的記憶體地址進行比較,相當於 Java 中的 ==

如果每個類型都去實現諸如 TextViewList、ActivityList 這樣的具體的類型,顯然是不可能的。因此就誕生了「泛型」,它的意思是把具體的類型泛化,編碼的時候用符號來指代類型,在使用的時候,再確定它的類型

使用關鍵字 out 來支援協變,等同於 Java 中的上界通配符 ? extends。
使用關鍵字 in 來支援逆變,等同於 Java 中的下界通配符 ? super。

var textViews: List<out TextView>  var textViews: List<in TextView>

Kotlin 標準函數
使⽤用時可以通過簡單的規則作出一些判斷

  • 返回⾃自身
    從 apply 和 also 中選
    作⽤域中使⽤ this 作為參數選擇 apply
    作⽤域中使⽤ it 作為參數選擇 also
  • 不需要返回⾃自身
    從 run 和 let 中選擇
    作用域中使用 this 作為參數,選擇 run
    作用域中使用 it 作為參數,選擇 let

apply 適合對一個對象做附加操作的時候
let 適合配合空判斷的時候
with 適合對同一個對象進行多次操作的時候

協程就是kotlin官方提供的執行緒api

屬性委託
有些常見的屬性操作,我們可以通過委託方式,讓它實現,例如:lazy 延遲屬性: 值只在第一次訪問的時候計算
類委託
可以通過類委託來減少 extend類委託的時,編譯器回優使用自身重新函數,而不是委託對象的函數

interface Base{  fun print()  }    case BaseImpl(var x: Int):Base{    override fun print(){  print(x)  }    }  // Derived 的 print 實現會通過構造函數的b對象來完成class Derived(b: base): Base by b

委託就是代理

  //介面代理,可以不是必須實現介面或抽象類的方法      class SeniorManager(val driver: Driver, val writer: Writer) : Driver by driver, Writer by writer        class CarDriver : Driver {          override fun drive() {              println("開車呢")          }      }        class PPTWriter : Writer {          override fun write() {              println("做PPT呢")          }        }        interface Driver {          fun drive()      }        interface Writer {          fun write()      }              val driver = CarDriver()          val writer = PPTWriter()          val seniorManager = SeniorManager(driver, writer)          seniorManager.drive()          seniorManager.write()      //類委託      interface Base {          fun print()      }      class BaseImpl(val x: Int) : Base {          override fun print() { print(x) }      }      class Derived(b: Base) : Base by b//實現繼承的代替方式      fun main(args: Array<String>) {          val b = BaseImpl(10)          Derived(b).print() // prints 10      }

使用 類名::class 獲取的是 Kotlin 的類型是 KClass
使用 類名::class.java 獲取的是 Java 的類型
Any
Kotlin 的頂層父類是 Any ,對應 Java 當中的 Object ,但是比 Object 少了 wait()/notify()等函數
Unit
Kotlin 中的 Unit 對應 Java 中的 void
在 Java 中通過 「類名.this」 獲取目標類引用
在 Kotlin 中通過「this@類名」獲取目標類引用