话说不要过度解读,可是学习嘛,许多东西是需求解读的,假如说单纯的快速的学习运用黑箱形式其实是最好的,我不需求知道他是什么意思,只需求在需求的时分正确的把他整处理就行。可是卷到如今的今日,黑箱形式现已不行了,你得知道为什么,那就卷吧。
作为一个运用Kotlin断断续续的搬砖的Android,今年上半年我还蛮喜爱JAVA的,主要是感觉用Kotlin写JAVA好像很生硬,可是这个砖全是Kotlin写到,搬砖2周左右,仿佛打通了任督二脉,根据方法的编程真的香,限制的只要想象力而不是API,于是也关注了一些Kotlin的大众号,一起也翻翻书啥的,可是更多的时分仍是在搬砖。
最近大众号推了一篇《Android 最常讨论的关于 Kotlin 的面试问题》 洋洋洒洒的应该有几千字吧,讲的都是Kotlin很根底的东西,当我快速的浏览完关闭的时分,我脑子里边关于哪些细碎的常识点还没有回忆完,以至于我搬砖的时分,那些常识点还在干扰着我搬砖,这是一个很有意思的本能现象,由于我关于这些常识点并不是了解到能够瞬间回忆的程度,大脑潜意识里以为这很重要,于是就帮忙回忆一遍,这蛮费心力的,一起也导致搬砖的注意力不集中简单写出bug。这和考试的时分大脑老是唱歌一个道理,当你把考试大脑唱的歌能够十分了解的唱出来的之后,考试就不至于唱那首歌了,只要了解的身心环境又才会触发那首歌了,至于会不会唱其他的,拖欠的重要事情太多了,估量下次会是其他事情。嗯,这也是这个笔记系列的重要原因吧,测验把常识点细化到某个层面的最小吧,那么就开整。
正文
我仍是依照自己的思路把文章的常识点换一下顺序吧,感觉从最简单的开始是一个好方向。
kotlin init块的意图是什么?他和结构函数有什么不同
原文定论:
kotlin中的init块用于初始化属性,并履行在创立内的实例时所需求运转的代码,他是主结构函数的一部分,在目标初始化阶段调用,与结构函数的差异在于,假如有多个结构函数,无论调用那个结构函数,init 块总会被履行,它整合了公共的初始化代码。
这便是kotlin编译器的牛逼的当地了,咱们写的一切Kotlin Android代码,他都会编译成 class的字节码,尽管不会生成.java文件,可是能够理解成生成了.class 文件。可是JAVA 里边是没有init 这个概念的,而且init在kotlin 的概念里边并不是JAVA的static{},那么答案便是kotlin 把init 里边的代码编译到结构函数里边去了,究竟JAVA class一定是有结构函数了的,哪怕你没有写。咱们先来看代码及其现象:
class Man : Person {
constructor(name: String, age: Int) : super(name = name) {
println("Man 2 个参数 $age")
}
constructor(name: String, code: String,age: Int) : super(name = name,code) {
println("Man 3 个参数 $age")
}
init {
println("Man init ")
}
}
open class Person constructor( name: String, code:String){
var msg:String="msg 的默许值"
constructor(name: String):this(name,"code") {
println("一个参数的结构函数")
}
init {
println("Person $name")
println("Person $msg")
msg="init"
println("Person $msg")
}
}
上面代码很简单。界说了一个Person 类,然后又一个两个参数的结构函数,一个一个一个参数的次结构函数。Man承继了Person类,然后界说了两个结构函数。
咱们先来看第一种最根底的状况。直接创立主结构函数的目标 Person(“man”,”new code”) 打印如下:
Person man
Person msg 的默许值
Person init
这个能够看出几个东西:
- init 被履行了。
- init 里边结构函数的的变量赋值是成功的。
- 类的成员变量在init里边是默许值
再来看第二种状况:创立次结构函数目标Person(“man”) 打印如下:
Person man
Person msg 的默许值
Person init
一个参数的结构函数
能够看出,次结构函数的打印在init 履行之后。 那么最终一种状况,子类创立目标 Man(“man”,5) 和 Man(“man”,”code”,5) 打印如下:
Person man
Person msg 的默许值
Person init
一个参数的结构函数
Man init
Man 2 个参数 5
------
Person man
Person msg 的默许值
Person init
Man init
Man 3 个参数 5
能够看到,他其实先履行的父类的init 函数。然后再履行Man目标的结构函数,那么就开始常识点汇总。
常识点汇总
结合上的定论咱们提取出以下几个常识点:
- 主结构函数目标赋值最快
- 然后是目标的变量的默许值
- 然后是init
- 然后是次结构函数
- 子类的init
- 子类的结构函数
当然了这个总结并不完善,并没涉及到一切状况,有片面之意。那么咱们来看一下两个Kotlin class 生成的字节码转化为JAVA class的样子:
public class Person {
@NotNull
private String msg;
@NotNull
public final String getMsg() {
return this.msg;
}
public final void setMsg(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.msg = var1;
}
public Person(@NotNull String name, @NotNull String code) {
Intrinsics.checkNotNullParameter(name, "name");
Intrinsics.checkNotNullParameter(code, "code");
super();
this.msg = "msg 的默许值";
String var3 = "Person " + name;
System.out.println(var3);
var3 = "Person " + this.msg;
System.out.println(var3);
this.msg = "init";
var3 = "Person " + this.msg;
System.out.println(var3);
}
public Person(@NotNull String name) {
Intrinsics.checkNotNullParameter(name, "name");
this(name, "code");
String var2 = "一个参数的结构函数";
System.out.println(var2);
}
}
public final class Man extends Person {
public Man(@NotNull String name, int age) {
Intrinsics.checkNotNullParameter(name, "name");
super(name);
String var3 = "Man init ";
System.out.println(var3);
var3 = "Man 2 个参数 " + age;
System.out.println(var3);
}
public Man(@NotNull String name, @NotNull String code, int age) {
Intrinsics.checkNotNullParameter(name, "name");
Intrinsics.checkNotNullParameter(code, "code");
super(name, code);
String var4 = "Man init ";
System.out.println(var4);
var4 = "Man 3 个参数 " + age;
System.out.println(var4);
}
}
能够看到,这个生成的class。咱们并没有对其的彻底总结,比如说 空校验、final class 、注解等等。这现已很细了,这些细枝末节的多看几次就记住了。那么就来看原文的下一条。
伴生目标的概念,以及他们如安在Kotlin中运用
原文定论:
在Kotlin中伴生目标是一个特殊的目标,他与声明他的类相关联,他相似于其他言语中的静态成员,可是具有更多的功用,伴生目标在类的一切实例中同享,其成员能够直接运用类名拜访。
能够看到伴生目标是一个Kotlin 言语的一个概念,在JAVA中并没有,仍是那个思路,我看你字节码转成JAVA是什么样的就行,咱们先来模拟一下一个网络图片加载,通过简单的装备了一个默许图片。
class ImageLoader{
companion object Config{
fun getDefaultImage():String ="这是一个默许图片"
}
fun load(path:String){
getDefaultImage()
}
}
JAVA 目标调用 装备 ImageLoader.Config.getDefaultImage(),kotlin 调用 ImageLoader.getDefaultImage()。至于为啥要先阐明JAVA 调用方法,由于咱们能够通过完成功用去猜测到他完成方法,很可能是ImageLoader 持有了一个进行的Config 目标。OK,那咱们直接看反编译过来的class:
public final class ImageLoader {
@NotNull
public static final Config Config = new Config((DefaultConstructorMarker)null);
public final void load(@NotNull String path) {
Intrinsics.checkNotNullParameter(path, "path");
Config.getDefaultImage();
}
public static final class Config {
@NotNull
public final String getDefaultImage() {
return "这是一个默许图片";
}
private Config() {
}
// $FF: synthetic method
public Config(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
为了节约blog字数空间,我把@Metadata 相关代码删除了,能够通过上面的内容看到,Kotlin把 config 编译成了一个JAVA静态的内部类,一起在ImageLoader 里边通过 static final 界说了一个config 目标。而定论上说的更多功用可能是代码上没有运用,所以反编译出来的class没有表现,而也验证了相似于静态目标这种逻辑。而咱们也只是写了一个简单class的伴生目标,比如说 data class ,object class 具体的相关内容还需求自己去测验一下。这里就不贴和详细描述了。object 不支持设置伴生目标,data class 生成的和正常class 相似,只是多了一些data class 的特性。
那么 伴生目标在什么状况下能够被运用呢,感觉在Android上运用的特别少,他的特性是伴生目标在一切类的实例中同享,这个玩意是静态目标,干上去了就不会回收了,需求运用的情景本身就不多,能够完成相似效果的有单例、承继关系持有、数据同享能够用viewModel等等。最近经常看到有用伴生目标提供一个目标获取或或许做fragment的不同入参的装备的,没有体会到这种写法的优势,明明能够只要一个目标,为啥要需求搞一个静态目标,而且在组件化中,fragment的创立根本就碰不到类直接创立目标的时机,有点懵逼。
解说Kotlin中的扩展函数的概念,inline,解说Kotlin 中let、apply、run、also和with 效果域函数的之间的差异,智能转化的概念
原文定论:
kotlin 中的扩展函数允许咱们再不修正其源码的状况下,为类增加新功用,他们是向外部库或标准类增加有用函数的强壮东西。
inline 是一个高阶函数的修饰符,他主张编译器在调用站点履行函数的代码的内联,当您将函数符号为 inline 时,该函数的字节码将直接杂乱到调用的代码中,某些时分能够进步功用。
- let 用于对非空目标履行一段代码,并回来该代码块中最终一个表达式的成果,他一般用于空安全检查和链接操作,当然结合when 进行逻辑分发也是能够的。
- apply 用于通过调用多个方法来装备目标,并回来目标本身,一般是用于目标装备属性
- run 用于在目标上履行一个段代码,功用相似与了let,但假如没有指定回来值,则回来块内最终一个表达式的成果或整个目标。
- alse 用于对目标履行附加操作,并回来原始目标,它一般用于记录日志或许不允许改变原有目标的状况。
- with 用于调用一个目标的多个函数,没有回来值。感觉和apply 相似,可是apply有回来值,with的写法和apply 这种写到目标后面能够链式调度的仍是在功用上有差异的。
Kotlin 中的智能转化是编译器依据特定条件履行的主动类型转化。当编译器能够保证某个变量属于某个代码块中的特定类型时,它会主动将该变量强制转化为该类型,从而允许您运用其属性和函数,而无需显式类型检查或强制转化。
听起来很黑科技的感觉。那么仍是来试试他是如安在JAVA中完成的。
fun main() {
"扩展函数".println()
}
fun String.println(){
println(this)
}
这代码很简单,便是关于String 界说了一个打印log的函数。一起运用了inline 界说的 内联函数。
public final class MainKt {
public static final void main() {
println("扩展函数");
}
public static void main(String[] var0) {
main();
}
public static final void println(@NotNull String $this$println) {
Intrinsics.checkNotNullParameter($this$println, "$this$println");
System.out.println($this$println);
}
}
尽管特别杂乱的看不出来,可是能够大致的看出来,他其实是把咱们界说的扩展函数界说了成了一个静态的函数,然后把扩展函数的完成整合到静态函数里边,这里就一起验证了两个理论的完成。
- 扩展函数是界说静态函数,然后把扩展的函数的完成在静态函数中完成。
- 内联函数是把内联函数的代码复制到调用的代码中。
那么咱们对扩展函数增加 inline 关键字呢?
fun main() {
val isPalindrome= "abc".isPalindrome()
println(isPalindrome)
}
inline fun String.isPalindrome(): Boolean {
val cleanString = this.toLowerCase().replace(Regex("[^a-zA-Z0-9]"), "")
return cleanString == cleanString.reversed()
}
反编译:
public final class MainKt {
public static final void main() {
String $this$isPalindrome$iv = "abc";
int $i$f$isPalindrome = false;
String var10000 = $this$isPalindrome$iv.toLowerCase();
Intrinsics.checkNotNullExpressionValue(var10000, "this as java.lang.String).toLowerCase()");
CharSequence var3 = (CharSequence)var10000;
Regex var4 = new Regex("[^a-zA-Z0-9]");
String var5 = "";
String cleanString$iv = var4.replace(var3, var5);
boolean isPalindrome = Intrinsics.areEqual(cleanString$iv, StringsKt.reversed((CharSequence)cleanString$iv).toString());
System.out.println(isPalindrome);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
public static final boolean isPalindrome(@NotNull String $this$isPalindrome) {
int $i$f$isPalindrome = 0;
Intrinsics.checkNotNullParameter($this$isPalindrome, "$this$isPalindrome");
String var10000 = $this$isPalindrome.toLowerCase();
Intrinsics.checkNotNullExpressionValue(var10000, "this as java.lang.String).toLowerCase()");
CharSequence var3 = (CharSequence)var10000;
Regex var4 = new Regex("[^a-zA-Z0-9]");
String var5 = "";
String cleanString = var4.replace(var3, var5);
return Intrinsics.areEqual(cleanString, StringsKt.reversed((CharSequence)cleanString).toString());
}
}
能够看到,会生成两个函数,先生成扩展函数的静态函数,然后把静态函数复制生成一个inline 复制的函数。嗯,成果扩展函数生成的静态函数就没有当地调用了,所以不主张关于扩展函数符号inline。
咱们能够看到,let apply run also 和with 其实都是 inline 界说的高阶函数。那么也就没有多少视点能够细化常识点了。
在编码阶段智能转化的概念感觉和高阶函数中的let、run的运用相契合,一起也契合when句子,咱们并不需求写回来类型,编译器能够自己根据代码逻辑进行判别。在编译阶段也是有许多的好处的,由于kotlin是直接生成字节码指令,Java编译器的许多优化比如说包装类型与根底类型的的调优等,kotlin 需求自己完成一份,一起新增了许多自己的优化思路。
今日先告一段落
其实这篇还有一些常识点还没有拆出来的,剩余的都是和协程相关的,由于有些组件代码需求在Android 中调用,一起我对协程相关有些常识点掌握的不是太牢固,需求先写demo 才能够拆分成细碎的常识点。sorry啊