文章目錄
1 在kotlin中創建集合
val set = hashSetOf(1, 7, 53) // 創建HashSet
val list = arrayListOf(1, 7, 53) // 創建ArrayList
val map = hashMapOf(1 to "one", 7 to "seven", 53 to "fifty-three") // 創建HashMap,to是一個函數,比如1爲key,value爲one,具體函數後面說
// javaClass相當於java中的getClass()
// kotlin使用的是java的集合類
println(set.javaClass)
println(list.javaClass)
println(map.javaClass)
輸出:
class java.util.HashSet
class java.util.ArrayList
class java.util.HashMap
2 讓函數更好調用
2.1 命令參數
java的集合都有一個默認的 toString
實現,但是它格式化的輸出是固定的
val list = listOf(1, 2, 3)
println(list)
輸出:
[1, 2, 3]
下面將使用kotlin的方式實現自定義的 toString
集合輸出格式
fun <T> joinToString(
collection: Collection<T>,
separator: String,
prefix: String,
postfix: String): String {
val result = StringBuilder(prefix)
for ((index, element) in collection.withIndex()) {
if (index > 0) result.append(seperator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
val list = listOf(1, 2, 3)
// 輸出(1; 2; 3)
// 以下語句晦澀難懂
println(joinToString(list, "; ", "(", ")")
// kotlin可以在設置參數時命名參數,更明顯的查看參數表示的意義
// 即在傳遞參數時,可以給參數起個名字來表示,讓代碼有更好的可讀性,比如這裏的參數";"、"("、")"分別起名了separator、prefix和postfix
println(joinToString(list, separator = "; ", prefix = "(", postfix = ")"))
2.2 默認參數值
// 形參可以設置默認參數值
fun <T> joinToString(
collection: Collection<T>,
separator: String = "; ",
prefix: String = "(",
postfix: String = ")") {}
// 可以省略所有或部分參數的值,讓方法重載更加方便
val list = joinToString(list);
在kotlin轉java時不想一個個的寫重載方法參數,可以在方法添加註解 @JvmOverloads
,編譯時會生成默認參數值的重載方法:
在kotlin時:
@JvmOverloads
fun <T> joinToString(Collection: Collection<T>, seperator: String, prefix: String, postfix: String) {}
在轉換到java時:
String joinToString(Collection<T> collection, String separator, String prefix, String postfix);
String joinToString(Collection<T> collection, String separator, String prefix);
String joinToString(Collection<T> collection, String separator);
String joinToString(Collection<T> collection);
2.3 消除靜態工具類:頂層函數和屬性
在java開發過程中總會用到一些工具類,有的是處理字符串有的是處理其他數據,往往這些方法都不會寫在某個功能模塊的類裏,而是寫在單獨的util包下提供xxxUtils工具類,這樣我們可以在處理相關數據的時候可以直接調用而不實例化。
在kotlin中有一種寫法叫頂層函數,這類函數一般定義在一個單獨的類文件中
// 假設該文件爲String.kt
// kotlin與java不同的地方在於,kotlin不需要函數、屬性、常量都要在class類中,可以直接將它們直接寫在文件上
// 寫在這些文件上的函數、屬性、常量都是static的,公開給所有模塊使用
@file:JvmName("StringFunctions") // 提供給Java調用時使用的名稱
package com.example.kotlin.strings
import java.lang.StringBuilder
fun <T> joinToString(
collection: Collection<T>,
separator: String = ", ",
prefix: String = "",
postfix: String = ""): String {
val result = StringBuilder(prefix)
for ((index, element) in collection.withIndex()) {
if (index > 0) result.append(seperator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
在java中使用時:
import com.example.kotlin.strings.StringFunctions;
StringFunctions.joinToString(...)
// 如果沒有kt文件沒有註明@file,要訪問頂層函數,是直接使用文件名訪問,比如StirngKt.joinToString(...)
頂層屬性
// 外部變量
var opCount = 0
fun performOperation() {
opCount++;
}
fun reportOperationCount() {
println("Operation performed $opCount times")
}
// 該變量會被存儲到一個靜態的字段中,val聲明的屬性是有一個getter的
// 如果想要聲明爲一個常量,可以使用const關鍵字
const val UNIX_LINE_SEPERATOR = "\n"
等價於java的
public static final String UNIX_LINE_SEPERATOR = "\n";
3 給別人的類添加方法:擴展函數和屬性
擴展函數,它就是一個類的成員函數,不過定義在類的外面。
// 計算一個字符串的最後一個字符
package strings
fun String.lastChar(): Char = this.get(this.length - 1)
你所要做的,就是把你要擴展的類或接口的名稱,放到即將添加的函數前面。這個類的名稱被稱爲接收者類型;用來調用這個擴展函數的那個對象,叫做接收者對象。
String.lastChar()的String爲接收者類型
this.get(this.length - 1)的this爲接收者對象
// 顧名思義,擴展函數就是擴展一個類,在這個類的基礎上再添加新的函數。
// 比如這裏擴展了String類,在調用時將擴展函數中的this都替換成了傳遞給擴展函數的String對象,比如"kotlin",就像在String類的內部添加了這個函數一樣,下面代碼的效果類似於:
// fun lastChar(): Char = "Kotlin".length - 1
println("Kotlin".lastChar())
注意,擴展函數並不允許你打破它的封裝性。和在內部定義的方法不同的是,擴展函數不能訪問私有的或者是受保護的成員。
簡單說,擴展函數就是隻要類型相同,就可以將函數的接收者類型進行替換的操作,擴展函數是一個靜態方法,this就是指代這個接收者類型對象
3.1 導入和擴展函數
需要使用擴展函數時,需要顯式地導入
// 在strings包下的擴展函數lastChar()
import strings.lastChar
// import strings.*
// import strings.lastChar as last // 修改導入的類或者函數名稱,使用as關鍵字,這裏把lastChar修改爲last
val c = "Kotlin".lastChar()
// val c = "Kotlin".last() // 修改擴展函數名稱後使用
3.2 作爲擴展函數的工具函數
擴展函數無非就是靜態函數的一個高效語法糖,可以使用更具體的類型作爲接收者類型,而不是一個類。
fun <T> Collections<T>.joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = ""): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
val list = listOf(1, 2, 3)
printlin(list.joinToString(separator = "; ", prefix = "(", postfix = ")"))
具體類型的擴展函數
fun Collection<String>.join(
separator: String = ", ",
prefix: String = "",
postfix: String = "") = joinToString(separator, prefix, postfix)
listOf("1", "2", "3").join()
3.3 擴展屬性
// 這裏必須定義getter函數,因爲沒有支持字段,因此沒有默認getter實現,同理也不能初始化因爲沒有地方存儲值
val String.lastChar: Char
get() = get(length - 1)
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value: Char) {
this.setCharAt(length - 1, value)
}
println("Kotlin".lastChar)
val sb = StringBuilder("Kotlin")
sb.lastChar = '!' // setter
println(sb)
如果你嘗試給擴展屬性添加默認值,會出現編譯錯誤:擴展屬性不能有初始化器:
val MutableList<Int>.sumIsEven: Boolean = false
get() = this.sum() % 2 == 0
爲什麼不能編譯通過?其實,這與擴展函數一樣,其本質也是對應java中的靜態方法。由於擴展沒有實際地將成員插入類中,因此對擴展屬性來說幕後字段是無效的。
public final class ExampleTestKt {
// 擴展屬性實際上也是一個靜態方法
public static final boolean getSumIsEven(@NotNull List $this$sumIsEven) {
Intrinsics.checkParameterIsNotNull($this$sumIsEven, "$this$sumIsEven");
return CollectionsKt.sumOfInt((Iterable)$this$sumIsEven) % 2 == 0;
}
}
擴展屬性只能由顯式提供的getter和setter定義:
3.4 擴展函數的實現機制
擴展函數如此方便,會不會對性能產生影響?我們可以寫一個擴展函數然後將它編譯爲java查看:
fun MutableList<Int>.exchange(fromIndex: Int, toIndex: Int) {
val temp = this[fromIndex]
this[fromIndex] = this[toIndex]
this[toIndex] = temp
}
@Metadata(
mv = {1, 1, 15},
bv = {1, 0, 3},
k = 2,
d1 = {"\u0000\u0012\n\u0000\n\u0002\u0010\u0002\n\u0002\u0010!\n\u0002\u0010\b\n\u0002\b\u0003\u001a \u0010\u0000\u001a\u00020\u0001*\b\u0012\u0004\u0012\u00020\u00030\u00022\u0006\u0010\u0004\u001a\u00020\u00032\u0006\u0010\u0005\u001a\u00020\u0003¨\u0006\u0006"},
d2 = {"exchange", "", "", "", "fromIndex", "toIndex", "app_debug"}
)
public final class ExampleTestKt {
public static final void exchange(@NotNull List $this$exchange, int fromIndex, int toIndex) {
Intrinsics.checkParameterIsNotNull($this$exchange, "$this$exchange");
int temp = ((Number)$this$exchange.get(fromIndex)).intValue();
$this$exchange.set(fromIndex, $this$exchange.get(toIndex));
$this$exchange.set(toIndex, temp);
}
}
我們可以發現,擴展函數是一個靜態方法。靜態方法的特點:它獨立於該類的任何對象,且不依賴類的特定實例,被該類的所有實例共享。此外,被 public
修飾的靜態方法本質上也就是全局方法。
所以,擴展函數不會帶來額外的性能消耗。
3.5 擴展函數的作用域
既然擴展函數是全局的,一般情況下我們可以定義在包內,比如 com.example.extension
包下:
// Extension.kt
package com.example.extension
fun MutableList<Int>.exchange(fromIndex: Int, toIndex: Int) {
val tmp = this[fromIndex]
this[fromIndex] = this[toIndex]
this[toIndex] = tmp
}
但如果你嘗試將擴展函數放在一個類中管理,就會出現問題:
class Extension {
// 即使添加了修飾符爲public也一樣無法訪問
public fun MutableList<Int>.exchange(fromIndex: Int, toIndex: Int) {
val tmp = this[fromIndex]
this[fromIndex] = this[toIndex]
this[toIndex] = tmp
}
}
你會發現無法調用擴展函數 mutableList.exchange()
了,方法修飾爲 public
也一樣,我們可以看下編譯代碼:
public final class Extension {
public final void exchange(@NotNull List $this$exchange, int fromIndex, int toIndex) {
Intrinsics.checkParameterIsNotNull($this$exchange, "$this$exchange");
int temp = ((Number)$this$exchange.get(fromIndex)).intValue();
$this$exchange.set(fromIndex, $this$exchange.get(toIndex));
$this$exchange.set(toIndex, temp);
}
}
擴展函數變成類的方法了,只能提供給類或子類使用。
3.6 成員方法優先級總高於擴展函數
// 類中定義成員函數foo()
class Son {
fun foo() = println("son called member foo")
}
// 定義擴展函數foo
fun Son.foo() = println("son called extension foo")
Son().foo()
輸出:
son called member foo
當擴展函數和現有類的成員方法同時存在時,kotlin將會默認使用類的成員方法。
這麼設計其實是有意義的,在多人開發的時候,如果每個人都對 Son
擴展了 foo()
,那就很容易造成混淆,對於第三方庫來說甚至是異常災難:我們把不應該更改的方法改變了。
3.7 標準庫中的擴展函數:run、let、also、takeIf
3.7.1 run
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
run
是任何類型 T
的通用擴展函數,run
中執行了返回類型爲 R
的擴展函數 block
,最終返回該擴展函數的結果。
run
擴展函數的作用域是獨立的:
fun testFoo() {
val nickanme = "prefert"
run {
val nickname = "test"
println(nickname)
}
println(nickname)
}
這個範圍函數本身似乎不是很有用。但相比範圍,還有一點不錯的是,它返回範圍內最後一個對象。
// 如果沒有登錄的話,顯示登錄對話框,否則顯示其他對話框
run {
if (!islogin) loginDialog else getNewAccountDialog
}.show()
3.7.2 let
// let
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this) // 返回閉包結果
}
// apply
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this // 返回本身對象T
}
let
和 apply
類似,唯一不同的是返回值:apply
返回的是原來的對象,而 let
返回的是閉包裏面的值。
let
的使用很多時候都和可空類型判斷結合處理:
data class Student(val age: Int)
class Kot {
val student: Student? = getStudent()
fun dealStudent() {
// student不爲null的時候調用擴展函數let
val result = student?.let {
println(it.age)
it.age // let返回閉包的結果
}
}
}
3.7.3 also
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
also
像是 let
和 apply
函數的加強版。
class Kot {
val student: Student? = getStudent()
var age = 0
fun dealStudent() {
val result = student?.also { stu ->
// this.age是外部類Kot的成員變量var age
// stu是指定的名稱,默認會提供it使用
this.age += stu.age
println(this.age)
println(stu.age)
this.age
}
}
}
值得注意的是:如果使用 apply
,由於它內部是一個擴展函數,this
將指向 studuent
本身而不是外部類 Kot
,而且也無法調用到 Kot.age
3.7.4 takeIf
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
contract {
callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
}
return if (predicate(this)) this else null
}
如果我們不僅僅只想判空,還想加入條件,這時 let
可能就顯得有點不足,就可以使用 takeIf
。
val result = student?.takeIf { it.age >= 18 }.let { ... }
takeIf
是接收滿足條件的情況下執行後續操作,還有相反的 takeUnless
。
3.8 擴展不是萬能的
kotlin是一種靜態類型語言,我們創建的每個對象不僅具有運行時,還具有編譯時類型。在使用擴展函數時,要清楚的瞭解靜態和動態調度之間的區別。
3.8.1 靜態與動態調度
以一個例子來說明什麼是靜態和動態調度。
class Base {
public void fun() {
System.out.println("I'm Base foo!");
}
}
class Extended extends Base {
@Override
public void fun() {
System.out.println("I'm Extended foo!");
}
}
Base base = new Extended();
base.fun();
輸出:
I'm Extended foo!
我們聲明一個名爲 base
的變量,它具有編譯時類型 Base
和運行時類型 Extended
。當我們調用時,base.foo()
將動態調度該方法,這意味着運行時類型 Extended
的 fun()
被調用。
當我們調用重載方法時,調度變爲靜態並且僅取決於編譯時類型。
void foo(Base base) {... }
void foo(Extended extended) { ... }
public static void main(String[] args) {
Base base = new Extended();
foo(base); // 即使base是Extended實例,但是會調用foo(Base base)
}
3.8.2 擴展函數始終靜態調度
擴展函數都有一個接收器(receiver),由於接收器實際上只是字節代碼中編譯方法的參數,因此你可以重載它,但不能覆蓋它。這可能是成員和擴展函數之間最重要的區別:前者是動態調度的,後者總是靜態調度的。
open class Base
class Extended: Base()
fun Base.foo() = "I'm Base.foo!"
fun Extended.foo() = "I'm Extended foo!"
fun main(args: Array<String>) {
val instance: Base = Extended()
val instance2 = Extended()
println(instance.foo()) // I'm Base.foo!
println(instance2.foo()) // I'm Extended foo!
}
3.8.3 類中的擴展函數
如果我們在類的內部聲明擴展函數,那麼它將不是靜態的。如果該擴展函數加上 open
關鍵字,我們可以在子類中進行重寫。這是否意味着它將被動態調度?
當在類內部聲明擴展函數時,它同時具有調度接收器和擴展接收器。
-
調度接收器(dispatch receiver):擴展被聲明爲成員時存在的一種特殊接收器,它表示聲明擴展名的類的實例
-
擴展接收器(extension receiver):與kotlin擴展密切相關的接收器,表示我們爲其定義擴展的對象
// X是調度接收器,Y是擴展接收器
class X {
fun Y.foo() = "I'm Y.foo"
}
如果將擴展函數聲明爲 open
,則它的調度接收器只能是動態的,而擴展接收器總是在編譯時解析。
open class Base
class Extended : Base()
open class X {
open fun Base.foo() {
println("I'm Base.foo in X")
}
open fun Extended.foo() {
println("I'm Extended.foo in X")
}
}
class Y : X() {
override fun Base.foo() {
println("I'm Base.foo in Y")
}
override fun Extended.foo() {
println("I'm Extended.foo in Y")
}
}
X().deal(Base()) // I'm Base.foo in X
Y().deal(Base()) // I'm Base.foo in Y - dispatch receiver被動態處理
X().deal(Extended() // I'm Base.foo in X - extension receiver被靜態處理
Y().deal(Extended() // I'm Base.foo in Y
可以發現 Extended
的擴展函數始終沒有被調用。決定兩個 Base
類擴展函數執行哪一個,直接因素是執行 deal()
的類的運行時類型。
擴展函數使用注意事項:
-
如果該擴展函數是頂級函數或成員函數,則不能被覆蓋
-
我們無法訪問其接收器的非公共屬性
-
擴展接收器總是被靜態調度
3.8.4 被濫用的擴展函數
fun Context.loadImage(url: String, imageView: ImageView) {
Glide.with(this)
.load(url)
.placeholder(R.mipmap.img_default)
.error(R.mipmap.ic_error)
.into(imageView)
}
ImageActivity.kt
this.loadImage(url, imageView)
上面的擴展函數其實是濫用的,實際上並沒有以任何方式擴展現有類。
Context
作爲上下文已經承擔了很多責任,基於 Context
擴展還很可能產生 ImageView
於傳入上下文週期不一致導致的問題。
正確做法是在 ImageView
進行擴展:
fun ImageView.loadImage(url: String) {
Glide.with(this.context)
.load(url)
.placeholder(R.mipmap.img_default)
.error(R.mipmap.ic_error)
.into(this)
}
// 圖片請求框架擴展
object ImageLoader {
fun with(context: Context, url: String, imageView: ImageView) {
Glide.with(context)
.load(url)
.placeholder(R.mipmap.img_default)
.error(R.mipmap.ic_error)
.into(imageView)
}
}
3.9 高階函數
高階函數指的是將函數用作一個函數的參數或者返回值的函數。
如下一個高階函數:
fun test(block: () -> Unit) {
block()
}
test {
...
}
一個高階函數格式如下:
高階函數名稱: [類型].(參數列表) -> 返回值
block: () -> Unit
block: (Int, Int) -> Int
3.9.1 將函數作爲參數的高階函數
public inline fun CharSequence.sumBy(selector: (Char) -> Int): Int {
var sum: Int = 0
// 遍歷字符串字符
for (element in this) {
sum += selector(element) // 高階函數的操作由外部處理
}
return sum
}
val str = "abc"
val sum = str.sumBy {
// 將字符轉爲int
it.toInt()
}
println(sum)
3.9.2 將函數作爲返回值的高階函數
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body() // 高階函數操作後返回類型T
} finally {
lock.unlock()
}
}
val result = lock(lock) {
// 不需要寫return關鍵詞
sharedResource.operation()
}
3.9.3 自定義高階函數
fun operation(num1: Int, num2: Int, result: (Int, Int) -> Int) {
return result(num1, num2)
}
val plus = operation(10, 10) { num1, num2 ->
num1 + num2
}
val reduce = operation(10, 10) { num1, num2 ->
num1 - num2
}
val ride = operation(10, 10) { num1, num2 ->
num1 * num2
}
val except = operation(10, 10) { num1, num2 ->
num1 / num2
}
3.9.4 run()和T.run()的區別
run()
和 T.run()
兩個函數差不多,兩者之間的區別可以通過源碼查看:
public inline fun <T> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
可以發現 T.run()
的高階函數 block()
是一個擴展在 T
類型下的函數,說明這個高階函數可以使用當前對象 T
的上下文。當我們想要使用當前對象的上下文的時候,可以使用這個函數。
"kotlin".run {
// 可以使用this對象,它代表對象"kotlin"
println("length=${this.length}")
println("first=${first()}")
println("last=${last()}")
}
上面說明的擴展函數 run
、let
、also
、takeIf
都是有使用到高階函數。
4 處理集合:可變參數、中綴調用和庫支持
4.1 擴展java集合的API
val strings: List<String> = listOf("first", "second", "fourteenth")
strings.last() // 輸出fourteenth
val numbers: Collection<Int> = setOf(1, 14, 2)
numbers.max() // 輸出14
last和max都是擴展函數
fun <T> List<T>.last(): T { .. }
fun Collection<Int>.max(): Int { .. }
4.2 可變參數:讓函數支持任意數量的參數
val list = listOf(2, 3, 5, 7, 11)
// vararg是可變參數修飾符,相當於java的可變參數符號"..."
fun listOf<T>(vararg values: T): List<T> { .. }
// kotlin和java的另一個區別是,當需要傳遞的參數已經包裝在數組中時,調用該函數的語法
// 在java中,可以按原樣傳遞數組,而kotlin則要求你顯式地解包數組,以便每個數組元素在函數中能作爲單獨的參數來調用
// 這個功能被稱爲展開運算符,而使用的時候,不過是在對應的參數前面放一個*
fun main(args: Array<String>) {
val list = listOf("args:", *args); // 展開運算符展開數組內容
println(list)
}
4.3 鍵值對的處理:中綴調用和解構聲明
4.3.1 中綴調用
// to是一種特殊的函數,稱爲中綴調用
val map = mapOf(1 to "one", 7 to "seven", 53 to "fifty-three")
// 下面是等價的
1.to("one")
1 to "one"
// 中綴調用可以與只有一個參數的函數一起使用,無論是普通的函數還是擴展函數
// 要允許使用中綴符號調用函數,需要使用infix修飾符標記
// Pair是kotlin標準庫中的類,用來表示一對元素
infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
4.3.2 解構聲明
我們想要從對象獲取數值存儲到臨時變量時經常會這樣做:
val b1 = Bird(20.0, 1, "blue")
val weight = b1.weight
val age = b1.age
val color = b1.color
kotlin爲我們提供瞭解構聲明,我們就可以這樣做:
val b1 = Bird(20.0, 1, "blue")
val (weight, age, color) = b1
println("$weight, $age, $color")
但是kotlin對於數組的解構也有一定限制,在數組中它默認最多允許賦值5個變量。
解構內部的原理是通過 componentN
的方法來實現,N代表類中屬性的順序:
// 數據類Bird反編譯爲java代碼
public final double component1() { return this.weight; }
public final int component2() { return this.age; }
@NotNull
public final String component3() { return this.color; }
5 字符串和正則表達式的處理
kotlin的正則表達式和java相同
5.1 分割字符串
// 可以指定多個分隔符,且在java中"."是需要做轉義操作的而在kotlin不用
"12.345-6.A".split(".", "-") // 輸出[12, 345, 6, A]
5.2 正則表達式和三重引號的字符串
// val path = "/Users/yole/kotlin-book/chapter.adoc"
fun parsePath(path: String) {
val directory = path.substringBeforeLast("/") // /Users/yole/kotlin-book
val fullname = path.substringAfterLast("/") // chapter.adoc
val filename = fullname.substringBeforeLast(".") // chapter
val extension = fullname.substringAfterLast(".") // adoc
println("Dir:$directory, name:$filename, ext:$extension")
}
fun parsePath(path: String) {
val regex = """(.+)/(.+)\.(.+)""".toRegex()
val matchResult = regex.matchEntire(path)
if (matchResult != null) {
val (directory, filename, extension) = matchResult.destructured
println("Dir:$directory, name:$filename, ext:$extension")
}
}
6 讓你的代碼更整潔:局部函數和擴展
在java中減少重複代碼時會使用 Extract Method抽取分解函數來實現重用代碼,但是這樣可能讓代碼更費解,因爲你以一個包含許多小方法的類告終,而且它們之間並沒有明確關係。
在kotlin提供了一個更整潔的方案:可以在函數中嵌套這些提取的函數。這樣既可以獲得所需的結構,也無需額外的語法開銷。
class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
if (user.name.isEmpty()) {
throw IllegalArgumentException("Can't save user ${user.id}:empty name")
}
if (user.address.isEmpty() {
throw IllegalArgumentException("Can't save user ${user.id}:empty address")
}
...
}
上面判斷 isEmpty()
的代碼是重複的,在kotlin中可以將驗證代碼放到局部函數中,可以擺脫重複,並保持清晰的代碼結構:
class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
// 在saveUser()方法中聲明一個validate()局部函數驗證有效性
// 局部函數可以訪問所在函數中的所有參數和變量
// 即validate()局部函數可以直接使用saveUser()函數的User參數
fun validate(value: String,
fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException("Can't save user ${user.id}:empty $fieldName")
}
}
// 調用局部函數驗證代碼有效性
validate(user.name, "Name")
validate(user.address, "Address")
// 執行後續操作
...
}
將上面代碼放到擴展函數中:
class User(val id: Int, val name: String, val address: String)
fun User.validateBeforeSave() {
fun validate(value: String, fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException("Can't save user id $id, empty $fieldName")
}
}
validate(name, "Name")
validate(address, "Address")
}
fun saveUser(user: User) {
user.validateBeforeSave()
}