Kotlin 經常使用語法篇

習慣用法和規範

類佈局

一般,一個類的內容按如下順序排列:java

  • 屬性聲明與初始化塊
  • 次構造函數
  • 方法聲明
  • 伴生對象

不要按字母順序或者可見性對方法聲明排序,也不要將常規方法與擴展方法分開。而是要把相關的東西放在一塊兒,這樣從上到下閱讀類的人就可以跟進所發生事情的邏輯。選擇一個順序(高級別優先,或者相反) 並堅持下去。編程

將嵌套類放在緊挨使用這些類的代碼以後。若是打算在外部使用嵌套類,並且類中並無引用這些類,那麼把它們放到末尾,在伴生對象以後。設計模式

常量名稱
bash

標有 const 的屬性,或者 val 屬性的對象應該使用大寫、下劃線分隔的名稱:app

const val MAX_COUNT = 8  //const屬於編譯期常量複製代碼
val USER_NAME_FIELD = "UserName"複製代碼

保存帶有行爲的對象或者可變數據的頂層/對象屬性的名稱應該使用常規駝峯名稱:ide

val mutableCollection: MutableSet<String> = HashSet()複製代碼

保存單例對象引用的屬性的名稱可使用與 object 聲明相同的命名風格:函數

val PersonComparator: Comparator<Person> = ...
複製代碼

Lambda 表達式參數

在簡短、非嵌套的 lambda 表達式中建議使用 it 用法而不是顯式聲明參數。而在有參數的嵌套 lambda 表達式中,始終應該顯式聲明參數。佈局

在 lambda 表達式中返回

避免在 lambda 表達式中使用多個返回到標籤。請考慮從新組織這樣的 lambda 表達式使其只有單一退出點。 若是這沒法作到或者不夠清晰,請考慮將 lambda 表達式轉換爲匿名函數。優化

不要在 lambda 表達式的最後一條語句中使用返回到標籤。ui

data數據類

data class User(val name: String, val age: Int)複製代碼

Kotlin編譯器會自動從主構造函數中聲明的全部屬性並會爲User類提供如下功能:

  • 全部屬性的 getters (對於 var 定義的還有 setters)
  • equals()
  • hashCode()
  • toString()
  • copy()

爲了確保數據類生成的代碼的一致性,通常知足主構造函數須要至少有一個參數,主構造函數的全部參數須要標記爲 valvar,並且數據類不能是抽象、開放、密封或者內部的

請注意,對於那些自動生成的函數,編譯器只使用在主構造函數內部定義的屬性。如需在生成的實現中排出一個屬性,請將其聲明在類體中:

data class Person(val name: String) {var age: Int = 0}複製代碼

toString()equals()hashCode() 以及 copy() 的實現中只會用到 name 屬性,而且只有一個 component 函數 component1()。雖然兩個 Person 對象能夠有不一樣的年齡,但它們會視爲相等。

複製

在不少狀況下,咱們須要複製一個對象改變它的一些屬性,但其他部分保持不變。 copy() 函數就是爲此而生成。對於上文的 User 類:

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)複製代碼

咱們能夠寫成:

val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)複製代碼

過濾 list

val positives = list.filter { x -> x > 0 }複製代碼

或者能夠更短:

val positives = list.filter { it > 0 }複製代碼

遍歷 map/pair型list

val map = hashMapOf("name" to "zhangsan","age" to "26","address" to "hangzhou")
   for ((k, v) in map) {
        println("$k -> $v")
        println("$k -> ${map[k]}")
   }

map的訪問支持 map[key]形式複製代碼

kv 能夠改爲任意名字。

「if」表達式

fun foo(param: Int) { 
val result = if (param == 1) {
    "one" 
  } else if (param == 2) { 
    "two"
  } else {
    "three"
  }
}複製代碼

使用條件語句

優先使用 tryifwhen 的表達形式。例如:

return if (x) foo() else bar() 
return when(x) {
 0 -> "zero"
else -> "nonzero"
}複製代碼

優先選用上述代碼而不是:

if (x)
   return foo()
else
   return bar() 複製代碼
when(x) {
      0 -> return "zero"
      else -> return "nonzero"
}複製代碼

注:二元條件優先使用 if 而不是 when,若是有三個或多個選項時優先使用 when

對一個對象實例調用多個方法 (with)

class Turtle {
    fun penDown()
    fun penUp()
    fun turn(degrees: Double)
    fun forward(pixels: Double)
}
val myTurtle = Turtle()
    with(myTurtle) { // 畫一個 100 像素的正方形
       penDown()
       for(i in 1..4) {
       forward(100.0)
       turn(90.0)
       }
      penUp()
}複製代碼

對象聲明

Kotlin中object關鍵字在多種狀況下出現,包括下面講到的「伴生對象」,「對象表達式」以及如今講的「對象聲明」都使用了object關鍵字,可見object關鍵字用法多麼廣發和強大;

object關鍵字出現他們都遵循一樣的核心理念:這個關鍵字定義了一個類,並建立了該類的實例,也就是說用object關鍵字在定義該類的同時建立了該類的對象;

