⬆️ ⬇️

Kotlin M5.3: Delegated Properties

Not so long ago, we released the next Milestone programming language Kotlin , M5.3.

This release includes quite a few different changes: from refactorings to new features in the language. Here I want to tell about the most interesting change: support for delegated properties (delegated properties).



Many want the language to support



In principle, you can say to all these people, they say, you want too much, and life, they say, is hard ... Another option: to support each type of property in a language in a special way ... Personally, I don’t like both options: sad users cast despondency, and to get special support for every sneeze - it is very expensive when developing a language. So we chose the third way: we developed a generalized mechanism that allows us to express different types of properties as ordinary library classes, without the need to support each of them separately in the language.



Delegated Properties



Let's start with an example:

')

class Example { var p: String by Delegate() } 


A new syntax has appeared: now after the property type you can write “by <expression>”. The expression after “by” is a delegate : the getter and setter calls for this property will be delegated to the value of this expression. We do not require a delegate to implement an interface; it is enough that it has get () and set () functions with a specific signature:



 class Delegate() { fun get(thisRef: Any?, prop: PropertyMetadata): String { return "$thisRef, thank you for delegating '${prop.name}' to me!" } fun set(thisRef: Any?, prop: PropertyMetadata, value: String) { println("$value has been assigned") } } 


(Some are afraid of the lack of a requirement to implement the interface. Do not be afraid if you are so calmer, here it is , even two - implement :))



If we read the value of the property p, the get () function is called from the Delegate class, and the first parameter is the object from which the property is requested, and the second is the object that describes the property p (it can, in particular, find out the name of the property) :



 val e = Example() println(ep) 


This example will output “Example @ 33a17727, thank you for delegating 'p' to me!”.



Similarly, when a property is written, set () is called. The first two parameters are the same as get (), and the third is the property value to be assigned:



 ep = "NEW" 


This example will display “NEW has been assigned to 'p' in Example @ 33a17727”.



You probably already guessed how you can implement lazy properties, etc.? You can try to do it yourself, but most of this is already implemented in the standard Kotlin library . The most commonly used delegates are defined in the kotlin.properties.Delegates object.



Lazy properties



Let's start with lazy:



 import kotlin.properties.Delegates class LazySample { val lazy: String by Delegates.lazy { println("computed!") "Hello" } } 


The Delegates.lazy () function returns a delegate object that implements a lazy evaluation of the property value: the first call to get () starts the lambda expression passed to lazy () as an argument, and remembers the resulting value; subsequent calls simply return the memorized.



If you want to use lazy properties in a multi-threaded program, use the blockingLazy () function: it guarantees that the value will be calculated by exactly one thread and published correctly.



Observable properties



 class User { var name: String by Delegates.observable("<no name>") { d, old, new -> println("$old -> $new") } } 


The observable () function takes two arguments: the initial value of the property and the handler (lambda expression) that is called with each assignment. The handler has three parameters: the description of the property that is being changed, the old value and the new value. If you need to be able to disable the assignment of some values, use the vetoable () function instead of observable ().



Properties without initializers



Relatively unexpected use of delegates: many users ask: “How to declare a not-null property, if I don’t have a value to initialize it with (will I assign it later)?”. Kotlin does not allow declaring non-abstract properties without initializers:



 class Foo { var bar: Bar // error: must be initialized } 


It would be possible to assign a null, then the type would no longer be “Bar”, but “Bar?”, And with each call it would be necessary to handle the case of a zero reference ... Now you can get away with a delegate:



 class Foo { var bar: Bar by Delegates.notNull() } 


If this property is considered before the first assignment, the delegate will throw an exception. After initialization, it simply returns the previously recorded value.



Storing properties in a hash table



Last example from library: storage of properties in Map. This is useful in “dynamic” code, for example, when working with JSON:



 class User(val map: Map<String, Any?>) { val name: String by Delegates.mapVal(map) val age: Int by Delegates.mapVal(map) } 


The constructor of this class takes a map:



 val user = User(mapOf( "name" to "John Doe", "age" to 25 )) 


Delegates calculate values ​​for stock keys — property names:



 println(user.name) // Prints "John Doe" println(user.age) // Prints 25 


Variable properties (var) are supported using the mapVar () function: values ​​are written with the same keys (for this, MutableMap is needed, not just the Map).



Conclusion



We talked about delegated properties, a mechanism that adds a new degree of freedom to the language and gives the opportunity to define its semantics for operations on properties. I showed examples that lie on the surface, but for sure there are still many ways to apply this mechanism, so welcome: invent and implement!



PS About other new items in Kotlin M5.3 you can read here (in English) .

Source: https://habr.com/ru/post/183444/



All Articles