Kotlin
New syntax introduced in --delegate. Delegation is a design pattern. Its basic idea is: the operation object will not handle a certain piece of logic itself, but will delegate the work to another auxiliary object for processing. For example, calling a method A
of a class methodA
is actually the execution B
of the class behind it.methodB
Kotlin
The delegation function is divided into two types: class delegation and delegation property.
The core idea of class delegation is to delegate the concrete implementation of a class to another class to complete. For example Set
, the data it stores is out of order and cannot store duplicate data. Set
Is an interface, if you want to use it, you need to use its specific implementation class, for example HashSet
. With the help of the delegation model, you can easily implement your own implementation class. For example, define one here MySet
and let it implement Set
the interface. The code is as follows:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> {
override val size: Int
get() = helperSet.size
override fun contains(element: T) = helperSet.contains(element)
override fun containsAll(elements: Collection<T>) = helperSet.containsAll(elements)
override fun isEmpty() = helperSet.isEmpty()
override fun iterator() = helperSet.iterator()
}
It can be seen that a parameter MySet
is received in the constructor , which is equivalent to an auxiliary object. HashSet
Then, in Set
the implementation of all the methods of the interface, they did not implement their own implementation, but called the corresponding method implementation in the auxiliary object, which is actually a delegation mode.
Well, since they are all implemented by calling the method of the auxiliary object, it is better to use the auxiliary object directly. It is true to say so, but if we just let most of the method implementations call the methods in the auxiliary object, and a small number of method implementations are rewritten by ourselves, or even add some of our own unique methods, then it will become a brand MySet
new Data structure classes, this is the meaning of the delegation pattern.
But this way of writing also has certain disadvantages. It’s okay if there are fewer methods to be implemented in the interface. If there are dozens or even hundreds of methods, each of them will call the corresponding method in the auxiliary object to implement it. I cried. So is there any solution to this problem? There is really no such thing in Java
, but Kotlin
it can be solved by the function of class delegation in .
Kotlin
The keywords used in the delegate are that by
you only need to use the keyword after the interface declaration by
, and then connect the delegated auxiliary object, you can save a lot of template code written before, as shown below:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by heplerSet {
}
The effect of these two pieces of code is exactly the same, but after using the function of class delegation, the code is obviously simplified too much. In addition, if we want to reimplement a certain method, we only need to rewrite that method separately, and other methods can still enjoy the convenience brought by class delegation, as shown below:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
fun helloWorld() = println("Hello World")
override fun isEmpty() = false
}
A new method is added here helloWorld()
, and the method is rewritten isEmpty()
to make it return forever false
. This is of course a wrong approach, here is just for demonstration. Now MySet
it has become a brand new data structure class, not only will it never be empty, but it can also print helloWorld()
. As for Set
the functions in other interfaces, it is HashSet
consistent with . This is Kotlin
what class delegates can do.
The core idea of class delegation is to delegate the specific implementation of a class to another class, and the core idea of delegated attributes is to delegate the specific implementation of an attribute (field) to another class. The syntax structure of delegated properties is as follows:
class MyClass {
var p by Delegate()
}
It can be seen that by
keywords are used to connect p
the properties on the left and Delegate
the instances on the right. This way of writing means that p
the specific implementation of the properties is entrusted to Delegate
the class to complete. When the property is called, the method of the class p
is automatically called , and when the property is assigned a value, the method of the class is automatically called .Delegate
getValue()
p
Delegate
setValue()
Therefore, it is Delegate
necessary to implement the class concretely. The code is as follows:
class Delegate {
var propValue: Any? = null
operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {
return propValue
}
operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {
propValue = value
}
}
This is a standard code implementation template. In Delegate
the class, we must implement getValue()
and setValue()
these two methods, and must use operator
keywords to declare.
getValue()
The method needs to receive two parameters: the first parameter is used to declare in which class the delegate function of Delegate
this class can be used, which is written here MyClass
to indicate that it can only MyClass
be used in the class; the second parameter KProperty<*>
is Kotlin
an attribute operation class in the class, which can be used in Get the values related to various attributes, which is not needed in the current scenario, but must be declared on the method parameters. In addition, <*>
this generic way of writing means that you don't know or don't care about the specific type of the generic, just to compile it through the grammar, which is somewhat similar to the way of writing Java
in <?>
. As for the return value, it can be declared as any type, just write it according to the specific implementation logic. The above code is just an example.
setValue()
The method is similar, except that it takes 3
a parameter. The first two parameters getValue()
are the same as the method, and the last parameter indicates the value to be assigned to the delegate property. The type of this parameter must be getValue()
consistent with the type of the method return value.
The workflow of the entire delegated property is implemented in this way. Now when we assign a value MyClass
to the property, the method of the class p
will be called , and when the value of the property is obtained, the method of the class will be called .Delegate
setValue()
MyClass
p
Delegate
getValue()
However, there is actually a situation where you don't need to Delegate
implement setValue()
the method in the class, that is, the properties MyClass
in the class p
are val
declared using keywords. This is also easy to understand. If p
the attribute is val
declared using keywords, it means that p
the attribute cannot be reassigned after initialization, so there is no need to implement setValue()
the method, just implement getValue()
the method.
Kotlin
by
The effect of delegation can be realized through keywords. For example by lazy { }
, it is actually the delayed initialization syntax implemented by delegation. Here's how it's used:
val laziness: String by lazy {
// by lazy实现延迟初化效果
println("I will hava a value")
"I am a lazy-initialized string"
}
A lazy loading technique is used here to put the code that you want to delay execution into by lazy
the code block, so that the code in the code block will not be executed at the beginning, only when the laziness
variable is called for the first time, the code in the code block code will be executed.
After learning Kotlin
the delegate function of , you can by lazy
decrypt the working principle of , and its basic grammatical structure is as follows:
val p by lazy {
... }
by lazy
It is not a keyword linked together, only by
a Kotlin
keyword in is, lazy
which is just a higher-order function here. lazy
An object will be created and returned in the function . Delegate
When we call p
the property, we actually call the method Delegate
of the object getValue()
, and then the expression passed in by the function getValue(
will be called in the method , so that the code in the expression can be executed , and the value obtained after calling the property is the return value of the last line of code in the expression.lazy
Lambda
p
Lambda
From this point of view, Kotlin
the lazy loading technology is not so mysterious. After mastering its implementation principle, we can also implement a lazy
function of our own. Create a new Later.kt
file and write the following code:
class Later<T>(val block: () -> T) {
}
First, a class is defined Later
and designated as a generic class. Later
The constructor accepts a function type parameter, which does not receive any parameters, and the return value type is the Later
generic type specified by the class.
Then Later
implement getValue()
the method in the class, the code is as follows:
class Later<T>(val block: () -> T) {
var value: Any? = null
operator fun getValue(any: Any?, prop: KProperty<*>): T {
if (value == null) {
value = block()
}
return value as T
}
}
Here, getValue()
the first parameter of the method is specified as Any?
a type, indicating that the desired Later
delegation function can be used in all classes. Then use a value
variable to cache the value, if value
it is empty, call the function type parameter passed in the constructor to get the value, otherwise return directly. Since lazy loading technology does not assign values to attributes, there is no need to implement setValue()
methods here.
The code is written here, and the function of the delegate property has been completed. Although we can use it immediately, in order to make its usage more like lazy
a function, it is better to define a top-level function. Later.kt
This function can be written directly in the file, but it must be defined Later
outside the class, because only functions that are not defined in any class are top-level functions. The code looks like this:
fun <T> later(block: () -> T) = Later(block)
We also define this top-level function as a generic function, and it also receives a function type parameter. The function of this top-level function is very simple: create Later
an instance of the class, and pass the received function type parameter to Later
the constructor of the class.
Now, our self-written later
lazy loading function is complete, you can directly use it to replace the previous lazy
function, as shown below:
val uriMatcher by later {
val matcher = UriMatcher(UriMatcher.NO_MATCH)
matcher.addURI(authority, "book", bookDir)
matcher.addURI(authority, "book/#", bookItem)
matcher.addURI(authority, "category", categoryDir)
matcher.addURI(authority, "category/#", categoryItem)
matcher
}
But how can we verify later
whether the lazy loading function of the function has taken effect? Here I have a very simple and convenient verification method, which is written as follows:
val p by later {
Log.d("TAG", "run codes inside later block")
"test later"
}
As you can see, we later
printed a line of log in the code block of the function. Put this code in either Activity
and call p
the property on the button's click event.
You will find that the log line in the function will not be printed Activity
when it is started . later
Only when you click the button for the first time, the log will be printed, indicating that the code in the code block was executed successfully. And when you click the button again, the log will not be printed again, because the code in the code block will only be executed once.
In this way, you can verify whether the lazy loading function has taken effect, and you can test it yourself.
In addition, it must be noted that although we have written a lazy loading function of our own, for the sake of simplicity, the basic implementation principle of the function is only roughly restored here, and lazy
some aspects such as synchronization and null value handling are not implemented very rigorously. . Therefore, in a formal project, using the Kotlin
built-in lazy
function is the best choice.
Requirements can be implemented through delegation instead of multiple inheritance:
interface CanFly {
fun fly()
}
interface CanEat {
fun eat()
}
open class Flyer : CanFly {
override fun fly() {
println("I can fly")
}
}
open class Animal : CanEat {
override fun eat() {
println("I can eat")
}
}
class Bird(flyer: Flyer, animal: Animal) : CanFly by flyer, CanEat by animal {
}
fun main() {
val flyer = Flyer()
val animal = Animal()
val b = Bird(flyer, animal)
b.fly()
b.eat()
}
Some people may have doubts: First, why is the delegation method so similar to interface implementation of multiple inheritance, and it does not seem to be much simpler. Second, this method seems to be very similar to composition, so what advantages does it have? There are two main points:
- As mentioned earlier, the interface is stateless, so even if it provides a default method implementation, it is very simple and cannot implement complex logic, and it is not recommended to implement complex method logic in the interface. We can use the method of delegation above, although it is also an interface delegation, but it uses a specific class to implement method logic, which can have more powerful capabilities;
- Assuming that the class we need to inherit is
A
, the delegate object isB
,C
, when we call it specifically, it is not like a combinationA.B.method
, but can be called directlyA.method
, which can better express the abilityA
to have thismethod
, and is more intuitive, although it is also through the delegate object. Execute specific method logic;