数字类型
和 java 一样,Kotlin 中所有数字类型都是有符号的,也就是说既可以表示正数,也可以表示负数。
安全转换函数
与 java 不一样,kotlin 提供了 toDoubleOrNull 和 toIntOrNull 这样的安全转换函数,如果数值不能正确转换,与其触发异常不如干脆返回 null 值。
示例:toIntOrNull。因为可能会返回空,所有 number 是可空类型。
fun main() {
// "8.98".toInt() 会抛异常
//val number : Int = "8.98".toInt()
val number : Int? = "8.98".toIntOrNull() // TODO 当没有转换成功,则直接返回 null
println(number)
}
Double 转 Int 与类型格式化
Double.toInt() 会直接丢弃小数点后的数值,损失了精度。Double.roundToInt() 是四舍五入的方式。
import kotlin.math.roundToInt
fun main() {
println(8.964565.toInt()) // 直接丢弃小数点后的值
println(8.964565.roundToInt()) //四舍五入
}
Double 类型格式化。格式化字符串是一串特殊字符,它决定该如何格式化数据。
示例:
fun main() {
val str = "%.2f".format(0.2694489)
println(str)
}
解释说明:%.2f 即保留2位小数,并且是四舍五入。返回的结果是一个 String。
标准库函数
1) apply()
apply 函数可看作一个配置函数,你可以传入一个接收者,然后调用一系列函数来配置它以便使用,如果提供 lambda 给 apply 函数执行,它会返回配置好的接收者。
解释说明:file1 是我们的通常写法,创建一个 File 对象,然后用这个对象来调用 read/write 等方法。file2 的写法就是通过配置函数的方式。将 read/write/execute 等方法配置在 apply() 函数中,然后返回给 file2 对象。将 File 对象传入到 apply 中(即那个 this),然后配置它。配置完之后再把这个 File 对象返回,即 file2(file2 就是接收者对象)。
可以看到,调用一个函数类配置接收者时,变量名就省掉了(即省掉 file1.setReadable() 中的 file1,而是直接调用 setReadable())。这是因为,在 lambda 表达式里,apply 能让每个配置函数都作用于接收者,这种行为有时又叫做相关作用域,因为 lambda 表达式里的所有函数调用都是针对接收者的,或者说,它们是针对接收者的隐式调用。
2) let()
let 函数能使某个变量作用于其 lambda 表达式里,让 it 关键字能引用它。let 与 apply 比较,let 会把接收者传给 lambda,而 apply 什么都不传,匿名函数执行完,apply 会返回当前接收者,而 let 会返回 lambda 的最后一行。
示例:求一个集合里第一个数的平方
解释说明:这个 it 就是 first() 获取的集合中的第一个元素,并且把它传入到了 lambda 表达式中。
如果不使用 let() 函数,那么计算一个数的平方,我们可能会这样写:
所以,使用 let 函数会方便很多。
3) run()
光看作用域行为,run 和 apply 差不多,但与 apply 不同,run 函数不返回接收者,run 返回的是 lambda 执行结果。
val file = File("D://run.txt")
val txt : Boolean = file.run {
readText().contains("xxx")
}
解释说明:readText() 读取 run.txt 文件的内容,通过 contains() 查看是否包含 "xxx",如果包含则返回 true,不包含则返回 false。
run() 也能用来执行函数引用
fun main() {
val isLong = "The People's Republic of China".run(::isLong)
println(isLong)
}
fun isLong(name : String) : Boolean{
return name.length >= 10
}
同时,支持多个函数链式调用。
示例:
fun main() {
val isLong = "The People's Republic of China".run(::isLong)
println(isLong)
"The People's Republic of China"
.run {::isLong}
.run {::showMessage}
.run {::printMes}
}
fun isLong(name : String) : Boolean{
return name.length >= 10
}
fun showMessage(isLong : Boolean) : String{
return if(isLong){
"Name is too long!"
}else{
"Please rename!"
}
}
fun printMes(Message : String){
println(Message)
}
解释说明:isLong() 的结果(Boolean)作为参数传入 showMeage() 中,showMessage() 中然后的结果(String)又作为参数传入 printMes() 中。
4) with()
with 函数是 run 函数的变体,他们的功能行为是一样的,但 with 的调用方式不同,调用 with 时需要值参作为其第一个参数传入。
5) also()
also 函数和 let 函数功能相似,和 let 一样,also 也是把接收者作为值参传给 lambda(即那个 it),但有一点不同:also 返回接收者对象,而 let 返回 lambda 结果。因为这个差异,also 尤其适合针对同一原始对象,利用副作用做事,既然 also 返回的是接收者对象,你就可以基于原始接收者对象执行额外的链式调用。
示例:
fun main() {
var fileContexts : List
File("D://also.txt")
.also {
// 先打印 文件名称
println(it.name)
}
.also {
// 再把文件里的元素一行一行的读出来,赋值给 fileContexts
fileContexts = it.readLines()
}
}
解释说明:第一个 also 打印 名字,然后返回 File 对象,所以第二个 also 也是同一个 File 对象再调用。
6) takeIf()
和其它标准函数有点不一样,takeIf 函数需要判断 lambda 中提供的条件表达式,给出 true 或 false 结果,如果判断结果为 true,从 takeIf 函数返回接收者对象,如果是 false,则返回 null。如果需要判断某个条件是否满足,再决定是否可以赋值变量或执行某项任务,takeIf 就非常有用。概念上讲,takeIf 函数类似于 if 语句,但它的优势是可以直接在对象实例上调用,避免了临时变量赋值的麻烦。
实例:
val readText = File("D://takeIf.txt")
.takeIf { it.exists() && it.canRead() }
?.readText()
解释说明:当 {it.exists() && it.canRead()}(匿名函数) 结果为 true, 那么这一句 takeIf { it.exists() && it.canRead() } 返回接收者对象(即File),然后再调用 readText();如果{it.exists() && it.canRead()}的返回结果为 false,那么 takeIf() 返回 null。所以这里调用 readText() 函数时用到了安全调用操作符?.。
注意:匿名函数{it.exists() && it.canRead()}是作为了 takeIf() 的参数,这里用的是简略写法。
7) takeUnless()
takeIf 辅助函数 takeUnless,只有判断你给定的条件结果是 false 时,takeUnless 才会返回原始接收者对象。