This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.
Commissioned by class
Class delegates actually correspond to the proxy pattern in Java
interface Base{
fun text(a)
}
// Delegate class (real class)
class BaseImpl(val x :String ) : Base{
override fun text(a) {
println(x)
}
}
// Delegate class
class Devices (b :Base) :Base by b
fun main(a){
var b = BaseImpl("I am the real class.")
Devices(b).text()
}
Copy the code
The output
I am real classCopy the code
As you can see, the delegate class (proxy class) holds the object of the real class, then the delegate class (proxy class) calls the method of the same name of the real class, and finally really implements the method of the real class. This is actually the proxy pattern
The delegate implementation in Kotlin uses the by keyword, which is followed by the delegate class, which can be an expression
Decompile into Java code and let’s see
public final class Devices implements Base {
// $FF: synthetic field
private final Base $$delegate_0;
public Devices(@NotNull Base b) {
Intrinsics.checkNotNullParameter(b, "b");
super(a);this.$$delegate_0 = b;
}
public void text(a) {
this.$$delegate_0.text(); }}public final class BaseImpl implements Base {
@NotNull
private final String x;
public void text(a) {
String var1 = this.x;
boolean var2 = false;
System.out.println(var1);
}
@NotNull
public final String getX(a) {
return this.x;
}
public BaseImpl(@NotNull String x) {
Intrinsics.checkNotNullParameter(x, "x");
super(a);this.x = x; }}public interface Base {
void text(a);
}
public final class BaseKt {
public static final void main(a) {
BaseImpl b = new BaseImpl("I am the real class.");
(new Devices((Base)b)).text();
}
// $FF: synthetic method
public static void main(String[] var0) { main(); }}Copy the code
Devices hold the BaseImpl object and override the text method, which internally calls baseimp.text ().
Attribute to entrust
The set/ GET method is delegated to setValue/getValue. Therefore, the delegated class (real class) needs to provide setValue/getValue methods. The val attribute only needs to provide the setValue method
Attribute delegate syntax:
Var < attribute name >: < type > by < expression >
class B {
// Delegate properties
var a : String by Text()
}
// Delegate class (real class)
class Text {
operator fun getValue(thisRef: Any? , property:KProperty< * >): String {
return "Property owner =$thisRef, attribute name = '${property.name}'Property value'
}
operator fun setValue(thisRef: Any? , property:KProperty<*>, value: String) {
println("Property value =$valueAttribute name = '${property.name}'Property owner =$thisRef")}}fun main(a){
var b = B()
println(b.a)
b.a = "ahaha"
}
Copy the code
The output
Property owner = com.example.lib.weituo.B@27fa135a, property name ='a'Attribute value Attribute value = ahaha attribute name ='a'Property owner = com.example.lib.weituo.B@27fa135a
Copy the code
As you can see from the above example, property A is delegated to Text, and the Text class has setValue and getValue, so when we call property A’s get/set methods, Delegate to Text setValue/getValue, as shown in the example above.
ThisRef: Owner of the property
Property: A description of the property, which is of type KProperty<*> or its parent class
Value: Indicates the value of the attribute
Decompile into Java code and let’s see
public final class B {
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(B.class, "a"."getA()Ljava/lang/String;".0))};
@NotNull
private final Text a$delegate = new Text();
@NotNull
public final String getA(a) {
return this.a$delegate.getValue(this, $$delegatedProperties[0]);
}
public final void setA(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "
"
?>);
this.a$delegate.setValue(this, $$delegatedProperties[0], var1); }}public final class BKt {
public static final void main(a) {
B b = new B();
String var1 = b.getA();
boolean var2 = false;
System.out.println(var1);
b.setA("ahaha");
}
// $FF: synthetic method
public static void main(String[] var0) { main(); }}public final class Text {
@NotNull
public final String getValue(@Nullable Object thisRef, @NotNull KProperty property) {
Intrinsics.checkNotNullParameter(property, "property");
return "Attribute Owner =" + thisRef + ", attribute name = '" + property.getName() + "Value of 'attribute';
}
public final void setValue(@Nullable Object thisRef, @NotNull KProperty property, @NotNull String value) {
Intrinsics.checkNotNullParameter(property, "property");
Intrinsics.checkNotNullParameter(value, "value");
String var4 = "Attribute value =" + value + "Attribute name = '" + property.getName() + "' Attribute owner =" + thisRef;
boolean var5 = false; System.out.println(var4); }}Copy the code
As you can see, class B holds the Text object. When the b.et () method is called, text.getValue () is internally called, and KProperty is created in B to hold the various parameters of the property
Simpler implementation of property delegates
We have to write getValue/setValue methods every time we implement a delegate, which is a bit of a hassle. We have interfaces to override these methods,ReadOnlyProperty and ReadWriteProperty
interface ReadOnlyProperty<in R, out T> {
operator fun getValue(thisRef: R, property: KProperty< * >): T
}
interface ReadWriteProperty<in R, T> {
operator fun getValue(thisRef: R, property: KProperty< * >): T
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
Copy the code
The delegated class simply implements the interface override method, and Val inherits ReadOnlyProperty
class Text1 : ReadOnlyProperty<Any, String> {
override fun getValue(thisRef: Any, property: KProperty< * >): String {
return "Property owner =$thisRef, attribute name = '${property.name}'Property value'}}class Text2 : ReadWriteProperty<Any, String> {
override fun getValue(thisRef: Any, property: KProperty< * >): String {
return "Property owner =$thisRef, attribute name = '${property.name}'Property value'
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
println("Property value =$valueAttribute name = '${property.name}'Property owner =$thisRef")}}class B {
val b :String by Text1()
val c :String by Text2()
}
Copy the code
Several delegates provided in the Koltin standard library
- Lazy properties: Its value is only evaluated at access time
- Observable Properties: Listeners are notified of changes to this property
- Map multiple attributes to (Map) instead of having a single field
The delay attribute lazy
Lazy () takes a lambda and returns an instance of lazy that can be used as a delegate to implement the delayed property, initialized only on the first call to the property
class Lazy {
val name :String by lazy {
println("First call initialization")
"aa"}}fun main(a){
var lazy =Lazy()
println(lazy.name)
println(lazy.name)
println(lazy.name)
}
Copy the code
The output
Initial call aa AA AACopy the code
Decompile Java code
public final class Lazy {
@NotNull
private final kotlin.Lazy name$delegate;
@NotNull
public final String getName(a) {
kotlin.Lazy var1 = this.name$delegate;
Object var3 = null;
boolean var4 = false;
return (String)var1.getValue();
}
public Lazy(a) {
this.name$delegate = kotlin.LazyKt.lazy((Function0)null.INSTANCE); }}// LazyKt.java
public final class LazyKt {
public static final void main(a) {
Lazy lazy = new Lazy();
String var1 = lazy.getName();
boolean var2 = false;
System.out.println(var1);
var1 = lazy.getName();
var2 = false;
System.out.println(var1);
var1 = lazy.getName();
var2 = false;
System.out.println(var1);
}
// $FF: synthetic method
public static void main(String[] var0) { main(); }}Copy the code
$delegate (kotlin.lazy), and the getName() method returns name$delegate.getValue().
Name $delegate is generated by kotlin.lazykt.lazy ((Function0) null.instance)
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
Copy the code
Ultimately generated by SynchronizedLazyImpl, continue with the source code
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
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 {
valtypedValue = initializer!! () _value = typedValue initializer =null
typedValue
}
}
}
override fun isInitialized(a): Boolean= _value ! == UNINITIALIZED_VALUEoverride fun toString(a): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
private fun writeReplace(a): Any = InitializedLazyImpl(value)
}
Copy the code
We can look directly at the get method of value, if _v1! UNINITIALIZED_VALUE == UNINITIALIZED_VALUE == UNINITIALIZED_VALUE == UNINITIALIZED_VALUE == UNINITIALIZED_VALUE == UNINITIALIZED_VALUE == UNINITIALIZED_VALUE
In fact, the same as their implementation of the delegate, also implement getValue method
Lazy delegate parameter
public enum class LazyThreadSafetyMode {
/** * Locks are used to ensure that only a single thread can initialize the [Lazy] instance. */
SYNCHRONIZED,
/** * Initializer function can be called several times on concurrent access to uninitialized [Lazy] instance value, * but only the first returned value will be used as the value of [Lazy] instance. */
PUBLICATION,
/** * No locks are used to synchronize an access to the [Lazy] instance value; if the instance is accessed from multiple threads, its behavior is undefined. * * This mode should not be used unless the [Lazy] instance is guaranteed never to be initialized from more than one thread. */
NONE,
}
Copy the code
- SYNCHRONIZED: Adds a synchronization lock to make lazy lazy thread initialization safe
- PUBLICATION: an initialized lambda expression that can be called multiple times at the same time, but only the return value of the first time is the initialized value
- NONE: No synchronization lock, not thread safe
val name :String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
println("First call initialization")
"aa"}}Copy the code
Observable attribute Observable delegate
You can observe the evolution of a property
class Lazy {
var a : String by Delegates.observable("Default value"){
property, oldValue, newValue ->
println( "$oldValue -> $newValue ")}}fun main(a){
var lazy =Lazy()
lazy.a = "First modification"
lazy.a = "Second modification"
}
Copy the code
The output
Default value -> First Change First change -> Second changeCopy the code
Vetoable commissioned
Like an Observable, a Vetoable can observe changes in properties, except that a Vetoable can decide whether to use new values
class C {
var age: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
println("oldValue = $oldValue -> oldValue = $newValue" )
newValue > oldValue
}
}
fun main(a) {
var c = C()
c.age = 5
println(c.age)
c.age = 10
println(c.age)
c.age = 8
println(c.age)
c.age = 20
println(c.age)
}
Copy the code
The output
oldValue = 0 -> oldValue = 5
5
oldValue = 5 -> oldValue = 10
10
oldValue = 10 -> oldValue = 8
10
oldValue = 10 -> oldValue = 20
20
Copy the code
If the new value is less than the old value, then it does not take effect, and you can see that the third value is 8, and it does not take effect if it is less than 10
Properties are stored in the Map
class D(valmap: Map<String, Any? >) {val name: String by map
val age: Int by map
}
fun main(a){
var d = D(mapOf(
"name" to "Xiao Ming"."age" to 12
))
println("name = ${d.name},age = ${d.age}")}Copy the code
The output
Name = Xiaoming,age =12
Copy the code