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("输出两个东西")
}
以上情况会输出什么呢 ?
答案是:输出一个东西
这里就是内联函数的一个小坑,他会使代码提前返回,其实看懂了内联的实现之后,这个提前返回也很容易想明白
最后
文章中如果有问题的地方,欢迎大家多多评论指正,另外还是希望得到大家的点赞支持✨😉
作者:酷的鑫