Kotlin学习3
1kotlin可空性
提前在编译期强迫重视null问题
除非另有规定,变量不能为null值。
为了避免NullPointerException,Kotlin的做法是不让我们给非空类型变量赋null值,但null在Kotlin中依然存在。
fun main(){
var str = "asds"
str = null //报错!!
var str:String ?="asdasd"
str = null
}
安全调用操作符
Kotlin区分可空类型和非可空类型,所以你要一个可空类型变量运行,而他有可能不存在,对于这种潜在危险,编译器时刻警惕着。为了应对这种风险,Kotlin不允许你在可控类型值上调用函数,除非你主动接受安全管理
选项一:安全调用操作符(?.)
如果遇到null值,他就跳过函数调用,而不是返回null。
fun main(){
val str = readLine()?.capitalize()
println(str)
}
使用带let的安全调用
安全调用允许在可空类型上调用函数,但是如果还想做点额外的事比如创建新值,或判断不为null就调用其他函数,怎么办?可以使用带let函数的安全调用操作符。你可以在任何类型上调用let函数,他的主要作用是让你在指定的作用域内定义一个或多个变量。
fun main(){
val str = readLine()?.let{
if(it.isNotBlank()){
it.capitalize()
}else{
"butterfly"
}
}
println(str)
}
非空断言操作符(!!.)
选项二:非空断言操作符
!!.又称感叹号操作符,当变量是null时,会抛出KotlinNullPointerException。
fun main(){
val str = readLine()!!.capitalize()
println(str)
}
当变量是null时 ,会去执行函数。
对比使用if判断null值情况
选项三:使用if判断null值情况
fun main(){
var str = readLine()
if(str!= null){
str = str.capitalize()
}else{
println("为null。")
}
//安全操作符支持链式调用
str = str?.capitalize()?.plus(" is great.")
println(str)
}
空合并操作符(?:)
?:操作符的意思是,如果左边的求值结果为null,就用右边的结果值。
val str = str ?: "butterfly"
空合并操作符也可以和let函数一起使用来替代if/else语句
fun main(){
var str = readLine()
str = str?.let{it.capitalize()}?:"butterfly"
println(str)
}
异常处理与自定义异常
fun main(){
car number:Int ?=null
try{
checkOperation(number)
number!!.plus(1)//这里会报异常
}catch(e:Exception){
println(e)
}
}
fun checkOperation(number:Int?){
number?:throw UnskilledException ()
}
//自定义异常
class UnskilledException :IlldgalArgumentException("操作不当")
先决条件函数
可以抛出带自定义信息的异常,可以用它定义先决条件,条件必须满足,目标代码才能执行。

