内容简介
上一篇我了解到了 `Kotlin` 很重要的一个角色 `Lambda` 表达式,并且了解到了它的本质。接下来我们来看下 `Kotlin` 为我们特有类型的函数,方便我们开发。
包级函数
Kotlin
创建文件都是创建 .kt
文件,而 Java
创建的是 .java
文件。在 Java
文件中只能定义一个类(当且不考虑内部类),并且名字要和文件名相同,文件路径对应的就是包名,这些都是乌龟的腚(规定)。
Kotlin
随和多了,定义的 kt
文件不再是类的约束。 kt
文件只是一个编写代码的容器,我们可以随意定义多个类,你要是喜欢可以把整个程序都编写在一个 kt
文件中,并且包名也不再是必须是文件路径了。最重要的是我们可以直接在 kt
文件定义方法和变量,我们称为 包级函数
丶 包级变量
。
我先定义了一个 Kot.kt
文件,对应包名 com.qihoo.Kot
(也就是路径名)
com.qihoo.test
编译器没报错,并且直接定义了 main
丶 call
方法,以及 2
个变量。 class
是什么?生成了一个 KotKt.class
文件,并且路径是 com/qihoo/test
,是我们修改包名路径和 kt
文件路径无关。
这里我遇到过一个坑,做 A
项目的时候,拷贝了自己 B
项目的 kt
文件,忘记修改包名了。在混淆的时候配置的混淆配置不对,导致出了一些问题。所以在写 Kotlin
的时候,一定要记住 kt
文件的路径,并不是生成类的路径。
我们来看看生成的 class
文件的源码,看看到底是产物是什么(其实大家猜也能猜到)? 包级函数&变量存在于对应 文件名.class
类中的静态变量&方法
看到了生成的类,我们应该知道了 java
如何调用这些方法和变量了。 通过 KotKt
调用静态方法即可。
Java
调用这些包级函数,还有编写 文件名Kt
来进行调用,很不直观, Kotlin
为我们提供了 @file:JvmName("生成的类名")
注解来修改生成的类名(注意混淆哦),直接在 kt
文件中编写即可。 后续会有专门的一篇,讲解 Kotlin
为我们提供的几个注解,来方便我们与 Java
之间的互相调用。
相比较 Kotlin
调用就比较直观了,直接导包调用即可。 我们在创建一个 kt
文件,尝试调用。 是不是超级简单呀?
这补充一个知识点,前面应该也说过。我们知道包级函数&变量的调用都是通过导包的,这样就会有一个奇异,如果 2
个不同包的 2
个 kt
文件中,定义了 2
个同样的包级函数怎么办呢?
例如:
com.qihoo.kot.Kot.kt
文件 com.qihoo.kot2.Kot.kt
文件
我们尝试的调用,会报错哦。 因为编译器不知道要调用那个 call
方法。 这时候我们可以写全路径的方式调用,也可以通过 as
关键词重新命名,来解决调用歧义。
扩展函数
Kotlin
的扩展函数和 Lambda
简直将 Kotlin
推向了高潮。它给使用 Kotlin
的开发者们增加了无限的想象。 曾经我们写了无数的工具类,一般都是抽取静态方法。 这种形式比较恶心,并且在协同开发过程,经常出现重复造轮子的工具类。 而 Kotlin
的扩展方法,能优化一部分这样的问题(后续讲到的 Kotlin
的高阶函数,大部分都是通过扩展方法实现的)。
例如: 我们经常数组有一个交换操作(以前是通过抽取工具类静态方法形式),接下来我们定义一个扩展函数来实现。 需要对什么类进行扩展,只需要使用 类名.扩展函数昵称(参数1:类型,参数2:类型)
。
是不是很神奇? 扩展函数的函数体,既然能直接调用扩展类的变量和方法,难道编译期把扩展方法插入到对应的扩展类里面了? 仔细想想也不可能,如果我为 Activity
扩展方法,你怎么可能注入到 Activity
类中,这些类都是在 ROM
中的。 那他到底怎么实现的呢? 我们来看下编译结果。
其实也很简单,就是多生成了一个静态方法,对应的第一个参数就是我们扩展类的调用对象罢了。 从本质我们就能看出一个问题,扩展函数的函数体只能调用访问扩展类的公开方法和属性。 现在知道为啥 kotlin
定义的变量和函数都是默认公开的了吧。
Kotlin
为我们封装的方法几乎都是以扩展函数的形式提供。文件读写,数组增删改查丶遍历都是以扩展函数的形式增加,大家可以创建一个对象,在通过代码提示看看有多少扩展函数吧。
内联函数
在讲解内联函数之前,大家可以了解下 Java
虚拟机的线程是如何调用方法的,线程执行完毕的依据又是什么呢(后面的扩展内容会讲到)?
我们如何定义内联函数呢? 其实很简单只需要通过 inline
修饰方法即可,接下来我们来看看通过 inline
修饰后和未修饰后的区别吧。
Java
信息,我们可以看到 main
方法单纯的就是调用了 call
方法。 接下来我们尝试通过 inline
修饰方法的结果( Koltin
代码就不贴出来了,就是 call
方法多了个 inline
修饰符)。 我们可以发现 main
方法没有直接调用 call
方法了,而是将 call
方法的函数复制了一份,直接写在 main
方法中了。 试想下这样的坏处,就是单独的 class
文件的体积会变大(怪不得 Kotlin
的项目体积明显比 Java
的大)。
这里有个点不知道大家有没有注意,通过 inline
修饰的方法,若行参中有 Lambda
表达式,也会默认将 Lambda
代码平铺到调用者函数中。试想下这样的好处是啥?在上一篇中我们学习过 Lambda
表达式的本质,会多生成一个 FunctionX
的类,如果这样做是不是就会少生成 1
个类呀?当然若我不想让 Lambda
也被内联也是可以的,只需要通过 noinline
修饰 Lambda
表达式。
不知道大家有没有考虑,内联函数中编写 return
会中断调用函数吗? 会不会将内联函数的 return
代码编译到调用者函数中呢? Kotlin
开发者可能是为了防止歧义,内联函数的 return
是不会中断调用函数的。 但是默认内联函数的 Lambda
表达式的 return
会中断。
可以看到代码中没有输出 3
, call
方法就结束了。 查看下编译源码,发现的确没有将输出 3
的语句写入。
接下来就有个问题,那如果我不想内联函数的 Lambda
中断函数呢? 可通过 crossinline
修饰 Lambda
表达式。 然后你会发现,若在 Lambda
表达式中写 return
会直接报错。
其实这里有必要说下, Lambda
表达式本身是不允许写 return
的。只有在内联函数且没有通过 crossinline
修饰的 Lambda
才可以调用 return
(可以直接中断调用函数)。那问题又来了,若我们的 Lambda
要有根据条件 return
的功能呢?只能通过 ifelse
吗?其实我们只需要使用 return@函数昵称
即可。
大家可以看下编译结果,还是很有意思的哦(不得不说 Kotlin
的编译器挺智能)。 Kotlin
提供给的很多扩展函数都是内敛函数,主要目的就是减少 Lambda
表达式生成的多余类。
扩展内容
以下内容都是扩展内容,懒得看可无视。
为何要有内联方法呢? 他给我们带来了什么好处呢? 我们来看下 Java
虚拟机是如何调用方法的吧!
栈帧概念:
这里要特别解释下栈帧,每个线程都会分配一个栈。 这个栈中存放的数据就是栈帧,每个栈帧就代表的一个方法。
举个例子: 当一个线程调用 a
方法, a
方法调用了 b
方法,那么这个线程的栈中就存放了 2
个栈帧,先将 a
方法压入栈,然后将 b
方法压入栈, b
方法执行完成弹出 b
, a
方法执行完弹出 a
。 当这个栈为空了,这个线程也就算是执行完成了。
看到这里我想大家知道为啥要有内联函数了吧? 它可以带来几个好处的。
减少线程调用栈的栈入栈出、
减少 Lambda
生成不必要的多余 FunctionX
类
--END--
识别二维码,关注我们