做用域函數是Kotlin中的一個很是有用的函數,它主要分爲兩種,一種是拓展函數式,另外一種是頂層函數式。做用域函數的主要功能是爲調用函數提供一個內部範圍,同時結合kotlin的語法糖提供一些便捷操做。微信
做用域函數主要有下面這幾種,它們的主要區別就是函數體內使用對象和返回值的區別。markdown
函數體內使用this代替本對象。返回值爲函數最後一行或者return指定的表達式app
函數內使用it代替本對象。返回值爲函數最後一行或者return指定的表達式。less
函數內使用this代替本對象。返回值爲本對象。函數
函數內使用it代替本對象。返回值爲本對象。ui
條件爲真返回對象自己不然返回null。this
條件爲真返回null不然返回對象自己。url
with比較特殊,不是以擴展方法的形式存在的,而是一個頂級函數。傳入參數爲對象,函數內使用this代替對象。返回值爲函數最後一行或者return指定的表達式。spa
將函數體執行屢次。code
經過表格進行下總結,以下所示。
操做符 | this/it | 返回值 |
---|---|---|
let |
it |
最後一行或者return指定的表達式 |
with | it |
最後一行或者return指定的表達式 |
run |
this |
最後一行或者return指定的表達式 |
also | this | 上下文對象 |
apply | this | 上下文對象 |
下面經過一個簡單的例子來演示下這些做用域函數的基本使用方式。
class TestBean {
var name: String = "xuyisheng"
var age: Int = 18
}
fun main(args: Array<String>) {
val test = TestBean()
val resultRun = test.run {
name = "xys"
age = 3
println("Run內部 $this")
age
}
println("run返回值 $resultRun")
val resultLet = test.let {
it.name = "xys"
it.age = 3
println("let內部 $it")
it.age
}
println("let返回值 $resultLet")
val resultApply = test.apply {
name = "xys"
age = 3
println("apply內部 $this")
age
}
println("apply返回值 $resultApply")
val resultAlso = test.also {
it.name = "xys"
it.age = 3
println("also內部 $it")
it.age
}
println("also返回值 $resultAlso")
val resultWith = with(test) {
name = "xys"
age = 3
println("with內部 $this")
age
}
println("with返回值 $resultWith")
test.age = 33
val resultTakeIf = test.takeIf {
it.age > 3
}
println("takeIf $resultTakeIf")
val resultTakeUnless = test.takeUnless {
it.age > 3
}
println("takeUnless $resultTakeUnless")
}複製代碼
執行結果以下所示。
Run內部 TestBean@27c170f0
run返回值 3
let內部 TestBean@27c170f0
let返回值 3
apply內部 TestBean@27c170f0
apply返回值 TestBean@27c170f0
also內部 TestBean@27c170f0
also返回值 TestBean@27c170f0
with內部 TestBean@27c170f0
with返回值 3
takeIf TestBean@27c170f0
takeUnless null複製代碼
官網提供了一張圖來幫助開發者選擇合適的做用域函數,以下所示。
run、with、repeat,是比較經常使用的3個頂級函數,它們是區別於其它幾種拓展函數類型的,它們的使用也比較簡單,示例代碼以下所示。
fun testRun() {
var str = "I am xys"
run {
val str = "I am zj"
println(str) // I am xys
}
println(str) // I am zj
}複製代碼
能夠發現,run頂級函數提供了一個獨立的做用域,能夠在該做用域內完整的使用全新的變量和屬性。
repeat(5){
print("repeat")
}複製代碼
repeat比較簡單,直接將函數體按指定次數執行。
前面的代碼已經演示過with如何使用。
with(ArrayList<String>()) {
add("a")
add("b")
add("c")
println("this = " + this)
this
}複製代碼
要注意的是其返回值是根據return的類型或者最後一行代碼來進行判斷的。
Kotlin的?操做符和做用域函數的拓展函數能夠很是方便的進行對象的判空及後續處理,例以下面的例子。
// 對result進行了判空並bindData
result?.let {
if (it.isNotEmpty()) {
bindData(it)
}
}複製代碼
相似apply這樣的做用域函數,能夠返回this的做用域函數,能夠將對象的建立和屬性的賦值寫在一塊兒,簡化代碼,相似builder模式,例以下面的這個例子。
// 使用普通的方法建立一個Fragment
fun createInstance(args: Bundle) : MyFragment {
val fragment = MyFragment()
fragment.arguments = args
return fragment
}
// 經過apply來建立一個Fragment
fun createInstance(args: Bundle)
= MyFragment().apply { arguments = args }複製代碼
再例以下面的實現。
// 使用普通的方法建立Intent
fun createIntent(intentData: String, intentAction: String): Intent {
val intent = Intent()
intent.action = intentAction
intent.data = Uri.parse(intentData)
return intent
}
// 經過apply函數的鏈式調用建立Intent
fun createIntent(intentData: String, intentAction: String) =
Intent().apply { action = intentAction }
.apply { data = Uri.parse(intentData) }複製代碼
以及下面的實現。
// 正常方法
fun makeDir(path: String): File {
val result = File(path)
result.mkdirs()
return result
}
// 改進方法
fun makeDir(path: String)
= path.let{ File(it) }.also{ it.mkdirs() }複製代碼
在開發中,有些對象有不少參數或者方法須要設置,但該對象又沒用提供builder方式進行構建,例以下面的例子。
val linearLayout = LinearLayout(itemView.context).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT)
}
progressBar.apply {
progress = newProgress
visibility = if (newProgress in 1..99) View.VISIBLE else View.GONE
}複製代碼
不管是let、run、apply仍是其它拓展函數,均可以實現這樣的需求,藉助it或this,能夠很方便的對該對象的多個屬性進行操做。
不過這些拓展函數仍是有一些細微的差異的,例如T.run和T.let(即便用it和this的區別)
例以下面的例子。
stringResult?.let {
nonNullString ->
println("The non null string is $nonNullString")
}複製代碼
經過對it的重命名,語義表達更加清楚。
藉助kotlin的?操做符,能夠簡化不少條件操做,例以下面的幾個例子。
url = intent.getStringExtra(EXTRA_URL)?.takeIf { it.isNotEmpty() } ?: run {
toast("url空")
activity.finish()
}複製代碼
上面的代碼演示了【從intent中取出url並在url爲空時的操做】。
test.takeIf { it.name.isNotEmpty() }?.also { print("name is $it.name") } ?: print("name empty")複製代碼
上面代碼演示了【從test中取出name,不爲空的時候和爲空的時候的操做】。
做用域函數的一個很是方便的做用就是經過其返回值的改變來組裝鏈式調用。一個簡單示例以下所示。
test.also {
// todo something
}.apply {
// todo something
}.name = "xys"複製代碼
經過let來改變返回值,從而將不一樣的處理經過鏈式調用串聯起來。
val original = "abc"
// 改變值而且傳遞到下一鏈條
original.let {
println("The original String is $it") // "abc"
it.reversed() // 改變參數而且傳遞到下一鏈條
}.let {
println("The reverse String is $it") // "cba"
it.length // 改變參數類型而且傳遞到下一鏈條
}.let {
println("The length of the String is $it") // 3
}複製代碼
上面的代碼藉助let,能夠將函數的返回值不斷進行修改,從而直接將下一個操做進行鏈式鏈接。而使用also(即返回this的做用域函數)能夠將多個對同一對象的操做進行鏈式調用,以下所示。
original.also {
println("The original String is $it") // "abc"
it.reversed() // 即便咱們改變它,也是沒用的
}.also {
println("The reverse String is ${it}") // "abc"
it.length // 即便咱們改變它,也是沒用的
}.also {
println("The length of the String is ${it}") // "abc"
}複製代碼
這裏只是爲了演示,因此將能夠寫在同一個做用域函數中的進行了拆分。
also和let的鏈式調用,實際上各有不一樣的使用技巧,經過let,能夠改變返回值,而經過also,能夠將多個不一樣的原子操做經過鏈式進行組合,讓邏輯更加明朗。
雖然also和apply都是返回this,但國際慣例,它們在使用的時候,仍是有一些細微的差異的,also強調的是【與調用者無關的操做】,而apply強調的是【調用者的相關操做】,例以下面的這個例子。
test?.also {
println("some log")
}?.apply {
name = "xys"
}複製代碼
let和run的返回值相同,它們的區別主要在於做用域內使用it和this的區別。通常來講,若是調用者的屬性和類中的屬性同名,則通常會使用let,避免出現同名的賦值引發混亂。
國際慣例,run一般使用在鏈式調用中,進行數據處理、類型轉換,例如?.run{}的使用。
歡迎關注個人微信公衆號——Android羣英傳