【?,!!,?:,:: ,->,$,_(下划线),@,as,..,{},,,】
1. 【?】
" ?" 修饰在成员变量的类型后面,表示这个变量可以为null,系统在任何情况下不会报它的空指针异常。
" ? " 修改在对象后面代表该对象如果为null则不处理后面逻辑
如果我们定义的变量是一个可以为空的类型,则要用【String?】
//在变量类型后面加上问号,代表该变量是可空变量
var name: String? = "eryang" //(PS:var和val关键字代表的意义会在另一篇幅中去着重讲)
name= null // 编译正确
var name: String = "eryang"
name= null // 编译错误
val len = name.length // 错误:变量“b”可能为空
val len = name?.length //如果 name非空,就返回 name.length ,否则返回 null,这个表达式的类型是 Int? 。
?.
let函数和?.同时使用来处理可空表达式
person?.let{
//内部的it一定是非空的
}
//如果person为空就不会调用let函数
//如果person不为空才会调用let函数,所以?.和let函数配合使用可以很方便的处理可空表达式
person?.let {
println(it.name) //输出name
}
下面再举个例子来深刻理解下let函数:
// 没有使用let函数的代码是这样的,看起来不够优雅
mVideoPlayer?.setVideoView(activity.course_video_view)
mVideoPlayer?.setControllerView(activity.course_video_controller_view)
mVideoPlayer?.setCurtainView(activity.course_video_curtain_view)
// 使用let函数后的代码是这样的
mVideoPlayer?.let {
it.setVideoView(activity.course_video_view)
it.setControllerView(activity.course_video_controller_view)
it.setCurtainView(activity.course_video_curtain_view)
}
2. 【!!】(Kotlin不推荐使用非空断言,通常我们会用?:来防止程序运行时报空指针异常而崩溃)
" !! "放在对象后面代表该对象如果为null则抛出异常
" !! "放在方法传递实参后面,也是代表不能为null,为null会抛异常
val len = name!!.length//如果name不为空,则返回name.length,如果name为空,则抛出异常NullPointerException
使用断!!可以很方便的在抛出空指针异常的时候定位到异常的变量的位置,但是千万不要连续使用断言!!
student!!.person!!.name//如果报空指针异常了则无法判断到底是student为空还是person为空,所以不要连续使用断言!!
" !!. "的用法就是相当于Java里的if()else()判断null
val nullClass: NullClass?=null
nullClass!!.nullFun()
转化成JAVA代码
NullClass nullClass = null;
if (nullClass!=null) {
nullClass.nullFun();
}else {
throw new NullPointerException();
}
!!
3.【?:】Elvis操作符
对象foo ?: 对象bar 表达式,意思为: 当对象 foo值为 null 时,那么它就会返回后面的对象 bar,否则返回foo。
?:
val len = name?.length ?: -1//当name不为空时,返回name.length,当name为空时,返回-1
val roomList: ArrayList<Room>? = null
val mySize= roomList?.size ?: 0 //mySize 的值就为0,因为 roomList?.size 为空,
val roomList: ArrayList<FPXBean>? = null
if (roomList?.size ?: 0 > 0) { // 这一行添加了?:
Log.d("TAG", "-->> 列表数不是0")
}
4. 【::】
双冒号操作符 表示把一个方法当做一个参数,传递到另一个方法中进行使用,通俗的来讲就是引用一个方法
反射
利用 ::,甚至可以享受到优于 Java 的反射特性。
inline fun <reified T> T.foo3(string: String) {
Log.e(T::class.simpleName, string)
}
这里暂时不需要理会 inline 和 reified,我们看到,直接使用泛型 T:: 即可反射获得其内部属性,如 class,constructor 等,这在 Java 中是不可能做到的。
//得到类的Class对象
startActivity(Intent(this@KotlinActivity, MainActivity::class.java))
::方法名
双冒号跟方法名作用就是将方法作为参数传入。
class ColonMagic {
/**
* 不需要参数
*/
private fun sayNoWords() {
println(" no msg ")
}
/**
* 一个参数
* message:String类型
*/
private fun say(message: String) {
println(message)
}
/**
* 两个参数
* msg: String类型
* dosay: (String) -> Unit 一个参数为String不需要返回值的方法体
*/
private fun peopleDo(msg: String, doSay: (String) -> Unit) {
//doSay(msg)调用的就是传入的say方法,即say(msg),只不过参数名是doSay本质是say方法
//此处打印出来 I say ! 日志
doSay(msg)
}
fun people() {
//调用peopleDo方法,并传入参数 “I say !” 和 say方法体
//此处 ::say 是将say方法作为参数传入peopleDo方法
//此处只是将say作为参数,而不会调用say方法,也就不会println(message),不会输出日志
peopleDo("I say !", ::say)
//此处报错,因为需要的是(String) -> Unit; 一个String参数,无返回值的方法体
//但是sayNoWords方法是没有参数的,所以会报错
peopleDo("I say !", ::sayNoWords)
}
}
fun main(msg: Array<String>) {
ColonMagic().people();
}
mian方法中调用 ColonMagic().people();打印日志如下:
I say !
方法作为参数传入时不会被调用,所以最后只有一行 I say !日志。
fun main() {
foo2("xxx", this::bar2) //right
foo2("xxx", this::bar1) //wrong, compile fail
}
fun bar1() {
print("bar1")
}
fun foo2(content: String, body: (String) -> Unit) {
body(content)
}
fun bar2(string: String) {
print(string)
}
作为参数的函数,该函数的参数类型和返回值类型一定要和规定的一致。上述的例子中bar1()是没有任何参数类型的,而foo2中第二个参数body:(String) -> Unit ,需要一个字符串类型,所以 foo2("xxx", this::bar1) 这种写法是错误的; foo2("xxx", this::bar1) 这种写法是对的,因为bar2(string: String) 的参数类型是一个字符串,符合参数类型和返回值类型一定要和规定的一致。
- “->”符号操作符,单从形态上看,是一种流向和对应的关系。即前面的语句执行之后,将执行流转到指向的语句,并且是对应的。
6.【$】符合和多行输入符
val i = 10
println("i = $i") // prints "i = 10"
val s = "abc"
println("$s.length is ${s.length}") // prints "abc.length is 3"
如上面的代码中,要把" i "连接到字符串中,模板表达式以美元符号($)开头,由一个简单名称组成: $i
//三引号的形式用来输入多行文本
val str = """
one
two
"""
//等价于
val str = "one\ntwo"
三引号之间输入的内容将被原样保留,之中的单号和双引号不转义,其中的不可见字符比如/n和/t都会被保留。(\t是补全当前字符串长度到8的整数倍,最少1个最多8个空格,补多少要看你\t前字符串长度,比如当前字符串长度10,那么\t后长度是16,也就是补6个空格,如果当前字符串长度12,此时\t后长度是16,补4个空格)
7. 【_】(下划线)
在Kotlin中,可以使用一个下划线字符(_)作为lambda或表达式函数的参数的名称,或作为解构条目的名称。
①作为lambda函数的参数名称
fun main(args: Array<String>) {
val aa = mapOf(1 to "a",2 to "B")
aa.forEach { key, value -> println("value:$value")
}
在上述示例中,只是用到了value值,key并没有用到。这样,我们就想不在声明key,那么就需要使用下划线字符(_)作为key替代,即:
fun main(args: Array<String>) {
val aa = mapOf(1 to "a",2 to "B")
aa.forEach { _, value -> println("value:$value")
}
②作为解构声明的参数
解构声明就是将一个对象解构(destructure)为多个变量,也就是意味着一个解构声明会一次性创建多个变量。尽管这样很方便,但是,如果用不到的变量必然也必须得声明,从而造成了变量的冗余。Kotlin-解构声明
data class Person(var age: Int, var name: String)
//Peron声明了 age,name两个变量。解构时如果只需要age这一个变量时
val person = Preson(18, "eryang")
val (age, _) = person
③数字字面值中的下划线
Kotlin的数字面值可以使用下划线来分隔数值进行分组:
val oneMillion = 1_000_000
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
fun main(args: Array<String>) {
// Log: 1000000
println(oneMillion)
// Log: ffecde5e
println(hexBytes.toString(16))
// Log: 11010010011010011001010010010010
println(bytes.toString(2))
}
8.【@】
①限定this的类型
class User {
inner class State{
fun getUser(): User{
//返回User
return this@User
}
fun getState(): State{
//返回State
return this@State
}
}
}
②作为标签
跳出双层for
loop@ for (itemA in arraysA) {
var i : Int = 0
for (itemB in arraysB) {
i++
if (itemB > 2) {
break@loop
}
println("itemB:$itemB")
}
}
命名函数自动定义标签
fun fun_run(){
run {
println("lambda")
}
var i: Int = run {
return@run 1
}
println("$i")
//匿名函数可以通过自定义标签进行跳转和返回
i = run (outer@{
return@outer 2
})
println(i)
}
从forEach函数跳出
fun forEach_label(ints: List<Int>)
{
var i =2
ints.forEach {
//forEach中无法使用continue和break;
// if (it == 0) continue //编译错误
// if (it == 2) /*break //编译错误 */
print(it)
}
run outer@{
ints.forEach {
if (it == 0) return@forEach //相当于在forEach函数中continue,实际上是从匿名函数返回
if (it == 2) return@outer //相当于在forEach函数中使用break,实际上是跳转到outer之外
}
}
if (i == 3)
{
//每个函数的名字代表一个函数地址,所以函数自动成为标签
return@forEach_label //等同于return
}
}
9.【as】类型转换运算符
①“不安全的” 类型转换操作符
如果类型转换不成功,类型转换操作符通常会抛出一个异常。因此,我们称之为 不安全的(unsafe)。在 Kotlin 中,不安全的类型转换使用中缀操作符 as
val y = null
val x: String = y as String
// 输出
输出
注意 null 不能被转换为 String,因为这个类型不是 可为 null 的(nullable),也就是说,如果 y 为 null,上例中的代码将抛出一个异常。为了实现与 Java 相同的类型转换,我们需要在类型转换操作符的右侧使用可为 null 的类型,比如:
val y = null
val x: String? = y as String?
println("x = $x") // x = null
上述代码,表示允许 String 可空,这样当 y = null 时,不会抛异常;但是,当类型转换失败时,还是会崩溃,如下:
val y = 66
val x: String? = y as String?
image.png
②“安全的” (nullable) 类型转换操作(as?)
为了避免抛出异常,你可以使用 安全的 类型转换操作符 as?,当类型转换失败时,它会返回 null,但不会抛出异常崩溃:
val y = 66
val x: String? = y as? String
println("x = $x") // x = null
val y = null
val x: String? = y as? String
println("x = $x") // x = null
尝试把值转换成给定的类型,如果类型不合适就返回null
image.png
10.【..】
Kotlin中有区间的概念,区间表达式由具有操作符形式 .. 的 rangeTo 函数辅以 in 和 !in 形成。 区间是为任何可比较类型定义的,但对于整型原生类型,它有一个优化的实现。以下是使用区间的一些示例:
if (i in 1..10) { // 等同于 1 <= i && i <= 10 (1 <= i <= 10)
println(i)
}
//使用until函数,创建一个不包括其结束元素的区间
for (i in 1 until 10) { // i in [1, 10) 排除了 10
println(i)
}
for (i in 1..4) print(i) // 输出“1234”
for (i in 4..1) print(i) // 什么都不输出
如果你想倒序迭代数字呢?也很简单。你可以使用标准库中定义的 downTo() 函数
for (i in 4 downTo 1) print(i) // 输出“4321”
step()函数,可以指定任意步长
for (i in 1..4 step 2) print(i) // 输出“13”
for (i in 4 downTo 1 step 2) print(i) // 输出“42”
11.【{}】
这里指的是lambda表达式的符号。
// 一个参数
var callback: ((str: String) -> Unit)? = null
callback = { println(it)}
// 判断并使用
callback?.invoke("hello")
//两个参数
var callback2: ((name: String, age: Int) -> Unit)? = null
callback2 = { hello: String, world: Int -> println("$hello's age is $world") }
callback2?.invoke("Tom", 22)
var callback3 :((num1:Int, num2: Int)->String)? = null
//类型可以推断
callback3 = { num1, num2 ->
var res:Int = num1 + num2
res.toString()
}
println(callback3?.invoke(1, 2))
kotlin中{}里面整个是lambda的一个表达式,而java8中{}部分只是lambda表达式的body部分。
12.【..】
13.【..】
14.【..】
15.【..】
参考:
1:https://blog.csdn.net/lckj686/article/details/80448471
2:https://blog.csdn.net/u011288271/article/details/106495785
3:https://blog.csdn.net/u011489043/article/details/95175506
4:https://blog.csdn.net/zxc123e/article/details/73368781
网友评论