话说不要过度解读,可是学习嘛,许多东西是需求解读的,假如说单纯的快速的学习运用黑箱形式其实是最好的,我不需求知道他是什么意思,只需求在需求的时分正确的把他整处理就行。可是卷到如今的今日,黑箱形式现已不行了,你得知道为什么,那就卷吧。

作为一个运用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啊