This is the first time I participate in 2022 more challenge the third day, view the full details of: 2022 for the first time, the more challenging | create learning continues to grow, indiana stage mode and win prizes
Lambda expressions
Lambda is nice, but do you know how Kotlin implemented it?
Kotlin code
fun foo(item: Int) = { print(item) }
Copy the code
Convert to Java bytecode
@NotNull
public final Function0 foo(final int item) {
return (Function0)(new Function0() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke(a) {
this.invoke();
return Unit.INSTANCE;
}
public final void invoke(a) {
intvar1 = item; System.out.print(var1); }}); }Copy the code
You can see that the Lambda method body is actually an anonymous inner class of Function0.
Function has no arguments so it’s 0. If it has one argument it’s Function1, Java goes up to 22 at most, but Kotlin designed FunctionN.
In summary, multiple Lambda expressions can lead to more anonymous inner classes in a program, which Kotlin’s team must have figured out, so they provided inline functions to solve the problem.
lazy
Behind lazy is a function that takes a lambda and returns an instance of lazy
. The first access to the property executes the lambda expression corresponding to lazy and records the result, and subsequent accesses to the property simply return the result of the record.
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
//lambda expression, which is responsible for taking the object example
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private vallock = 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 {
// Get the object and assign it to _value
valtypedValue = initializer!! () _value = typedValue initializer =null
typedValue
}
}
}
override fun isInitialized(a): Boolean= _value ! == UNINITIALIZED_VALUE ... }Copy the code
Can give a Lazy Lazy synchronization locks: system attribute default and synchronization lock, namely LazyThreadSafetyMode. SYNCHRONIZED, it allows only one thread at the same time to the attributes of the Lazy initialization, so it is thread-safe. (of course, can also be passed to the Lazy LazyThreadSafetyMode. NONE of the attributes, says it will not have a multithreaded processing, there would be no thread spending)
There is also an lateInit property that allows lazy assignment, but this property does not support arguments to the primitive data type.
What if the underlying type of data also wants to be loaded lazily? Delegates. NotNull
can be achieved
val index by Delegates.notNull<Int> ()Copy the code
There are two ways to implement delayed initialization: by lazy and Lateinit
/** * Lazy must be val modifier * the first time it is called, and cannot be changed after the assignment ** The lazy attribute is locked by default, so that only one thread can access it at a time
val sex by lazy(LazyThreadSafetyMode.NONE) {
if(color) "male" else "female"
}
// Unlike lazy, LateInit can delay assignments but it cannot be used for base types
lateinit var mName: String
'Lateinit' modifier is not allowed on properties of primitive types
// There are two ways to avoid reporting an error:
//1. Use the Integer type instead
//2. Delegates Delegates. Null
lateinit var mAge: Int
fun setName(name: String) {
this.mName = name
}
Copy the code
Data classes
kotlin
data class CarInfo(val name: String, val color: String)
Copy the code
Converted to Java
public final class CarInfo {
@NotNull
private final String name;
@NotNull
private final String color;
@NotNull
public final String getName(a) {
return this.name;
}
@NotNull
public final String getColor(a) {
return this.color;
}
public CarInfo(@NotNull String name, @NotNull String color) {
Intrinsics.checkNotNullParameter(name, "name");
Intrinsics.checkNotNullParameter(color, "color");
super(a);this.name = name;
this.color = color;
}
@NotNull
public final String component1(a) {
return this.name;
}
@NotNull
public final String component2(a) {
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(a) {
return "CarInfo(name=" + this.name + ", color=" + this.color + ")";
}
public int hashCode(a) {
String var10000 = this.name;
intvar1 = (var10000 ! =null ? var10000.hashCode() : 0) * 31;
String var10001 = this.color;
returnvar1 + (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; }}}Copy the code
😲, a bit scary, but don’t panic. Most of these methods are get set methods, but there are two that Java doesn’t have: Component and copy. Let’s take a look at these two methods.
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);
}
Copy the code
You can see that the value of the newly generated object var0 will copy the value of the original address directly.
This seems to be a shallow copy 😶, we did the experiment.
val carInfo = CarInfo("Huffy Dog."."Black"."benzine")
// Destruct is used here
val (name, color, engine) = carInfo
val carInfo1 = carInfo.copy()
/ / *!!!!!! Note here that the underlying data type does not include strings, but the address of the object assigned by the constant is already changed, so there should be no problem
carInfo1.engine = "xxxxxx"
println(carInfo1.engine)
println("$name.${color}.${carInfo.engine}") >>>>>>>>> XXXXXX Huff Dog, black,benzineCopy the code
Yi ~ seems to have no problem! The copied object modification properties also do not modify the meta-object.
This is because, although the underlying data type does not include strings, the address of the object assigned by the constant is already changed, so there is no problem
One thing to note about shallow copy is that all types, except for the underlying type of data, are copied references, that is, the same object referenced.
Let’s try adding a class this time to take a look.
For example, we added a Person attribute to carInfo, which is a class, and the problem is caused by shallow copy.
// We modify the CarInfo class like this
data class CarInfo(val name: String, var color: String) {
operator fun component3(a): Person {
return person
}
var person = Person("json")
// subconstructor
constructor(name: String, color: String, person: Person): this(name, color) {
this.person = person
}
}
data class Person(var name: String)
val carInfo = CarInfo("Huffy Dog."."Black", Person("benzine"))
val (name, color, person) = carInfo
// Note that we have a shallow copy here
val carInfo1 = carInfo.copy()
carInfo1.person.name = "xxxxx"
carInfo1.color = "Red"
println("carInfo1 ${carInfo1.person.hashCode()} carInfo ${carInfo.person.hashCode()}")
println("$name.${carInfo.color}.${carInfo.person.name}")
>>>>>>>>>>>>>>>>
carInfo1 114516600 carInfo -222081999Huff dog, black,benzineCopy the code
Something unexpected has happened again, no, no, something must be wrong (how could my reasoning be wrong 🤨). The answer is in CarInfo. Since our Person is in the secondary constructor, the auto-generated copy method does not process the person either, i.e. it does not assign the object, so it uses the original person created, named JSON.
operator fun component3(a): Person {
return person
}
// Here an object is recreated
var person = Person("json")
Copy the code
It is also a reminder to avoid using subconstructors in data classes, which can cause problems.
Careful students noticed that when we added person, we added a Component method. What does this method do?
This is related to the concept of deconstruction. In order to implement deconstruction smoothly, this method is added. Kotlin automatically generates component methods on the properties of the primary constructor by default, while the secondary constructor does not, so it needs to be generated manually.
If you delete the secondary construct and move the Person into the main construct, you’ll see the shallow copy problem
>>>>>>>>>>>>>>>>
carInfo1 114516600 carInfo 114516600
// A change in the copy object causes a change in the value of the original objectHarvard Dog, black, XXXXXCopy the code
Well, Nice.
The problem has been identified. How can we solve it?
All you need to do here is implement a deep copy.
fun deepCopy(a): CarInfo {
val carInfo = this.copy()
// Call the copy method of the subclass object
carInfo.person = carInfo.person.copy()
return carInfo
}
Copy the code
Shallow copy and deep copy issues are not the focus, but rather how the Kotlin version should be implemented and how to avoid errors.
Inline function
fun main(a) {
foo {
println("Output something in a method block.")}}fun foo(block:() -> Unit) {
block.invoke()
println("Output something"} >>> convert to Javapublic final void main() {
// Pass in a Function0 object
this.foo((Function0)null.INSTANCE);
}
public final void foo(@NotNull Function0 block) {
Intrinsics.checkNotNullParameter(block, "block");
block.invoke();
String var2 = "Output something";
System.out.println(var2);
}
// add inline
inline fun foo(block:() -> Unit) {
block.invoke()
println("Output something")
}
>>> java
public final void main() {
int $i$f$foo = false;
int var3 = false;
// You can see that the executed code is embedded here
String var4 = "Output something in a method block.";
System.out.println(var4);
String var5 = "Output something";
System.out.println(var5);
}
public final void foo(@NotNull Function0 block) {
int $i$f$foo = 0;
Intrinsics.checkNotNullParameter(block, "block");
block.invoke();
String var3 = "Output something";
System.out.println(var3);
}
Copy the code
So that’s the optimization of the inline function
Let’s look at an example
fun main(a) {
foo {
return}}inline fun foo(block:() -> Unit) {
println("Output something")
block.invoke()
println("Output two things.")}Copy the code
What is the output of the above situation?
The answer is: output something
So this is a little bit of a hole in the inline function, and it makes the code return ahead of time, which is actually pretty easy to figure out if you look at the inline implementation
The last
If you have any questions in this article, you are welcome to comment on it. In addition, we hope to get your thumbs up and support ✨😉