在Java中 NullPointerException对于我们开发者已经司空见惯,带给我们太多不必要的麻烦,Kotlin对此做了改良,Kotlin更多地把运行时可能会出现 null问题,以编译时报错的方式,提前在编译期强迫我们重视起来,而不是等到运行时报错,防患于未然,提高了程序的健壮性。
对于 null值问题,Kotlin反其道而行之,除非另有规定,否则不允许变量为 null,这样一来,因为 null问题导致的运行时崩溃就从根源上得到了解决。
如果我们给变量赋值为 null,编译器会报红提示我们修改代码。
可空性为了避免空指针异常,Kotlin的做法是不让我们给非空类型变量赋空值,但 null在Kotlin中依然存在着。
fun main() {
// ? 在这里的意义就是表明声明的 str是可空字符串
var str: String? = "honey"
str = null
println("input:$str")
}
Kotlin区分可空类型和非可空类型,所以要运行可空类型变量,而它又可能为 null值,对于这种潜在风险,编译器时刻警惕着。为了应对这种风险,Kotlin不允许我们使用可空类型变量调用函数,除非我们主动接手安全管理。
"?."会告诉编译器,如果是 null值,就跳过函数调用,而不是返回 null。
str = null
val newStr = str?.capitalize()
println(newStr)
从下面的示例中,我们确实看到了当是null值是,会跳过函数调用:
fun main() {
val str = null
println(str?.capitalized())
}
private fun String.capitalized(): String {
println("我被调用了")
return if (isNotEmpty() && this[0].isLowerCase()) substring(0, 1).toUpperCase() + substring(1) else "String is null"
}
安全操作符“let”
安全调用允许我们使用可空类型值调用函数,但是我们还想做点额外的事情,比如变量为空白字符串时赋新值,或者不为 空白字符串时调用其他函数,此时 let就该出场表演了。
fun main() {
var str: String? = "honey"
//var str: String? = null //为 null时安全调用let函数会直接跳过
//str = ""
str = str?.let {
if (it.isNotBlank()) {
it.capitalize()
} else {
"str is blank"
}
}
println("str = $str")
}
安全操作调用符"!!."
"!!."是非空断言操作符,也叫感叹号操作符,当调用者为 null时,会抛出 KotlinNullPointerException。
fun main() {
var str: String? = ""
str = null
str!!.capitalize()
}
上面的代码会跑出空指针异常:
安全调用操作符 VS if我们也可以跟Java中一样,使用 if判断 null值情况,但相比之下,安全调用操作符使用更灵活,代码更简洁,我们可以使用安全操作符进行多个函数的链式调用。
fun main() {
var str: String? = "luffy"
//str = null
//使用 if判断
if(str != null) str = str.capitalize() else str = "str is null"
println("str = $str")
//使用安全调用操作符链式调用
println(str?.capitalize().plus(" is great."))
}
空合并操作符
"?:"操作符的作用是,如果它左边的表达式求值结果为 null ,就使用右边的结果值,否则用左边的结果值。
fun main() {
var str: String? = "luffy"
str = null
println(str ?: "honey")
}
空合并操作符也可以和 let函数结合使用,代替if/else:
fun main() {
var str: String? = "luffy"
str = null
str = str?.let { it.capitalize() } ?: "Honey"
println(str)
}
异常
import kotlin.IllegalArgumentException
fun main() {
var str: String? = null
try {
checkOperation(str)
str!!.capitalize()
} catch (e: KotlinNullPointerException) {
println(e.cause?.message)
}
}
fun checkOperation(str: String?) {
str ?: throw UnskilledException()
}
class UnskilledException() : IllegalArgumentException("Improper operation")
先决条件函数
Kotlin标准库提供了一些便利函数,使用这些内置函数,可以抛出带自定义信息的异常,这些便利函数 叫做先决条件函数,我们可以用它定义先决条件,条件必须满足,目标代码才能执行。
fun main() {
var num: Int? = null
try {
checkNotNull(num){"Improper operation"}
num!!.plus(1)
} catch (e: KotlinNullPointerException) {
println(e.cause?.message)
}
}
字符串操作
substring
字符串截取,substring函数支持IntRang类型(表示一个整数范围的类型)的参数,until创建的范围不包括上限值。
const val WORDS = "Luffy's friend"
fun main() {
val index = WORDS.indexOf(''')
var str1 = WORDS.substring(0, index)
//Kotlin中,substring函数支持IntRang类型
var str2 = WORDS.substring(0 until index)
println("str1 = $str1")
println("str2 = $str2")
}
split
split函数返回的是List集合数据,List集合有支持解构语法特性,它允许我们在一个表达式里给多个变量赋值,解构常用来简化变量的赋值。
const val NAMES = "Luffy,Honey,Kitty"
fun main() {
val names: List = NAMES.split(',')
println(names[0])
//解构语法特性
val (origin, dest, proxy) = NAMES.split(',')
println("$origin,$dest,$proxy")
}
replace
fun main() {
//加密替换一个字符串
val str1 = "The people's Republic of China."
//第一个参数是正则表达式,用来指定要替换哪些字符
//第二个参数是匿名函数,用来决定当匹配到正则中的字符时,该怎么处理
val str2 = str1.replace(Regex("[aoeiu]")) {
when (it.value) {
"a" -> "8"
"o" -> "6"
"e" -> "5"
"i" -> "2"
"u" -> "0"
else -> it.value
}
}
println("str1 = $str1")
println("str2 = $str2")
}
字符串比较
在Kotlin中,用"=="检查两个字符串中的字符是否匹配,用"==="检查两个变量是否指向内存堆上同一对象,而在java中,"=="用作引用的比较
,做内容比较时用equals方法。
fun main() {
val name1 = "Honey"
val name2 = "Honey"
println("name1 == name2 : ${name1 == name2}")
println("name1 === name2 : ${name1 === name2}")
val name3 = "Luffy"
val name4 = "luffy".capitalize()
println("name3 == name4 : ${name3 == name4}")
println("name3 === name4 : ${name3 === name4}")
}
执行结果:
上面示例代码执行结果有没有疑问,这个就涉及到Java的内存模型了,字符串是放在常量池中的,因为字符串也是常量,所以示例代码中的name1和name2都指向了同一字符串对象“Honey”,所以两个输出都是true;而“Luffy”和“luffy”是两个不同的字符串常量,虽然"luffy".capitalize()的执行结果也是“Luffy”,但是这期间生成了新的字符串对象:
看下 capitalize()的源码就明白了,这里进行了字符串的拼接,所以生成了新的字符串对象,所以name3和name4虽然内容一样,但是指向了不同的对象。
foreach遍历字符fun main() {
val saying = "For you, a thousand times over."
println("* * *")
saying.forEach { println("* $it *") }
println("* * *")
}
数字类型
和java一样,Kotlin的所有数字类型都是有符号的,即可以表示正数,也可以表示负数。
安全转换函数Kotlin提供了 toDoubleOrNull和 toIntOrNull这样的安全转换函数,如果数值不能正确转换,与其触发异常不如干脆返回null值。
import kotlin.math.roundToInt
fun main() {
//val result = "66.88".toInt()//抛出 java.lang.NumberFormatException
val result1 = "66.88".toIntOrNull()
println("result1:$result1")
val result2 = "66.88".toDouble()
println("result2:$result2")
//精度损失
val result4 = 98.66.toInt()
println("result4:$result4")
//四舍五入
val result5 = 98.66.roundToInt()
println("result5:$result5")
}
Double类型格式化
格式化字符串是一串特殊的字符,它绝对如何格式化数据。
fun main() {
val result = "%.2f".format(5.20522)
println("result:$result")
}
注意到上述代码输出结果是:5.21,说明格式化之后的Double数据会四舍五入 。



