那些你不知道的Kotlin冷知识

Posted datian1234

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了那些你不知道的Kotlin冷知识相关的知识,希望对你有一定的参考价值。

Lambda表达式

Lambda固然好用 ,但是你知道Kotlin是如何实现的吗 ?
kotlin代码

 fun foo(item: Int) =  print(item) 

转换为 java字节码

 @NotNull
 public final Function0 foo(final int item) 
    return (Function0)(new Function0() 
       // $FF: synthetic method
       // $FF: bridge method
       public Object invoke() 
          this.invoke();
          return Unit.INSTANCE;
       
 
       public final void invoke() 
          int var1 = item;
          System.out.print(var1);
       
    );
 

可以看到这个 Lambda方法体 实际上是Function0的一个匿名内部类 。
Function没有参数所以是0,如果有一个参数就是Function1 ,Java 最多到22, 但是Kotlin设计了FunctionN 。
综上所述 ,多处使用Lambda表达式会使程序中的匿名内部类增多 ,这里Kotlin团队肯定也想到了 ,所以他们提供了inline函数来解决这个问题 。

lazy

lazy的背后是接受一个lambda并返回一个Lazy实例的函数,第一次访问该属性时,会执行lazy对应的Lambda表达式并记录结果,后续访问该属性时只是返回记录的结果。

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable 
    //lambda 表达式,负责拿对象示例
    private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // final field is required to enable safe publication of constructed instance
    private val lock = lock ?: this

    override val value: T
        get() 
            val _v1 = _value
            if (_v1 !== UNINITIALIZED_VALUE) 
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            

            return synchronized(lock) 
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) 
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                 else 
                    //拿到对象之后进行赋值给_value
                    val typedValue = initializer!!()
                    _value = typedValue
                    initializer = null
                    typedValue
                
            
        

    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE

    ...

Lazy的同步锁:系统会给lazy属性默认加上同步锁,也就是LazyThreadSafetyMode.SYNCHRONIZED,它在同一时刻只允许一个线程对lazy属性进行初始化,所以它是线程安全的。 (当然也可以传递给Lazy LazyThreadSafetyMode.NONE这个属性,表示不会有多线程方面的处理,也就没有线程方面的开销)

还有一个lateinit也可以实现延迟赋值 ,但是这个属性不支持基本数据类型的参数 。

[如果基础类型的数据也想要延时加载怎么办 ?] 通过Delegates.notNull<T>可以实现

 val index by Delegates.notNull<Int>()

有两种方式可以实现延时初始化: by lazy lateinit

 /**
  * 懒加载
  * 变量必须是val修饰
  * 首次调用进行赋值,赋值之后将不可再修改
  *
  * 系统会默认给lazy 属性默认加上同步锁 ,同一时刻只有一个线程访问 ,保证安全 ,当然也可以设置为NONE来关闭提升性能
  *
  */
 val sex by lazy(LazyThreadSafetyMode.NONE) 
     if(color) "male" else "female"
 
 
 //与lazy不同 lateinit可以延时赋值 但是它不能用于基础类型
 lateinit var mName: String
 
 //这样会报错 ,报错信息 : 'lateinit' modifier is not allowed on properties of primitive types
 //如果想要不报错有两种方式: 
 //1. 用Integer类型替代
 //2. 使用委托 Delegates.notNull<T> 的方式
 lateinit var mAge: Int
 
 fun setName(name: String) 
     this.mName = name
 

数据类

kotlin

data class CarInfo(val name: String, val color: String)

转换成Java

