目录:
• 1.作用域函数 apply、also 与 let、run
• 2.委托 by 关键字
• 3.用when替代switch 与 if + N个else if
• 4.inline,noinline,crossinline
• 5.操作符重载、中缀表达式
• 6.拓展函数,拓展属性,匿名拓展
1 作用域函数 apply、also 与 let、run 使用场景建议
1.1 不需要返回其他类型对象 使用 apply、also,通常用于对象初始化时
1.2 需要返回其他类型对象 使用 let,run,通常用于对象引用时
2 委托 by 关键字
2.1 常用的属性委托 by lazy
维持一个单例成员,在首次访问时才创建,有点像java 中的懒加载
他们三种使用方式,效果都一样。在创建时都将使用悲观锁,锁对象都是声明成员的当前类,都会产生 [锁] 开销
实际使用过程中,需要慎重考虑2点,否则得不偿失
- 1 是否需要延迟加载
- 2 是否需要线程安全
2.2 自定义属性委托
场景分析:假设下面的两个变量都需要直接操作 set 与 get
这里写了两遍一样的样板代码,这能避免吗?这时候就用到了自定义属性委托
创建一个委托对象,实现 get set 方法 并在启动补充属性操作的逻辑
通过 by 关键字调用
这看似神奇但却不是什么黑魔法,都是依赖于编译器自动创建了相关代码逻辑,通过反编译就可以看到
2.3 想 by 点儿啥 就 by 点儿啥?官方文档也坑爹
这样看起来似乎挺爽的,这个例子中先不说类型安全的问题,最关键是我们用kotlin是为了消除NPE
但官网文档此处就是教开发者如何用kotlin特性不知不觉中写出一个NPE来
普通写法可以避免NPE吗?当然是可以的,编译器可以帮助你避免
2.4 使用委托编辑实现静态代理
3 用when替代switch 甚至是 if + N个else if
不过when 也容易when出坑
此处第一个条件实际编译后的代码是
BooleanCompanionObject.INSTANCE
这便是问题所在,这是非常危险的,也许永远都不达预期
注意: when 的条件前面不加 is 的时候就是判断具体的值
4 inline,noinline,crossinline
4.1 inline的主要作用是节省匿名函数带来的开销
- 不使用 inline
- 使用 inline
- 降低了调用栈的深度
但是也会使代码变得膨胀(因为他会拷贝到所有调用出),具Google GDE所说,降低调用栈并不能有效的提升多少性能
总结来说是利用编译时常量的思想优化性能,比如变量使用 const (java中 static final ),函数则使用 inline
所以如果一个 函数 需要 函数参数 ,且这个 函数 被频繁调用则考虑使用 inline
4.2 noinline :禁用内联
在 inline 场景下,默认不允许把一个函数当作对象使用
因为经过 inline 优化,这个 preAction 对象根本就不存在
所以需要使用 noinline
4.3 crossinline :内联加强
下面的代码中, return会结束哪个函数的执行?
结合 inline 机制模拟编译后的伪代码可看出她结束的是外部的 main 方法
这是完全符合逻辑与期待的。 inline 函数的函数参数的Lambda表达式,允许使用 return
如果这个函数参数又被其他内部调用包裹了呢?
这时候就需要使用 crossinline
但是 return 就不可再使用,也就避免了 retrun 要退出谁的问题。
简单使用场景建议:
- 函数的接收参数中有 函数类型参数 才需要考虑使用 inline
- 函数类型参数 需要作为 对象 使用时使用 noinline
- 函数类型参数 需要被间接调用时使用 crossinline
警告:跨Module使用 inline ,有以下问题
在一个Library声明一个类,定义一个protecte 的 inline 函数,并在内部Lambda中访问一个protecte 修饰的成员。在另一个App Module中继承这个类,并访问这个 inline 函数。则会报错:无权限访问
这似乎是一个kotlin的bug,至少编译器应该进行错误提示
5 操作符重载、中缀表达式
fun main() {
val point = Point(10, 20)
println("一元 负号 ${-point}") // 输出“Point(x=-10, y=-20)”
val p1 = Point(1,1)
val p2 = Point(1,1)
println("二元 + ${p1 + p2}")
println("二元 - ${p1 - p2}")
println("中缀 大于 ${p1 moreThan p2}")
}
///操作符重载:覆盖基础操作符函数(以运算符号的方式调用函数)
///中缀表达式:自定义操作符(不能是运算符号 只能是字母)
/*
kotlin 操作符 + - 等都是函数映射
val a = 1,val b = 1 , a + b 实际上是访问的下面的函数
public operator fun plus(other: Int): Int
借助操作符重载我们可以对任意对象拓展出 + - * / 等操作符
*/
//操作符重载-------operator-----------------------
data class Point(val x: Int, val y: Int)
//一元 [-] 号 -obj
operator fun Point.unaryMinus() = Point(-x, -y)
//二元 [+] 号 obj1 + obj2
operator fun Point.plus(p: Point): Point {
return Point(x+p.x,y+p.y)
}
//二元 [-] 号 obj1 - obj2
operator fun Point.minus(p: Point): Point {
return Point(x-p.x,y-p.y)
}
/*
kotlin 中允许使用 infix 关键字将函数声明为中缀表达式
在调用上得到简化使用:a.moreThan(b) -> a moreThan b
*/
//中缀表达式----------infix---------------------
infix fun Point.moreThan(p:Point):Boolean{
return x*y > p.x * p.y
}
复制代码
6.拓展属性,拓展函数,匿名拓展
kotlin拓展具备很广的应用场景,所以拓展的代码很容易膨胀,不易于管理,容易发生滥用导致耦合度提高,破外代码边界。
权限优先级建议:
- 类私有
- 文件私有
- 包私有
- 模块私有
- 全局通用
6.1 拓展属性实战举例
6.2 拓展函数
6.3 匿名拓展
inline+匿名拓展,比如 let、apply 等内置拓展函数
参考:
- kotlin官方文档
- 扔物线Hencoder课程