在開發中咱們一般會使用到單例模式,java中單例經過static字段存儲實例對象,並將構造私有化,經過暴露出一個靜態方法用來惟一訪問實例,在kotlin中能夠直接通多object聲明這樣的一個類,經過這種「對象聲明」方式將類的聲明和類的惟一實例結合在一塊兒。

與類聲明同樣,一個對象聲明一樣能夠包括屬性,方法,初始化語句塊等聲明,惟一不容許的是構造方法,與普通類實例不一樣,對象聲明在定義的時候就已經被建立,不須要在其餘地方調用構造方法;

object Persion{

fun add(var a : Int , var b:Int):Int{

return a+b

}

}

val sum=Persion.add(3,5) //單例調用

伴生對象

伴生對象也叫類內部的對象,其聲明能夠用 companion 關鍵字標記:

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}複製代碼

能夠省略伴生對象的名稱,在這種狀況下將使用名稱 Companion

class MyClass {
     companion object { }
}
val x = MyClass.Companion複製代碼

注意:伴生對象的成員看起來像java中的靜態成員,在運行時他們仍然是真實對象的實例成員,並且,例如還能夠實現接口:

interface Factory<T> {
    fun create(): T
}

class MyClass {
      companion object : Factory<MyClass> {
          override fun create(): MyClass = MyClass() 
      }
}複製代碼
val f: Factory<MyClass> = MyClass複製代碼

固然,在 JVM 平臺,若是使用 @JvmStatic 註解,你能夠將伴生對象的成員生成爲真正的靜態方法和字段。

對象表達式

object關鍵字不只能夠用來聲明一個單例對象、伴生對象,也能夠用來聲明一個匿名對象,匿名對象替代java中的匿名內部類的用法。

button.setOnClickListener(object: View.OnClickListener(){ override fun OnClick(v:View){}})

除了去掉了對象名字之外,語法與對象聲明相同,對象表達式聲明瞭一個類並建立了該類的實例,

但並無爲這個類或實例分配一個名字,一般來說,他們都不須要一個名字,由於你會將這個對象做爲一個函數的參數,若是須要分配一個名字給這個對象,你能夠將這個對象存儲到一個變量中:

val listener=object: View.OnClickListener(){ override fun OnClick(v:View){}}複製代碼

與java的匿名對象不一樣的是,java匿名對象只能擴展一個類或者實現一個接口,kotlin的對象表達式能夠實現多個接口或者不實現接口。

須要注意的是,Kotlin的對象表達式並非單例類型的,每次執行到該位置時候,就會從新建立一個新的實例。

例外與java最大不一樣是kotlin的對象表達式中能夠訪問被建立函數中的局部變量,可是在java中訪問被限制在final類型變量上,可是kotlin中解除了這個限制,這就表明kotlin中對象表達式能夠訪問並修改被建立函數的局部變量,

button.setOnClickListener(object: View.OnClickListener(){

var count=0

override fun OnClick(v:View){

count++

}

})

Lambda 表達式

做爲函數參數的代碼塊, lambda表達式一般做爲一個參數傳入函數中,也能夠單獨存儲到一個變量中; 在java 8 中jdk 中也支持了lambda編程風格,這也是java 語言在不斷演變和優化中最讓人望眼欲穿的功能, 那麼使用lambda到底能帶來那些有優點呢?

java中最普通的點擊監聽器實現:

button.setOnClickListener(new View.OnClickListener(){

@override

public void OnClick(View view){

// todo

}

})

經過匿名內部類去傳入一個監聽器實例,並實現監聽器的click方法,當咱們有多個view須要實現這種點擊實現,那咱們就得寫多個這種實現,雖然寫起來很簡單,可是確實爲咱們增長了代碼量,不管從語言角度仍是設計模式角度看待這個問題,良好的編程風格主要原則之一就是避免代碼在任何地方重複, 那麼lambda表達式就很好解決這個問題, kotlin和java8 以後實現是:

