Kotlin: lateinit to val, or, alternatively, a var that can set once

Kotlin: lateinit to val, or, alternatively, a var that can set once



Just curious: In Kotlin, I would love to get some val that can be initialized by lazy, but with a parameter. That's because I need something that's created very late in order to initialize it.



Specifically, I wish I had:


private lateinit val controlObj:SomeView



or:


private val controlObj:SomeView by lazy view:View->view.findViewById(...)



and then:


override fun onCreateView(....) {
val view = inflate(....)


controlObj = view.findViewById(...)



or in the 2nd case controlObj.initWith(view) or something like that:


controlObj.initWith(view)


return view



I cannot use by lazy because by lazy won't accept external parameters to be used when initialising. In this example - the containing view.


by lazy


by lazy


view



Of course I have lateinit var but it would be nice if I could make sure it becomes read only after setting and I could do it in one line.


lateinit var



Is there a pretty clean way to create a read only variable that initializes only once but only when some other variables are born? Any init once keyword? That after init the compiler knows it's immutable?


init once



I am aware of the potential concurrency issues here but if I dare to access it before init, I surely deserve to be thrown.





I recommend reading about contracts in Kotlin. They allow you to do cool things
– Maneki Neko
yesterday





8 Answers
8



You can implement own delegate like this:


class InitOnceProperty<T> : ReadWriteProperty<Any, T>

private object EMPTY

private var value: Any? = EMPTY

override fun getValue(thisRef: Any, property: KProperty<*>): T
if (value == EMPTY)
throw IllegalStateException("Value isn't initialized")
else
return value as T



override fun setValue(thisRef: Any, property: KProperty<*>, value: T)
if (value != EMPTY)
throw IllegalStateException("Value is initialized")

this.value = value




After that you can use it as following:


inline fun <reified T> initOnce(): ReadWriteProperty<Any, T> = InitOnceProperty()

class Test

var property: String by initOnce()

fun readValueFailure()
val data = property //Value isn't initialized, exception is thrown


fun writeValueTwice()
property = "Test1"
property = "Test2" //Exception is thrown, value already initalized


fun readWriteCorrect()
property = "Test"
val data1 = property
val data2 = property //Exception isn't thrown, everything is correct





In case when you try to access value before it initialized you will get exception as well as when you try to reassign new value.





Here variable is var not val
– Rezo Shalikashvili
Jan 25 at 14:31





Yep, and as said in title of question "or, alternatively, a var that can set once"
– hluhovskyi
Jan 25 at 14:32





"Specifically, I wish I had private lateinit val controlObj:SomeView or private val controlObj:SomeView by lazy view:View->view.findViewById(...)"
– Rezo Shalikashvili
Jan 25 at 14:34






Yeah, looks like the "School solution". More or less, that's what I thought... No shortcut from that :-( Except, of course, for the easy case like view where you have getView evaluated on the lazy call.
– Maneki Neko
Jan 29 at 9:38



In this solution you implement a custom delegate and it becomes a separate property on your class. The delegate has a var inside, but the controlObj property has the guarantees you want.


var


controlObj


class X
private val initOnce = InitOnce<View>()
private val controlObj: View by initOnce

fun readWithoutInit()
println(controlObj)


fun readWithInit()
initOnce.initWith(createView())
println(controlObj)


fun doubleInit()
initOnce.initWith(createView())
initOnce.initWith(createView())
println(controlObj)



fun createView(): View = TODO()

class InitOnce<T : Any>

private var value: T? = null

fun initWith(value: T)
if (this.value != null)
throw IllegalStateException("Already initialized")

this.value = value


operator fun getValue(thisRef: Any?, property: KProperty<*>): T =
value ?: throw IllegalStateException("Not initialized")



BTW if you need thread safety, the solution is just slightly different:


class InitOnceThreadSafe<T : Any>

private val viewRef = AtomicReference<T>()

fun initWith(value: T)
if (!viewRef.compareAndSet(null, value))
throw IllegalStateException("Already initialized")



operator fun getValue(thisRef: Any?, property: KProperty<*>): T =
viewRef.get() ?: throw IllegalStateException("Not initialized")





One thing that can be improved - it is handling of null. Sometimes view can be absent and in such case the exception is thrown. In any other aspects it is the best solution.
– hluhovskyi
Jan 26 at 8:19


null





You think a different exception should be thrown instead of IllegalStateException? BTW i'm not sure i like this one better than your aproach with a guaranteed single-assign to var. This one has the burden of the exposed delegate.
– Marko Topolnik
Jan 26 at 8:26



IllegalStateException


var





I am saying that findViewById can return null in case when view is absent in view hierarchy and it is ok. But since you do view ?: throw IllegatStateException for determining if value initialized, null cannot be stored in your delegate even if it is valid value.
– hluhovskyi
Jan 26 at 8:40


findViewById


null


view ?: throw IllegatStateException


null





The whole API of InitOnce is based on the non-nullable type View so this presents a consistent story. Sure, if another use case asks for a nullable val, this could be adapted.
– Marko Topolnik
Jan 26 at 8:52



InitOnce


View


val



You can use lazy. For example with TextView


lazy


TextView


val text by lazy<TextView?>view?.findViewById(R.id.text_view)



where view is getView().
And after onCreateView() you can use text as read only variable


view


getView()


onCreateView()


text





This is not what question is about. In your case, value of variable is determined statically.
– Rezo Shalikashvili
Jan 25 at 14:20





Interesting, though... Solves some use cases. view is actually a method, getView(). It is evaluated by the time of invocation.
– Maneki Neko
Jan 27 at 8:44






And - when doing that - beware of silly mistake #1: onCreateView(...) myTextView.text = "Boo!" will not work because when done from onCreateView, view as in getView() may still be null :-( Just reminding...
– Maneki Neko
Jan 31 at 19:36



onCreateView(...) myTextView.text = "Boo!"



You can implement own delegate like this:


private val maps = WeakHashMap<Any, MutableMap<String, Any>>()

object LateVal
fun bindValue(any: Any, propertyName: String, value: Any)
var map = maps[any]
if (map == null)
map = mutableMapOf<String, Any>()
maps[any] = map


if (map[propertyName] != null)
throw RuntimeException("Value is initialized")


map[propertyName] = value


fun <T> lateValDelegate(): MyProperty<T>
return MyProperty<T>(maps)


class MyProperty<T>(private val maps: WeakHashMap<Any, MutableMap<String, Any>>) : ReadOnlyProperty<Any?, T>

override fun getValue(thisRef: Any?, property: KProperty<*>): T
val ret = maps[thisRef]?.get(property.name)
return (ret as? T) ?: throw RuntimeException("Value isn't initialized")




fun <T> lateValDelegate(): LateVal.MyProperty<T>
return LateVal.MyProperty<T>(maps)


fun Any.bindValue(propertyName: String, value: Any)
LateVal.bindValue(this, propertyName, value)



After that you can use it as following:


class Hat(val name: String = "casquette")
override fun toString(): String
return name



class Human
private val hat by lateValDelegate<Hat>()

fun setHat(h: Hat)
this.bindValue(::hat.name, h)


fun printHat()
println(hat)




fun main(args: Array<String>)
val human = Human()
human.setHat(Hat())
human.printHat()



In case when you try to access value before it initialized you will get exception as well as when you try to reassign new value.



also,you can write DSL to make it readable.


object to

infix fun Any.assigned(t: to) = this

infix fun Any.property(property: KProperty<*>) = Pair<Any, KProperty<*>>(this, property)

infix fun Pair<Any, KProperty<*>>.of(any: Any) = LateVal.bindValue(any, this.second.name, this.first)



and then call it like this:


fun setHat(h: Hat)
h assigned to property ::hat of this





Update: the times they are achangin' ... contracts are a good read. Still experimental but you can do cool things with contracts
– Maneki Neko
yesterday



If you really want a variable to be set only once you can use a singleton pattern:


companion object
@Volatile private var INSTANCE: SomeViewSingleton? = null

fun getInstance(context: Context): SomeViewSingleton =
INSTANCE ?: synchronized(this)
INSTANCE ?: buildSomeViewSingleton(context).also INSTANCE = it


private fun buildSomeViewSingleton(context: Context) =
SomeViewSingleton(context)



Then all you have to do is call getInstance(...) and you will always get the same object.


getInstance(...)



If you want to bind the lifetime of the object to the surrounding object just drop the companion object and put the initializer in your class.



Also the synchronized block takes care of the concurrency issues.





It's wrong. Variable is mutable, var instead of val. This is not what author wanted
– Rezo Shalikashvili
Jan 25 at 14:21





Indeed, the class object could modify the private variable. On the other hand this solution provides a way to have safe public and concurrent access from other classes. The only caveat is that you must be disciplined not to touch the private var, but rather use the getInstancemethod. I agree, that is not 100% what was asked, but close.
– leonardkraemer
Jan 25 at 20:05



getInstance



For Activity it is OK to do follow:


Activity


private val textView: TextView by lazy findViewById<TextView>(R.id.textView)



For Fragment it is not makes sense to create final variable with any View type, because you will lost connection with that view after onDestroyView.


Fragment


final


View


onDestroyView



P.S. Use Kotlin synthetic properties to access views in Activity and Fragment.


Activity


Fragment





Better not, because every findViewById will DFS from the root view. It costs more CPU. I would use the lowest view in the tree from which I can conveniently search views.
– Maneki Neko
Jan 27 at 8:50




You can implement own delegate like this:


class LateInitVal
private val map: MutableMap<String, Any> = mutableMapOf()

fun initValue(property: KProperty<*>, value: Any)
if (map.containsKey(property.name)) throw IllegalStateException("Value is initialized")

map[property.name] = value


fun <T> delegate(): ReadOnlyProperty<Any, T> = MyDelegate()

private inner class MyDelegate<T> : ReadOnlyProperty<Any, T>

override fun getValue(thisRef: Any, property: KProperty<*>): T
val any = map[property.name]
return any as? T ?: throw IllegalStateException("Value isn't initialized")






After that you can use it as following:


class LateInitValTest
@Test
fun testLateInit()
val myClass = MyClass()

myClass.init("hello", 100)

assertEquals("hello", myClass.text)
assertEquals(100, myClass.num)



class MyClass
private val lateInitVal = LateInitVal()
val text: String by lateInitVal.delegate<String>()
val num: Int by lateInitVal.delegate<Int>()

fun init(argStr: String, argNum: Int)
(::text) init argStr
(::num) init argNum


private infix fun KProperty<*>.init(value: Any)
lateInitVal.initValue(this, value)




In case when you try to access value before it initialized you will get exception as well as when you try to reassign new value.



I believe there is no such thing "init once". Variables are either final or not.
lateinit variables are not final. Because, well, you reassign it some other value after initialization phase.



If someone finds the solution, I'm starring this question





This is not an answer, but an opinion.
– leonardkraemer
Jan 25 at 14:13





Better than writing wrong answer
– Rezo Shalikashvili
Jan 25 at 14:17





I'm sorry if you understood me wrong, I did not want to offend you, rather I wanted to tell you why your answer is not helpful (It comes off as being based on belief, rather than knowledge (which I can only assume you have)). I understand that you had the best intentions. In my opinion your answer is not an 'answer', but should rather be a comment on the question.
– leonardkraemer
Jan 25 at 20:00



Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Some of your past answers have not been well-received, and you're in danger of being blocked from answering.



Please pay close attention to the following guidance:



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

PHP code is not being executed, instead code shows on the page

Ĝň ċũ,İĨĻĐđēĵſ ĤŹĝĽķũ ĆŹđě,ļ łĂģĚ ŻŷĶĸĦķĖĝŔ ūŬĿŜćīŴĽĺ,ľ,ũĽňĎźāœ,ĉŃĩ,ĎĻē,ŕųūČœijčĶŶĻijŕ,ĩěŗſŗāŸņŰž,Žů ĭğŪľ Čŗćļžĵţ Ūĉņ ĵ ůŁ ţŅ,ĮŶ ŁŵĮ śŁĬŇŏ ŮĜʼnœĦā,Ũŏ Ź ŖŀšĦ ŃłĉŹŅťŸŇ,ŷī