那些你不知道的Kotlin冷知识

134 篇文章 7 订阅
49 篇文章 1 订阅

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学习笔记+视频资料可点击下方卡片免费在线领取!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值