最后如何让自己一步步成为技术专家
说句实话,如果一个打工人不想提升自己,那便没有工作的意义,毕竟大家也没有到养老的年龄。
当你的技术在一步步贴近阿里p7水平的时候,毫无疑问你的薪资肯定会涨,同时你能学到更多更深的技术,交结到更厉害的大牛。
推荐一份Java架构之路必备的学习笔记,内容相当全面!!!
成年人的世界没有容易二字,前段时间刷抖音看到一个程序员连着加班两星期到半夜2点的视频。在这个行业若想要拿高薪除了提高硬实力别无他法。
你知道吗?现在有的应届生实习薪资都已经赶超开发5年的程序员了,实习薪资26K,30K,你没有紧迫感吗?做了这么多年还不如一个应届生,真的非常尴尬!
进了这个行业就不要把没时间学习当借口,这个行业就是要不断学习,不然就只能被裁员。所以,抓紧时间投资自己,多学点技术,眼前困难,往后轻松!
【关注】+【转发】+【点赞】支持我!创作不易!
相应地,在调用处也需要填上这个参数。
我可以填成匿名函数的形式:
也可以简单点,写成 Lambda 表达式:
因为 Java 并没有对函数类型的变量的原生支持,Kotlin 需要想办法来让这种自己新引入的概念在 JVM 中落地。而它想的办法是什么呢?就是用一个 JVM 对象来作为函数类型的变量的实际载体,让这个对象去执行实际的代码。也就是说,在我对代码做了刚才那种修改之后,程序在每次调用 hello() 的时候都会创建一个对象来执行 Lambda 表达式里的代码,虽然这个对象是用一下之后马上就被抛弃,但它确实被创建了。
这有什么坏处?其实一般情况下也没什么坏处,多创建个对象算什么?但是你想一下,如果这种函数被放在循环里执行:
内存占用是不是一下就飚起来了?而且关键是,你作为函数的创建者,并不知道、也没法规定别人在什么地方调用这个函数,也就是说,这个函数是否出现在循环或者界面刷新之类的高频场景里,是完全不可控的。这样一来……这一类函数就全都有了性能隐患了。高阶函数是 Kotlin 相比起 Java 很方便的一个特性,但却有这么一个性能隐患,这……还让人怎么放心用啊?
这就是 inline 关键字出场的时候了。
刚才我说了,inline 关键字不止可以内联自己的内部代码,还可以内联自己内部的内部的代码,意思是什么呢,就是你的函数在被加了 inline 关键字之后,编译器在编译时不仅会把函数内联过来,而且会把它内部的函数类型的参数——那就是那些 Lambda 表达式——也内联过来。换句话说,这个函数被编译器贴过来的时候是完全展开铺平的:
经过这种优化,是不是就避免了函数类型的参数所造成的临时对象的创建了?这样的话,是不是就不怕在循环或者界面刷新这样的高频场景里调用它们了?
天然的性能缺陷,我们通过 inline 关键字让函数用内联的方式进行编译,来减少参数对象的创建,从而避免出现性能问题。
所以,inline 是用来优化的吗?是,但你不能无脑使用它,你需要确定它可以带来优化再去用它,否则可能会变成负优化。其实换个角度想想:既然 inline 是优化,为什么 Kotlin 没有直接开启它,而要把它做成选项,而且还是个默认关闭的选项?就是因为它还真不一定是优化,加不加它需要我们自己去做判断。那怎么去做这个判断呢?很简单,如果你写的是高阶函数,会有函数类型的参数,加上 inline 就对了。
嗯……不过如果你们团队对于包大小有非常极致的追求,也可以选择酌情使用 inline,比如对代码做严格要求,只有会被频繁调用的高阶函数才使用 inline——这个可能在实施上会有点难度,一般来说,按我刚才说的原则就已经够了。
另外,Kotlin 的官方源码里还有一个 inline 的另类用法:在函数里直接去调用 Java 的静态方法:
用偷天换日的方式来去掉了这些 Java 的静态方法的前缀,让调用更简单:
这个很有必要跟大家提一下:这种用法不是 inline 被创造的初衷,也不是 inline 的核心意义,这属于一种相对偏门的另类用法。——不过这么用没什么问题啊,因为它的函数体简洁,并不会造成字节码膨胀的问题。你如果有类似的场景,也可以这么用。
讲到这儿……应该知道内联函数怎么用了吧?那我们就……继续深入一下?
noinline
说完 inline,我们来说另一个关键字:noinline。noinline 的意思很直白:inline 是内联,而 noinline 就是不内联。不过它不是作用于函数的,而是作用于函数的参数:对于一个标记了 inline 的内联函数,你可以对它的任何一个或多个函数类型的参数添加 noinline 关键字:
添加了之后,这个参数就不会参与内联了:
好理解吧?好理解是好理解,(皱眉)可是这有什么用啊?为什么要关闭这种优化?
首先我们要知道,函数类型的参数,它本质上是个对象。我们可以把这个对象当做函数来调用,这也是最常见的用法:
但同时我们也可以把它当做对象来用。比如把它当做返回值:
但当我们把函数进行内联的时候,它内部的这些参数就不再是对象了,因为他们会被编译器拿到调用处去展开。也就是说,当你的函数被这样调用的时候:
代码会被这样编译:
哎?请问你找谁啊?
发现问题了吗?当一个函数被内联之后,它内部的那些函数类型的参数就不再是对象了,因为它们的壳被脱掉了。换句话说,对于编译之后的字节码来说,这个对象根本就不存在。一个不存在的对象,你怎么使用?
所以当你要把一个这样的参数当做对象使用的时候,Android Studio 会报错,告诉你这没法编译:
那……我如果真的需要用这个对象怎么办?加上 noinline:
加了 noinline 之后,这个参数就不会参与内联了:
那我们就也可以正常使用它了。
所以,noinline 的作用是什么?是用来局部地、指向性地关掉函数的内联优化的。既然是优化,为什么要关掉?因为这种优化会导致函数中的函数类型的参数无法被当做对象使用,也就是说,这种优化会对 Kotlin 的功能做出一定程度的收窄。而当你需要这个功能的时候,就要手动关闭优化了。这也是 inline 默认是关闭、需要手动开启的另一个原因:它会收窄 Kotlin 的功能。
那么,我们应该怎么判断什么时候用 noinline 呢?很简单,比 inline 还要简单:你不用判断,Android Studio 会告诉你的。当你在内联函数里对函数类型的参数使用了风骚操作,Android Studio 拒绝编译的时候,你再加上 noinline 就可以了。
crossinline
最后再来说 crossinline。这是个很有意思的关键字,刚才讲的 noinline 是局部关闭内联优化对吧?而这个 crossinline,是局部加强内联优化。
我们先来看代码。这里有一个内联函数,还有一个对它的调用:
假如我往这个 Lambda 表达式里加一个 return:
这个 return 会结束哪个函数的执行?是它外面的 hello() 还是再往外一层的 main()?
按照通常的规则,肯定是结束 hello() 的对吧?因为 hello() 离它近啊,return 所结束的肯定是直接包裹住它的那个函数。可是大家想一想,这个 hello() 是个内联函数对不对?内联函数在编译优化之后会怎么样?会被铺平是不是?而这个调用,在铺平后会变成这样:
那你再看看,return 结束的是哪个函数?是外层的对吧?也就是说,对于内联函数,它的参数中 Lambda 的 return 结束的不是这个内联函数,而是那个调用这个内联函数的更外层的函数。是这个道理吧!
道理是这个道理,但这就有问题了。什么问题?我一个 return 结束哪个函数,竟然要看这个函数是不是内联函数!那岂不是我每次写这种代码都得钻到原函数里去看看有没有 inline 关键字,才能知道我的代码会怎么执行?那这也太难了吧!
这种不一致性会给我们带来极大困扰,因此 Kotlin 制定了一条规则:Lambda 表达式里不允许使用 return,**除非——**这个 Lambda 是内联函数的参数。
那这样的话规则就简单了:
- Lambda 里的 return,结束的不是直接的外层函数,而是外层再外层的函数;
- 但只有内联函数的 Lambda 参数可以使用 return。
注:Lambda 可以用 return@label 的方式来显式指定返回的位置,但这个不是今天讨论的内容。
这样就既消了歧义,也避免了需要反复查看每个函数是不是内联函数的麻烦。
不过……我们如果把事情再变复杂一点——最后一次了,不会更复杂了:
这次,我用 runOnUiThread() 把这个参数放在了主线程执行,这是一种很常见的操作。
但,这就带来了一个麻烦:本来在调用处最后那行的 return 是要结束它外层再外层的 main() 函数的,但现在因为它被放在了 runOnUiThread() 里,hello() 对它的调用就变成了间接调用。所谓间接调用,直白点说就是它和外层的 hello() 函数之间的关系被切断了。和 hello() 的关系被切断,那就更够不着更外层的 main() 了,也就是说这个间接调用,导致 Lambda 里的 return 无法结束最外面的 main() 函数了。
最后
针对以上面试题,小编已经把面试题+答案整理好了
面试专题
除了以上面试题+答案,小编同时还整理了微服务相关的实战文档也可以分享给大家学习
源码】](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)收录**