以下說明這些功能:collections, strings 及 regular expressions 的相關函數。以命名的參數及預設值,定義參數。透過擴增的 functions 及 properties,使用 Java Libraries。以 top-level 及 local function 的方式重組程式碼。
建立 collections
kotlin 的 .javaClass
就等同 java 的 getClass()
//println(set.javaClass)
//class java.util.HashSet
val set = hashSetOf(1, 7, 53)
//println(list.javaClass)
//class java.util.ArrayList
val list = arrayListOf(1, 7, 53)
//println(map.javaClass)
//class java.util.HashMap
val map = hashMapOf(1 to "one", 7 to "seven", 53 to "fifty-three")
kotlin 沒有建立自己的 collection classes 而是沿用 java 的 collection classes
// last() 取得最後一個 element
val strings = listOf("first", "second", "fourteenth")
println(strings.last())
// max() 取得最大值
val numbers = setOf(1, 14, 2)
println(numbers.max())
// println 會呼叫 list 的 toString()
val list = listOf(1, 2, 3)
println(list)
joinToString 將 collection 以 prefix, separator, postfix 的格式列印出來
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(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
fun main(args: Array<String>) {
val list = listOf(1, 2, 3)
println(joinToString(list, "; ", "(", ")"))
}
執行結果
(1; 2; 3)
為了避免每一次都需要填寫四個參數的麻煩,可利用以下方式處理
- Named Arguments 呼叫 function 時,將參數命名
joinToString(collection, separator = " ", prefix = " ", postfix = ".")
- Default parameter values 函數的參數提供預設值
fun <T> joinToString(
collection: Collection<T>,
separator: String = ", ",
prefix: String = "",
postfix: String = ""
): String
接下來可用以下方式呼叫函數
joinToString(list, ", ", "", "")
joinToString(list)
joinToString(list, "; ")
joinToString(list, prefix = "# ")
top-level functions and properties
取代 static utility clsses
建立 join.kt,放入以下 code
package strings
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(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
$ kotlinc join.kt
編譯後,會產生 strings/JoinKt.class
所有 top-level function 都會成為該 class 的 static methods
如果加上 @file:JvmName("StringFunctions")
就可改變 class 的名稱,為 strings.StringFunctions
@file:JvmName("StringFunctions")
package strings
fun joinToString(...): String { ... }
top-level properties 轉換到 java 時,一樣會產生 accessor methods, val 變數會產生 getter,var 變數產生 getter/setter。
const val UNIX_LINE_SEPARATOR = "\n"
加上 const,就會轉換為 java 的 public static final String UNIX_LINE_SEPARATOR = "\n";
extension functions and properties,將 method 增加到另一個 class 裡面
因 kotlin 通常會增加到某一個既有的 Java project 裡面,故須要將 kotlin 跟既有的 java code 整合在一起。可使用 extension function,在 class 外面訂製一個新的 function
package strings
fun String.lastChar(): Char = this.get(this.length - 1)
String
稱為 receiver type,也就是要擴充的 class 名稱,this
稱為 receiver object
以下的例子,String
為 receiver type,"Kotlin"
為 receiver object
>>> println("Kotlin".lastChar())
n
也可以將 this 省略
package strings
fun String.lastChar(): Char = get(length - 1)
extension functions 必須要另外 import 才能使用
import strings.lastChar
// import strings.*
val c = "Kotlin".lastChar()
也可以在 import 後,用 as
產生別名,這可以解決 conflict 問題
import strings.lastChar as last
val c = "Kotlin".last()
從 java 呼叫 extension functions
如果剛剛的 code 是放在 StringUtil.kt
裡面
/* Java */
char c = StringUtilKt.lastChar("Java");
以 utility class 作為 extensions
將 joinToString 擴充到 Collection
fun <T> Collection<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()
}
fun main(args: Array<String>) {
val list = listOf(1, 2, 3)
println(list.joinToString(separator = "; ",
prefix = "(", postfix = ")"))
}
改成這樣,就只接受 String 的 collection
fun Collection<String>.join(
separator: String = ", ",
prefix: String = "",
postfix: String = ""
) = joinToString(separator, prefix, postfix)
extension functions 無法被 overriding
在 class 裡面定義的 function 可以被 overriding
open class View {
open fun click() = println("View clicked")
}
class Button: View() {
override fun click() = println("Button clicked")
}
fun main(args: Array<String>) {
val view: View = Button()
view.click()
}
執行結果
Button clicked
extension functions 無法被 overriding,因為 extension function 是 static method
open class View {
open fun click() = println("View clicked")
}
class Button: View() {
override fun click() = println("Button clicked")
}
fun View.showOff() = println("I'm a view!")
fun Button.showOff() = println("I'm a button!")
fun main(args: Array<String>) {
val view: View = Button()
view.showOff()
}
執行結果
I'm a view!
extension properties
extension property 就像是 property 再加上 receiver type,壹定要定義 getter,不能夠定義初始值,在 StringBuilder.lastChar 是定義為 var,因為 StringBuilder 裡面存的資料是會變動的。
val String.lastChar: Char
get() = get(length - 1)
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value: Char) {
this.setCharAt(length - 1, value)
}
fun main(args: Array<String>) {
// 使用起來就像是 property
println("Kotlin".lastChar)
val sb = StringBuilder("Kotlin?")
sb.lastChar = '!'
println(sb)
}
其他支援 collections 的功能
Kotlin standard library 其他支援 collection 的功能
- 關鍵字
vararg
: 可定義有任意參數數量的 function - infix calls
- destructuring declarations,可 unpack composite value 為 multiple variables
為什麼 Kotlin 跟 Java 使用相同的 collection class 但是卻有更多功能?原因是 last, max function 都是以 extension function 實作的。
>>> val strings: List<String> = listOf("first", "second", "fourteenth")
>>> strings.last()
fourteenth
>>> val numbers: Collection<Int> = setOf(1, 14, 2)
>>> numbers.max()
14
fun <T> List<T>.last(): T { /* returns the last element */ }
fun Collection<Int>.max(): Int { /* finding a maximum in a collection */ }
- varargs: 讓 function 可接受不定數量的參數
fun listOf<T>(vararg values: T): List<T> { ... }
直接在參數前面加上 *
,稱為 spread operator
fun main(args: Array<String>) {
val list = listOf("args: ", *args)
println(list)
}
- infix call
// regular way 呼叫函數
1.to("one")
// infix call
1 to "one"
以下是簡化版 infix 的定義方式
infix fun Any.to(other: Any) = Pair(this, other)
Pair 是 kotlin standard library 的 class,代表一組 elements,例如 val (number, name) = 1 to "one"
可將 1 to "one"
轉換為 Pair 再提供給 (number, name) 設定變數
for ((index, element) in collection.withIndex()) {
println("$index: $element")
}
fun <K, V> mapOf(vararg values: Pair<K, V>): Map<K, V>
val map = mapOf(1 to "one", 7 to "seven", 53 to "fifty-three")
使用 strings 及 regular expressions
- splitting strings
kotlin 使用跟 java 一樣的 regular expression syntax
fun main(args: Array<String>) {
println( "12.345-6.A".split("\\.|-".toRegex()) )
println( "12.345-6.A".split(".", "-") )
// [12, 345, 6, A]
}
- 將 file full path name 切割為 directory, filename, extension
使用標準函式庫的 substring functions
fun parsePath(path: String) {
val directory = path.substringBeforeLast("/")
val fullName = path.substringAfterLast("/")
val fileName = fullName.substringBeforeLast(".")
val extension = fullName.substringAfterLast(".")
println("Dir: $directory, name: $fileName, ext: $extension")
}
fun main(args: Array<String>) {
parsePath("/Users/yole/kotlin-book/chapter.adoc")
}
使用 regular expression 的方法:regular expression 以 triple-quoted string 撰寫,這樣就不需要 escape characters,所以直接寫 \.
就可以了
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")
}
}
fun main(args: Array<String>) {
parsePath("/Users/yole/kotlin-book/chapter.adoc")
}
- multiline triple-quoted strings
在 triple-quoted string 裡面包含所有的字元,trimMargin 可去掉 .
前面所有字元
val kotlinLogo = """| //
.|//
.|/ \"""
fun main(args: Array<String>) {
println(kotlinLogo.trimMargin("."))
}
結果為
| //
|//
|/ \
用 embedded expression 填寫 $
字元
>>> val price = """${'$'}99.9"""
>>> println(price)
$99.9
利用 local functions 及 extensions 簡化程式碼
以下程式碼中,name 及 address 欄位檢查部分的程式碼有重複
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")
}
// Save user to the database
}
fun main(args: Array<String>) {
saveUser(User(1, "", ""))
}
建立一個新的 local function: validate,利用該 local function 檢查欄位
class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
fun validate(user: User,
value: String,
fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException(
"Can't save user ${user.id}: empty $fieldName")
}
}
validate(user, user.name, "Name")
validate(user, user.address, "Address")
// Save user to the database
}
fun main(args: Array<String>) {
saveUser(User(1, "", ""))
}
因為是 local function,可直接使用 user 這個物件
class User(val id: Int, val name: String, val address: String)
fun saveUser(user: 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")
// Save user to the database
}
fun main(args: Array<String>) {
saveUser(User(1, "", ""))
}
將 validate 改成 extension function,讓程式碼更簡潔
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: empty $fieldName")
}
}
validate(name, "Name")
validate(address, "Address")
}
fun saveUser(user: User) {
user.validateBeforeSave()
// Save user to the database
}
fun main(args: Array<String>) {
saveUser(User(1, "", ""))
}
沒有留言:
張貼留言