public final class CarInfo 
   @NotNull
   private final String name;
   @NotNull
   private final String color;

   @NotNull
   public final String getName() 
      return this.name;
   

   @NotNull
   public final String getColor() 
      return this.color;
   

   public CarInfo(@NotNull String name, @NotNull String color) 
      Intrinsics.checkNotNullParameter(name, "name");
      Intrinsics.checkNotNullParameter(color, "color");
      super();
      this.name = name;
      this.color = color;
   

   @NotNull
   public final String component1() 
      return this.name;
   

   @NotNull
   public final String component2() 
      return this.color;
   

   @NotNull
   public final CarInfo copy(@NotNull String name, @NotNull String color) 
      Intrinsics.checkNotNullParameter(name, "name");
      Intrinsics.checkNotNullParameter(color, "color");
      return new CarInfo(name, color);
   

   // $FF: synthetic method
   public static CarInfo copy$default(CarInfo var0, String var1, String var2, int var3, Object var4) 
      if ((var3 & 1) != 0) 
         var1 = var0.name;
      

      if ((var3 & 2) != 0) 
         var2 = var0.color;
      

      return var0.copy(var1, var2);
   

   @NotNull
   public String toString() 
      return "CarInfo(name=" + this.name + ", color=" + this.color + ")";
   

   public int hashCode() 
      String var10000 = this.name;
      int var1 = (var10000 != null ? var10000.hashCode() : 0) * 31;
      String var10001 = this.color;
      return var1 + (var10001 != null ? var10001.hashCode() : 0);
   

   public boolean equals(@Nullable Object var1) 
      if (this != var1) 
         if (var1 instanceof CarInfo) 
            CarInfo var2 = (CarInfo)var1;
            if (Intrinsics.areEqual(this.name, var2.name) && Intrinsics.areEqual(this.color, var2.color)) 
               return true;
            
         

         return false;
       else 
         return true;
      
   

😲, 有点恐怖,但是也不要慌,大部分都是get set方法 ,不过有两个方法是Java原来没有的,component 和 copy,接下来我们就来看一看这两个方法。

copy

public static CarInfo copy$default(CarInfo var0, String var1, String var2, int var3, Object var4) 
    if ((var3 & 1) != 0) 
        var1 = var0.name;
    

    if ((var3 & 2) != 0) 
        var2 = var0.color;
    

    return var0.copy(var1, var2);

可以看到新生成的对象 var0的值将直接复制原地址的值 。
这好像是浅拷贝呀😶,我们来做了实验 。

val carInfo = CarInfo("哈弗大狗", "黑色", "benzine")
//这里用到了解构
val (name, color, engine) = carInfo
val carInfo1 = carInfo.copy()
//* !!!这里注意 基础数据类型不包括String ,但是常量赋值的String,对象地址本来就改变了,所以不会出问题
carInfo1.engine = "xxxxxx"
println(carInfo1.engine)
println("$name, $color,$carInfo.engine")

>>>>>>>>>
xxxxxx
哈弗大狗, 黑色,benzine

咦~ 好像没什么问题呀 ! 复制后的对象修改属性也没有对元对象进行修改 。
这是因为虽然基础数据类型不包括String ,但是常量赋值的String, 对象地址本来就改变了,所以不会出问题

浅拷贝需要注意的地方是:除了基础类型的数据 ,其他的类型都是复制引用 ,也就是引用的同一个对象 。

这次我们试一试增加一个类看一看。
比如咱们的 carInfo中增加了一个属性 Person,这是一个类 ,通过copy进行浅复制后就会出现问题 。

//我们这样修改CarInfo类
data class CarInfo(val name: String, var color: String) 
    operator fun component3(): Person 
        return person
    

    var person = Person("json")
    
    //次构造函数
    constructor(name: String, color: String, person: Person): this(name, color) 
        this.person = person
    


data class Person(var name: String)



val carInfo = CarInfo("哈弗大狗", "黑色", Person("benzine"))
val (name, color, person) = carInfo

//注意我们这里还是浅拷贝
val carInfo1 = carInfo.copy()
carInfo1.person.name = "xxxxx"
carInfo1.color = "红色"


println("carInfo1 $carInfo1.person.hashCode()  carInfo $carInfo.person.hashCode()")

println("$name, $carInfo.color,$carInfo.person.name")


>>>>>>>>>>>>>>>>

carInfo1 114516600  carInfo -222081999
哈弗大狗, 黑色,benzine

