转载

Koltin第九讲---特有函数

内容简介

上一篇我了解到了 `Kotlin` 很重要的一个角色 `Lambda` 表达式,并且了解到了它的本质。接下来我们来看下 `Kotlin` 为我们特有类型的函数,方便我们开发。

包级函数

Kotlin  创建文件都是创建  .kt  文件,而  Java  创建的是  .java  文件。在  Java  文件中只能定义一个类(当且不考虑内部类),并且名字要和文件名相同,文件路径对应的就是包名,这些都是乌龟的腚(规定)。

Kotlin 随和多了,定义的  kt 文件不再是类的约束。  kt 文件只是一个编写代码的容器,我们可以随意定义多个类,你要是喜欢可以把整个程序都编写在一个  kt 文件中,并且包名也不再是必须是文件路径了。最重要的是我们可以直接在  kt 文件定义方法和变量,我们称为  包级函数 丶  包级变量

我先定义了一个  Kot.kt  文件,对应包名  com.qihoo.Kot (也就是路径名)

Koltin第九讲---特有函数

接下来我修改了包名  com.qihoo.test  编译器没报错,并且直接定义了  main  丶  call  方法,以及  2  个变量。

我们来看下产物,看看生成的  class  是什么?生成了一个  KotKt.class  文件,并且路径是  com/qihoo/test ,是我们修改包名路径和  kt  文件路径无关。

Koltin第九讲---特有函数

这里我遇到过一个坑,做 A 项目的时候,拷贝了自己  B 项目的  kt 文件,忘记修改包名了。在混淆的时候配置的混淆配置不对,导致出了一些问题。所以在写  Kotlin 的时候,一定要记住  kt 文件的路径,并不是生成类的路径。

包级函数本质

我们来看看生成的  class  文件的源码,看看到底是产物是什么(其实大家猜也能猜到)? 包级函数&变量存在于对应  文件名.class  类中的静态变量&方法

Java如何调用

看到了生成的类,我们应该知道了  java  如何调用这些方法和变量了。 通过  KotKt  调用静态方法即可。

我们  Java  调用这些包级函数,还有编写  文件名Kt  来进行调用,很不直观,  Kotlin  为我们提供了  @file:JvmName("生成的类名")  注解来修改生成的类名(注意混淆哦),直接在  kt  文件中编写即可。

后续会有专门的一篇,讲解 Kotlin 为我们提供的几个注解,来方便我们与  Java 之间的互相调用。

Kotlin调用

相比较  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  会中断调用函数吗? 会不会将内联函数的  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  。 当这个栈为空了,这个线程也就算是执行完成了。

Koltin第九讲---特有函数

看到这里我想大家知道为啥要有内联函数了吧? 它可以带来几个好处的。

  1. 减少线程调用栈的栈入栈出、

  2. 减少  Lambda  生成不必要的多余  FunctionX  类

推荐阅读

Koltin第九讲---特有函数

Koltin第九讲---特有函数

--END--

识别二维码,关注我们

Koltin第九讲---特有函数

原文  http://mp.weixin.qq.com/s?__biz=MzUyODg5NDg3NQ==&mid=2247484093&idx=1&sn=3218158bd90abb1709a11d83768a3950
正文到此结束
Loading...