目录
1.函数内联
有关内联的引出,有些材料上说的是:在JVM上,你定义的lambda会以对象实例的形式存在,JVM会为所有同lambda打交道的变量分配内存,这就产生了内存开销,更糟的是,lambda的内存开销会带来严重的性能问题。幸运的是,kotlin有一种优化机制叫内联,有了内联,JVM就不需要使用lambda对象实例了,因而避免了变量内存分配。哪里需要使用lambda,编译器就会将函数体复制粘贴到哪里。
值得注意的是,使用lambda的递归函数无法内联,因为会导致赋值粘贴无限循环,编译会警告。
内联函数的关键字是inline。例如上次的学习笔记-kotlin(1)中的高阶函数代码咱们来看看它的字节码。操作步骤show kotlin -> Decompile 转换成java咱们更方便看。
fun main(){
val meilidepaomo:(String, Int) -> String = {goodsName, hour ->
val currentYear : Int = 2007
"${currentYear}年, 双11${goodsName}的促销事件剩余${hour}小时"
}
showOnBoard("安慕希", meilidepaomo)
}
private fun showOnBoard(goodsName:String, showdowns:(String, Int) -> String) {
val hour : Int = 6
println(showdowns(goodsName, hour))
}
public final void main() {
Function2 meilidepaomo = (Function2)null.INSTANCE;
this.showOnBoard("安慕希", meilidepaomo);
}
private final void showOnBoard(String goodsName, Function2 showdowns) {
int hour = 6;
Object var4 = showdowns.invoke(goodsName, Integer.valueOf(hour));
boolean var5 = false;
System.out.println(var4);
}
上边没有使用内联关键字的时候的字节码
下边使用了内联关键字的字节码
fun main(){
val meilidepaomo:(String, Int) -> String = {goodsName, hour ->
val currentYear : Int = 2007
"${currentYear}年, 双11${goodsName}的促销事件剩余${hour}小时"
}
showOnBoard("安慕希", meilidepaomo)
}
private inline fun showOnBoard(goodsName:String, showdowns:(String, Int) -> String) {
val hour : Int = 6
println(showdowns(goodsName, hour))
}
public final void main() {
Function2 meilidepaomo = (Function2)null.INSTANCE;
String goodsName$iv = "安慕希";
int $i$f$showOnBoard = false;
int hour$iv = 6;
Object var6 = meilidepaomo.invoke(goodsName$iv, Integer.valueOf(hour$iv));
boolean var7 = false;
System.out.println(var6);
}
private final void showOnBoard(String goodsName, Function2 showdowns) {
int $i$f$showOnBoard = 0;
int hour = 6;
Object var5 = showdowns.invoke(goodsName, Integer.valueOf(hour));
boolean var6 = false;
System.out.println(var5);
}
我们能看出来showOnBoard()函数在加了inline内联关键字后的字节码,把其函数体内的代码赋值粘贴到了main()中
2.函数引用
函数引用方式::函数名
示例:将上边的代码改成具名函数后
fun main(){
showOnBoard("安慕希", ::meilidepaomo)
}
private fun showOnBoard(goodsName:String, showdowns:(String, Int) -> String) {
val hour : Int = 6
println(showdowns(goodsName, hour))
}
private fun meilidepaomo(goodsName:String, hour:Int):String {
val currentYear : Int = 2007
return "${currentYear}年, 双11${goodsName}的促销事件剩余${hour}小时"
}
3.函数类型作为返回类型
函数类型也是返回类型,可以定义一个返回函数的函数。有点绕嘴,哈哈哈。
fun main(){
val meilidepaomo = showOnBoard();
println(meilidepaomo("蜡笔小新"))
}
private fun showOnBoard() : (String) -> String {
val currentYear : Int = 2007;
val hour : Int = 5;
return {goodsName:String ->
"${currentYear}年,双11${goodsName}促销倒计时,剩余${hour}小时"
}
}
其中showOnBoard()函数的返回类型是(String)->String的一个函数(一个String的参数,返回值为String),使用meilidepaomo的时候传入一个String参数即可
运行结果:2007年,双11蜡笔小新促销倒计时,剩余5小时
4.闭包
闭包的概念:匿名函数能修改并引用定义在自己的作用域之外的变量,匿名函数引用着定义自身的函数里的变量,kotlin中的lambda就是闭包。换成通俗的讲法就是,function2在funtion1中,function2使用了funtion1中的变量,这个时候我们就称为闭包。
闭包存在的意义是什么呢?我找到的资料是,kotlin学习脚本语言,脚本语言没有Package的概念,当一个变量在文件A中声明了,在B中用相同的名字声明,就编译警告了。闭包能够保证其作用域。
5.Null
kotlin区分可空类型和非可空类型。空值异常kotlin会在编译时就警告,而不是等到运行之后再崩溃。除非你接手处理。处理的方式有:
(1)安全调用操作符
操作符:?
val i:Int? = null
这样我们的Int就是可空类型的了,编辑器再看到安全调用符,如果为空值,就会跳过函数调用,而不是让空值继续执行函数,如:
fun main(){
val str : String? = getStr()?.plus("222")
println(str)
}
private fun getStr():String? {
return "LOVE";
}
运行结果:LOVE222
fun main(){
val str : String? = getStr()?.plus("222")
println(str)
}
private fun getStr():String? {
return null;
}
运行结果:null
代码不难,getStr()函数返回一个可空的String,?.plus表示不为空的话执行plus方法。第一段代码运行结果LOVE222表示走进了plus()函数,第二段代码运行结果null,表示跳过了函数调用。这样咱们就可以理解之前的一些概念了。
(2)非空断言操作符
操作符:!!
val str : String? = getStr()!!.plus("222")
咱们不管是否为空就是要走plus,就可以这样使用,但这样容易空指针异常
(3)使用if判断空值
这个很好理解了,跟java差不多
if(str == null) {.......}
(4)使用空合并操作符
操作符:?:
fun main(){
val str : String? = getStr()?.plus("222")?:"111111"
println(str)
}
private fun getStr():String? {
return null;
}
运行结果:111111
如果为空的话,就执行后面的东西。从运行结果上来看,也符合这样的一个逻辑。个人感觉起来更像是3原运算符似的boolean ? 结果1 : 结果2。
6.异常
抛出异常和java相同
try{
.....
} catch(e:Exception){
println(e)
}