button.setOnClickListener{  // todo  }複製代碼

這段代碼和java匿名內部類作了一樣的事情,可是更加簡單易讀,lambda被看成只有一個方法的匿名對象的替代品使用;

lambda一樣跟集合搭配使用是kotlin 的一大特點,如找到一個list<Persion> 中年齡最大的persion,在java中廣泛實現是你會引入兩個中間變量,一個用來保存最大年齡,而另外一個用來保存最大年齡的人,而後遍歷這個列表,不斷更新這兩個變量:

public void findMaxAgePersion(List<Persion> persions){

int maxAge=0

Persion oldPersion=null

forEach(Persion persion:persions){

if(persion.age>maxAge){

maxAge=persion.age

oldPersion=persion

}

}

println(oldPersion)

}

List persions=new ArrayList<Persion>()
persions.add(new Persion("zhangli",16))
persions.add(new Persion("wangpeng",22))
findMaxAgePersion(persion)
複製代碼

Kotlin中實現:

val persions=listOf(Persion("zhangli",16),Persion("zhangpeng",26))

val persion=persions.maxBy{it.age}

println(persion)

咱們一般在java中對集合作的大多數事情能夠經過使用lambda或成員引用的庫函數來更好的表達,這樣代碼就少不少,也變得更容易理解;

lambda語法結構:

{x:Int, y:Int -> x+y }

Kotlin的lambda表達式始終用花括號包圍,花括號中經過 箭頭(->)將實參和函數體分開,

val sum={x:Int, y:Int -> x+y }

println(sum(3, 5)) // lambda表達式存儲到變量中,能夠當作普通函數經過實參正常調用


Kotlin中對lambda有些語法約定:

  • 若是lambda表達式做爲函數最後一個實參,能夠將lambda表達式放到括號外面;
  • 當lambda做爲函數惟一的一個實參時,能夠將函數括號直接省略;
  • 當有多個實參時候,便可以選擇把lambda留在括號內強調它是一個實參,也能夠放到括號外邊;
  • 當函數有多個lambda實參須要傳入,不能超過一個lambda表達式放到外面;


咱們拿上面的persions.maxBy{it.age}案例來講:

亦能夠寫成:

persions.maxBy({persion:Persion -> persion.age})複製代碼

做爲最後一個參數,可將lambda放到括號外邊:

persions.maxBy(){ persion:Persion -> persion.age }複製代碼

做爲惟一參數,可省略函數空括號:

persions.maxBy{ persion:Persion -> persion.age }複製代碼

省略lambda參數類型(上下文自動推斷類型)

persions.maxBy{ persion-> persion.age }   複製代碼

使用默認參數名稱

persions.maxBy{ it.age }  //it 是自動默認生成的參數複製代碼

集合的函數式API

在kotlin中有不少跟集合操做相關的擴展函數,這種擴展函數無疑爲咱們減輕了不少負擔,這使得Kotlin相比java語法顯得更加簡單和高效的地方之一,kotlin中在合適地方使用這些標準庫函數能夠幫助咱們更加高效開發,同時使得咱們代碼結構和邏輯顯得更加簡潔和清晰;

  • filter

filter函數遍歷集合,並返回符合傳入lambda表達式爲ture條件的集合元素,首先明白咱們操做的是集合結構,而且返回的也是集合,filter函數幫助咱們過濾出lambda表達式中符合條件的元素,舉例說明:

var list=listOf(1,2,3,4)
prinltn (list.filter() { it % 2==0 })
// [2,4]  只有偶數留下來
複製代碼

上面結果返回的是一個新集合,集合中只包含那邊符合lambda中判斷式的元素,即:filter函數選出了匹配給定斷定式的元素;

val persions= listOf(Persion("zhangsan",30),Persion("wangwu",32),Persion("liuxing",21))

println(persions.filter{

it.age<30

})

// Persion("liuxing",21)

  • map

filter函數不會改變這些元素的值,幫助咱們從這些集合中篩選出符合條件的元素,若是須要對集合元素作修改或者變換須要用到map操做符;

map函數對集合中每一個元素應用給定的函數並把結果放到一個新集合中;

var list=listOf(1,2,3,4)
prinltn (list.map { it * it })
// [1,4,9,16]  

複製代碼

同filter函數,返回的是一個新的集合,原集合數據並無修改或者破壞,而且新集合包含的元素個數不會變化,只是對集合中元素應用了給定的函數變換,即:map對集合中每一個元素應用了lambda表達式;

val persions= listOf(Persion("zhangsan",30),Persion("wangwu",32),Persion("liuxing",21))

println(persions.map{

     it.name

})

// [zhangsan,wangwu,liuxing]

也可使用成員引用寫法:

println(persions.map(Persion::name))

  • all

all函數檢查集合中全部元素是否知足給定斷定式,返回值是Boolean類型的,例如:

val peoples=listOf(Persion("zhangsan",27),Persion("wangwu",22))

println(peoples.all{it.age>25})

// false
複製代碼
  • any

any函數檢查集合中是否存在某個元素符合給定判斷式,返回值也是Boolean類型,例如:

val peoples=listOf(Persion("zhangsan",27),Persion("wangwu",22))

println(peoples.any{it.age>25})

// true複製代碼

 注意:!all 加上條件等價於用any加上這個條件的取反來替換,反之亦然!

  • count


若是你想知道有多少個元素符合條件,可使用count函數將結果返回,例如:

println(peoples{

it.age>20

})

// 2 複製代碼
  • find

要找到第一個知足給定斷定式的元素,可使用find函數,例如:

println(peoples.find{
it.age>25}
)
// [ "zhangshan", 27 ]複製代碼
  • with

"with" 函數用來對同一個對象執行屢次操做,不須要反覆引用對象名稱;

"with" 返回的值是執行lambda代碼的結果,該結果就是lambda中最後一行代碼的值;

  • apply

"apply" 函數用來對同一個對象執行屢次操做,也不須要反覆引用對象名稱;

"apply" 返回的值是元素自己(接受者自己);

相關文章
相關標籤/搜索