Currently I'm starting to learn Kotlin. I have a property like this:
var startTime: Int
get() = {
// read value from database
}
set(value) {
// save value to database
}
Here I always read and write the value every time I use the getter and setter.
Can this property be evaluated lazy? I want to read the value once the first time I use the getter and cache it for following calls. I know that values can be lazy but I found nothing about variables. What is the correct way in Kotlin to cache this property?
Kotlin offers lazy properties (https://kotlinlang.org/docs/reference/delegated-properties.html#lazy) that are computed on first access and cached.
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main(args: Array<String>) {
println(lazyValue)
println(lazyValue)
}
Will produce
computed!
Hello
Hello
What you need is not lazy evaluation, but a backing field:
private var _startTime: Int? = null
var startTime: Int
get() = {
if (_startTime != null) {
return _startTime!!
} else {
// read value from database and assign it to _startTime
}
}
set(value) {
_startTime = value
// save value to database
}
Alternatively you could declare _startTime as non-nullable and have an additional flag private var isStartTimeSet: Boolean = false used for checking if it has been fetched from database already.
Related
In Kotlin when you create a getter/setter pair, you typically set the getter using inline code. But I am wondering if it is possible to replace the inline code with an anonymous function:
var UserSettings: UserSettings?
get() = getUserSettings() // Replace this with an anonymous function?
set(value) {
putPref(USER_SETTINGS, Json.stringify(UserSettings.serializer(), value!!))
}
private fun getUserSettings(): UserSettings? {
val info = getPref(KEY_USER_SETTINGS)
return Json.parse(UserSettings.serializer(), info!!)
}
Can the getUserSettings() be replaced with an anonymous function? In the code above I have a separate function getUserSettings that I would like to place right after the get() =
Yes, you can. Just have a look at getters and setters - backing properties, where there is (the first and only example), mentioning get() { instead of get() =. Your sample would then look as follows:
var UserSettings: UserSettings?
get() {
val info = getPref(KEY_USER_SETTINGS)
return Json.parse(UserSettings.serializer(), info!!)
}
set(value) {
putPref(USER_SETTINGS, Json.stringify(UserSettings.serializer(), value!!))
}
i don't know this is what you asking for but it might be helpful
var v: Int? = null
get() = run {
return field
}
set(value) = run {
field = value
}
in this case getter must be equel to Int? and setter allways must be equel to Unit. so in the run we return that types
I think the answer of #Roland is valid.
Be aware that maintaining the = before the anonymous function the compiler returns an error as you are describing.
Can you double check that you are NOT writing something like this?
var UserSettings: UserSettings?
get() = { ... }
And that you are writing:
var UserSettings: UserSettings?
get() { ... }
I am familiar with Java, but I am having difficulty working with Kotlin.
To illustrate my question, here is some Java Code. If the getter finds the field to be NULL, it initializes the field, before returning the field.
package test;
public class InitFieldJava {
private final static String SECRET = "secret";
private String mySecret;
public String getMySecret() {
if(mySecret == null) initMySecret();
return mySecret;
}
private void initMySecret() {
System.out.println("Initializing Secret ....");
mySecret = SECRET;
}
public static void main(String[] args) {
InitFieldJava field = new InitFieldJava();
System.out.println(field.getMySecret());
}
}
Can I do something like the above in Kotlin. My attempt in Kotlin looks like this:
package test
class InitFieldKotlin {
private val SECRET = "secret"
private var mySecret: String? = null
get() {
if (mySecret == null) initMySecret() //Infinite Recursion!!!
return mySecret
}
private fun initMySecret() {
println("Initializing Secret ....")
mySecret = SECRET
}
companion object {
#JvmStatic
fun main(args: Array<String>) {
val field = InitFieldKotlin()
println(field.mySecret)
}
}
}
My problem is that this results in infinite recursion:
Exception in thread "main" java.lang.StackOverflowError
at test.InitFieldKotlin.getMySecret(InitFieldKotlin.kt:7)
at test.InitFieldKotlin.getMySecret(InitFieldKotlin.kt:7)
at test.InitFieldKotlin.getMySecret(InitFieldKotlin.kt:7)
at test.InitFieldKotlin.getMySecret(InitFieldKotlin.kt:7)
I’d appreciate knowing what I’m doing wrong.
Try to use field keyword inside get():
private var mySecret: String? = null
get() {
if (field == null) initMySecret()
return field
}
Generally speaking, field allows to access your value directly without calling get, almost in the same way as in your Java example. More information can be found in documentation.
The problem you're facing is that when you call your property this way, the getter will be called again. And when you call getter, another getter is called, and so on until an StackOverflow.
You can fix this as shown by #Google, and using field inside the getter, instead of the property name:
if (field == null)initMySecret()
This way you won't access the property using its getter.
But more importantly: why don't you use a lazy initialization? If the variable is final, and it seems to be, you could use a lazy val
This way, the field won't be nullable anymore, so you won't have to safe-call it. And you'll not use boilerplate code, Kotlin can do this lazy initialization for you!
val mySecret: String by lazy {
println("Initializing Secret. This print will be executed only once!")
"SECRETE" //This value will be returned on further calls
}
More examples on Lazy can be seen at Kotlin Docs
I find it can only access backing field in the set or get.Is there any way can access backing field in other place at class?
for example.
var width:Int=0
get() {
return field*10;
}
set(value) {
field=value/10;
}
I want to access the real value but not it multiple 10
when i using c#,there are no field keyword so always need to declare a new variable to store the real data.In the previous example it's will be something look like
private var _width=0;
var width:Int
get() {
return _width*10;
}
set(value) {
_width=value/10;
}
so if i want to access real value in the class,i can just access _value.
But in kotlin,is there have someway can just access backing field without these verbose declaration?
No. Your C# example works fine in Kotlin, it's called a backing property.
Kotlin, You can use backing properties
Backing Properties
If you want to do something that does not fit into this "implicit backing field" scheme, you can always fall back to having a backing property:
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // Type parameters are inferred
}
return _table ?: throw AssertionError("Set to null by another thread")
}
In all respects, this is just the same as in Java since access to private properties with default getters and setters is optimized so that no function call overhead is introduced.
Consider this Kotlin code:
var parent: T? = null
get() = if (isParent) this as T else field
set(value) { field = if (value == null) null else value.parent }
val isParent: Boolean
get() = parent == null
var description = ""
get() = if (isParent) field else parent!!.description
set(value) { if (isParent) field = value else parent!!.description = value }
Assume that isParent returns true if this instance is a parent instance. If not getParent() will return the parent instance. In Java you are allowed to access directly field of a different instance of same class like this:
String getDescription() { return getParent().description; }
void setDescription(String value) { getParent().description = value; }
(I am not saying that is a best thing to do, I simplified it for demostration). Comparing to Java, it would be nice to be able to do following:
var description = ""
get() = parent.field
set(value) { parent.field = value }
However this does not work and unfortunately it makes the code less readable. Especially if you have a lot of such variables, which are bound to this parent.
A backing field of a property can only be accessed from a getter or setter of that property, and only for the instance on which the getter or setter has been invoked. If you need to provide multiple ways to access an attribute of a class, you need to define two distinct properties, one of which has a backing field to store the data and another has a getter and setter referring to the first property.
class Foo {
var parent: Foo? = null
val parentOrSelf: Foo get() = parent ?: this
private var _description: String? = null
var description = ""
get() = parentOrSelf._description
set(value) { parentOrSelf._description = value }
}
Is there a way to tell if a lazy val has been initialised in Kotlin without initialising it in the process?
eg if I have a lazy val, querying if it is null would instantiate it
val messageBroker: MessageBroker by lazy { MessageBroker() }
if (messageBroker == null) {
// oops
}
I could potentially use a second variable, but that seems messy.
private var isMessageBrokerInstantiated: Boolean = false
val messageBroker: MessageBroker by lazy {
isMessageBrokerInstantiated = true
MessageBroker()
}
...
if (!isMessageBrokerInstantiated) {
// use case
}
Is there some sexy way of determining this, like if (Lazy(messageBroker).isInstantiated())?
Related (but not the same): How to check if a "lateinit" variable has been initialized?
There is a way, but you have to access the delegate object which is returned by lazy {}:
val messageBrokerDelegate = lazy { MessageBroker() }
val messageBroker by messageBrokerDelegate
if(messageBrokerDelegate.isInitialized())
...
isInitialized is a public method on interface Lazy<T>, here are the docs.
Since Kotlin 1.1, you can access a property delegate directly using .getDelegate().
You can write an extension property for a property reference that checks that it has a Lazy delegate that has already been initialized:
/**
* Returns true if a lazy property reference has been initialized, or if the property is not lazy.
*/
val KProperty0<*>.isLazyInitialized: Boolean
get() {
if (this !is Lazy<*>) return true
// Prevent IllegalAccessException from JVM access check on private properties.
val originalAccessLevel = isAccessible
isAccessible = true
val isLazyInitialized = (getDelegate() as Lazy<*>).isInitialized()
// Reset access level.
isAccessible = originalAccessLevel
return isLazyInitialized
}
Then at the use site:
val messageBroker: MessageBroker by lazy { MessageBroker() }
if (this::messageBroker.isLazyInitialized) {
// ... do stuff here
}
This solution requires kotlin-reflect to be on the classpath. With Gradle, use compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
The isAccessible = true part is required for the .getDelegate(), because otherwise it cannot access the private field storing the delegate reference.
Testing if the lazy property is easy enough:
import kotlin.reflect.KProperty0
import kotlin.reflect.jvm.isAccessible
val KProperty0<*>.isLazyInitialized: Boolean
get() {
// Prevent IllegalAccessException from JVM access check
isAccessible = true
return (getDelegate() as Lazy<*>).isInitialized()
}
…but you can make it even easier to reference a property without initializing it:
/**
* Returns the value of the given lazy property if initialized, null
* otherwise.
*/
val <T> KProperty0<T>.orNull: T?
get() = if (isLazyInitialized) get() else null
Now you can do things like:
private val myList by lazy {
mutableSetOf<String>()
}
fun add(str: String) {
// Create the list if necessary
myList += str
}
fun remove(str: String) {
// Don't create the list
::myList.orNull?.remove(str)
}
fun clear() {
// Don't create the list
::myList.orNull?.clear()
}