fun main(){
car number:Int ?=null
try{
checkOperation(number)
number!!.plus(1)//这里会报异常
}catch(e:Exception){
println(e)
}
}
fun checkOperation(number:Int?){
checkNotNull(number,{"Something error"})
}
substring
字符串截取,substring函数支持IntRange类型(表示一个整数范围的类型)的参数,until创建的范围不包括上限值
const cal NAME = "Jimmy's friend"
fun main(){
val index = NAME.indexOf('\'')
//NAME.substring(0,index)
val str= NAME.substring(0 until index)
println(str)
}
split
split函数返回的是List集合数据,List集合又支持解构语法特性,它允许你在一个表达式里给多个变量赋值,解构常用来简化变量的赋值
const val NAMES = "jack,jacky,jason"
fun main(){
val data :List<String>= NAMES.split(',')
//data[0]
val(origin,dest,proxy) = NAMES.split(',')
println("$origin $dest $proxy")
}
replace
fun main(){
val str1 = "The people's Republic of China."
//第一个参数是正则表达式,用来决定要替换那些字符
//第二个参数是匿名函数,用来确定该如何替换正则表达式搜索到的字符
val str2:String = str1.replace(Regex("[aeiou]")){
when(it.value){
"a"->"8"
"e"->"6"
"i"->"9"
"o"->"1"
"u"->"3"
else ->it.value
}
}
println(str1)
println(str2)
}
==与===比较
==检查两个字符串中的字符是否匹配;
===检查两个变量是否指向内存堆上同一个对象。
fun main(){
val str1 = "Jason"
val str2= "jason".capitalize()
println("$str1, $str2")
println(str1==str2)//true
println(str1===str2)//false
}
字符串遍历
fun main(){
val str1 = "The people's Republic of China.".forEach{
print("$it *")
}
数字类型的安全转换函数
kotlin所有数字类型都是有符号的

kotlin提供了toDoubleOrNull和toIntOrNull这样的安全转换函数,如果树脂不能正确转换,与其触发异常不如干脆返回null值。
fun main(){
//抛出异常
//val number:Int = "8.98".toInt()
val number:Int ?= "8.98".toIntOrNull()
println(number)
}
Double转int与类型格式化
double转int
精度损失与四舍五入
fun main(){
//精度损失
println(8.956756.toInt())//8
//四舍五入
println(8.956756.roundToInt())//9
//格式化
val s = "%.2".format(8.956756)
println(s)//8.96 四舍五入了
}
标准库函数
apply
可以看做一个配置函数,你可以传入一个接收者,然后调用一系列函数来配置它以便使用,如果提供lambda给apply执行,他会返回配置好的接收者。
fun main(){
//配置一个file实例
val file1 = File("E://i have a dream.txt")
file1.setReadable(true)
file1.setWritable(true)
file1.setExrcutable(false)
//使用apply
val file2 = File("E://i have a dream.txt").apply{
setReadable(true)
setWritable(true)
setExrcutable(false)
}
}
可以看到,调用一个函数类配置接受者时,变量名就省略掉了,这是因为在lambda表达式里,apply能让每个配置函数都作用于接收者,这种行为有时又叫做相关作用域,因为lambda表达式里的所有函数调用都是针对接收者的,或者说,他们是针对接收者的隐式调用。
let
能使某个变量作用于其lambda表达式里,让it关键字能引用它。let与apply比较,let会把接收者传递给lambda,而apply什么都不传,匿名函数执行完,apply会返回当前接收者,而let会返回lambda的最后一行
fun main(){
val res = listOf(3,2,1).first().let{
it * it
}
println(res)//9
println(format("Jack"))
}
fun format(guestName:String?):String{
return guestName?.let{
"Welcome, $it."
}?:"what's your name?"
}
run
光看作用于来说,run和apply差不多,但run函数不返回接收者,run返回的是lambda结果,也就是true或者false
import java.io.File
fun main(){
val file = File("E://i have a dream.txt")
val result :Boolean = file.run{
readText().contains("great")
}
println(result)//true
}
run 也可以用来执行函数引用
fun main (){
val result = "The people's Republic of China.".run(::isLong)
println(result)
//当有多个函数调用,run的优势就显而易见了
"The people's Republic of China."
.run(::isLong)
.run(::showMessage)
.run(::println)
}
fun isLong(name :String) = name.length>=10
fun showMessage(isLong:Boolean):String{
return if(isLong){
"Name is too long."
}else{
"please rename."
}
}
with
with函数是run函数的变体,他们功能行为是一样的,但是with的调用凡事不同,需要值参作为其第一个参数传入
fun main(){
val res = "The people's Republic of China.".run{
length>=10
}
val isToolong:Boolean = with("The people's Republic of China."){
length>=10
}
}
also
和let类似,也是把接收者作为值参传给lambda,但有一点不同,also返回接收者对象,而let返回lambda结果。因为这个差异,also尤其适合针对同一原始对象,利用副作用做事,既然also返回的是接收者对象,你就可以基于原始接受者对象执行额外的链式调用
import java.io.File
fun main(){
var fileContents:List<String>//没有初始化
File("E://i have a dream.txt")
.also{
println(it.name)
}.also{
fileContents = it.readLines()//初始化
}
println(fileContents)
}
takeIf
takeIf函数需要判断lambda中提供的条件表达式,给出true或false结果。如果是true,从takeIf函数返回接收者对象,如果是false,则返回null。如果需要判断某个条件是否满足,再决定是否可以赋值变量或者执行某项任务,takeIf就非常有用,概念上讲,takeIf函数类似if语句,但他的优势是可以直接在对象实例上调用,避免了临时变量赋值的麻烦。
import java.io.File
fun main(){
val res :String? = File("E://i have a dream.txt")
.takeIf{
it.canRead()&&it.canWrite()
}
?.readText()
println(res)
}
takeUnless
takeIf的辅助函数takeUnless,只有判断你给定条件结果是false时,takeUnless才会返回原石接收者对象。
import java.io.File
fun main(){
val fileContent3 :String? = File("E://i hava a dream.txt")
.takeUnless{it.isHidden}
?.readText()
println(fileContent3)
}