意想不到的事情又发生了 ,不对不对 ,一定是哪里出问题了(我的推论怎么可能出问题呢🤨) 。
答案在CarInfo中 ,因为我们的person在次构造方法中 ,所以自动生成的copy方法也没有处理这个person ,也就是没有进行对象赋值 ,所以直接使用的是创建的那个原始person ,名叫json

 operator fun component3(): Person 
     return person
 
 //这里重新创建了一个对象
 var person = Person("json")

这里也提醒我们在data class中尽量不要使用次构造函数 ,会带来种种问题 。
细心的同学注意到了我们加入person的时候,多加入了一个Component方法。这个方法是干啥的 ?
这里与一个概念解构有关 ,为了可以顺利的实现 解构,所以加入了这个方法 ,Kotlin默认对主构造函数的属性自动生成component方法 ,而次构造函数就没有 ,所以需要手动生成 。
如果将次构造删掉 ,将person移动到主构造中,就会发现浅拷贝的问题出来了


carInfo1 114516600  carInfo 114516600
//copy对象的更改导致了 原来对象值的变化
哈弗大狗, 黑色, xxxxx

嗯,Nice 。
问题揪出来了,我们该如何解决呢 ?
这里只需要实现一个深拷贝就可以了。

fun deepCopy(): CarInfo 
    val carInfo = this.copy()
    //其实也很简单就是调用了子类对象的copy方法
    carInfo.person = carInfo.person.copy()
    return carInfo

其实浅拷贝和深拷贝问题不是重点 ,这里主要是为了分析Kotlin版本该如何实现,如何避免错误。

内联函数

fun main() 
    foo 
        println("在方法块里 输出一个东西")
    


fun foo(block:() -> Unit) 
    block.invoke()
    println("输出一个东西")



>>> 转换成java

public final void main() 
    //传入一个Function0对象 这里就是很厉害了 每次都是创建一个对象
    this.foo((Function0)null.INSTANCE);


public final void foo(@NotNull Function0 block) 
    Intrinsics.checkNotNullParameter(block, "block");
    block.invoke();
    String var2 = "输出一个东西";
    System.out.println(var2);



//加入内联之后呢
inline fun foo(block:() -> Unit) 
    block.invoke()
    println("输出一个东西")


>>> java

public final void main() 
    int $i$f$foo = false;
    int var3 = false;
    //可以看到执行的代码都被内嵌在这里了
    String var4 = "在方法块里 输出一个东西";
    System.out.println(var4);
    String var5 = "输出一个东西";
    System.out.println(var5);


public final void foo(@NotNull Function0 block) 
    int $i$f$foo = 0;
    Intrinsics.checkNotNullParameter(block, "block");
    block.invoke();
    String var3 = "输出一个东西";
    System.out.println(var3);

以上就是内联函数的优化
接下来我们来看个示例

fun main() 
    foo 
        return
    


inline fun foo(block:() -> Unit) 
    println("输出一个东西")
    block.invoke()
    println("输出两个东西")

以上情况会输出什么呢 ?
答案是:输出一个东西

这里就是内联函数的一个小坑,他会使代码提前返回,其实看懂了内联的实现之后,这个提前返回也很容易想明白

最后

文章中如果有问题的地方,欢迎大家多多评论指正,另外还是希望得到大家的点赞支持✨😉

作者:酷的鑫
链接:https://juejin.cn/post/7067832301941424141
更多Android学习笔记+视频资料可点击下方卡片免费在线领取!

以上是关于那些你不知道的Kotlin冷知识的主要内容,如果未能解决你的问题,请参考以下文章

你不知道的冷知识:JSON.stringify 居然还能这样用?

常被人忽略的顽疾之一!!!你不知道的冷知识

C#冷知识系列那些你知道或者不知道的奇淫巧技

科普你知道,运动冷知识,火速围观

那些你不知道的HTML知识,快来学习一下吧

那些你不知道的 TCP 冷门知识