Kotlin:lateinit 到 val,或者,一个可以设置一次的 var
Kotlin: lateinit to val, or, alternatively, a var that can set once
只是好奇:在 Kotlin 中,我很想获得一些可以通过惰性初始化但带有参数的 val。那是因为我需要一些很晚才创建的东西来初始化它。
具体来说,我希望我有:
private lateinit val controlObj:SomeView
或:
private val controlObj:SomeView by lazy { view:View->view.findViewById(...)}
然后:
override fun onCreateView(....) {
val view = inflate(....)
controlObj = view.findViewById(...)
或第二种情况 controlObj.initWith(view)
或类似的情况:
return view
我不能使用 by lazy
因为 by lazy
不接受在初始化时使用的外部参数。在此示例中 - 包含 view
.
我当然有 lateinit var
但如果我能确保它在设置后变为只读并且我可以在一行中完成,那就太好了。
有没有一种非常干净的方法来创建只初始化一次但仅在其他一些变量诞生时才初始化的只读变量?任何 init once
关键字?在 init 之后编译器知道它是不可变的?
我知道这里潜在的并发问题,但如果我敢在 init 之前访问它,我肯定活该被扔掉。
我相信没有这样的事情"init once"。变量要么是最终的,要么不是。
lateinit 变量不是最终的。因为,好吧,你在初始化阶段后重新分配了一些其他值。
如果有人找到解决方案,我将在这个问题上加注星标
您可以使用 lazy
。例如 TextView
val text by lazy<TextView?>{view?.findViewById(R.id.text_view)}
其中 view
是 getView()
。
在 onCreateView()
之后你可以使用 text
作为只读变量
如果你真的希望一个变量只设置一次你可以使用单例模式:
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)
}
然后您只需调用 getInstance(...)
,您将始终获得相同的对象。
如果你想将对象的生命周期绑定到周围的对象,只需删除伴随对象并将初始化程序放在你的 class 中。
同步块还负责处理并发问题。
您可以像这样实现自己的委托:
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 (this.value != EMPTY) {
throw IllegalStateException("Value is initialized")
}
this.value = value
}
}
之后您可以按如下方式使用它:
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
}
}
如果您在初始化之前尝试访问值,您将遇到异常以及尝试重新分配新值时。
对于Activity
,可以按照以下步骤操作:
private val textView: TextView by lazy { findViewById<TextView>(R.id.textView) }
对于 Fragment
,使用任何 View
类型创建 final
变量是没有意义的,因为在 onDestroyView
之后您将失去与该视图的连接。
P.S。使用 Kotlin synthetic properties 访问 Activity
和 Fragment
.
中的视图
在此解决方案中,您实现了一个自定义委托,它在您的 class 上成为一个单独的 属性。代表内部有一个 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")
}
顺便说一句,如果你需要线程安全,解决方案略有不同:
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")
}
您可以像这样实现自己的委托:
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")
}
}
}
之后您可以按如下方式使用它:
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)
}
}
如果您在初始化之前尝试访问值,您将遇到异常以及尝试重新分配新值。
您可以像这样实现自己的委托:
private val maps = WeakHashMap<Any, MutableMap<String, Any>>()
object LateVal {
fun bindValue(any: Any, propertyName: String, value: Any) {
val map = maps.getOrPut(any) { mutableMapOf<String, Any>() }
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)
}
之后您可以按如下方式使用它:
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()
}
如果您在初始化之前尝试访问值,您将遇到异常以及尝试重新分配新值时。
此外,您可以编写 DSL 以使其可读。
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)
然后这样称呼它:
fun setHat(h: Hat) {
h assigned to property ::hat of this
}
安全委派,同步且有用的消息
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
interface InitOnce<T> : ReadWriteProperty<Any?, T> {
val isInitialized: Boolean
val value: T
}
class SynchronizedInitOnceImpl<T> : InitOnce<T> {
object UNINITIALIZED_VALUE
private var name: String? = null
@Volatile
private var _value: Any? = UNINITIALIZED_VALUE
override val value: T
@Suppress("UNCHECKED_CAST")
get() {
val _v = synchronized(this) { _value }
if (_v !== UNINITIALIZED_VALUE) return _v as T
else error("'$name' not initialized yet")
}
override val isInitialized: Boolean
get() = _value !== UNINITIALIZED_VALUE
override operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
if(name == null) name = property.name
return value
}
override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
synchronized(this) {
val _v = _value
if (_v !== UNINITIALIZED_VALUE) error("'${property.name}' already initialized")
else _value = value
}
}
}
fun <T> initOnce(): InitOnce<T> = SynchronizedInitOnceImpl()
用法
var hello: String by initOnce()
只需使用初始容量为 1 的 HashMap
(ConcurrentHashMap,如果线程是一个问题)和 computeIfAbsent
(它只会初始化给定键的值一次)
val fooByBar = HashMap<Bar, Foo>(1)
fun getFoo(bar: Bar) = fooByBar.computeIfAbsent(bar) {
fooFactory.create(bar)
}
try {
bazes.forEach { baz ->
val foo = getFoo(baz.bar) // assuming bar is same for all bazes, if not then it's still fine
foo.quux()
}
} catch (ex: Exception) {
logger.error(ex.message, ex)
return@whatever
} finally {
fooByBar.forEach { (_, foo) ->
foo.close() // if Foo : AutoCloseable
}
}
只是好奇:在 Kotlin 中,我很想获得一些可以通过惰性初始化但带有参数的 val。那是因为我需要一些很晚才创建的东西来初始化它。
具体来说,我希望我有:
private lateinit val controlObj:SomeView
或:
private val controlObj:SomeView by lazy { view:View->view.findViewById(...)}
然后:
override fun onCreateView(....) {
val view = inflate(....)
controlObj = view.findViewById(...)
或第二种情况 controlObj.initWith(view)
或类似的情况:
return view
我不能使用 by lazy
因为 by lazy
不接受在初始化时使用的外部参数。在此示例中 - 包含 view
.
我当然有 lateinit var
但如果我能确保它在设置后变为只读并且我可以在一行中完成,那就太好了。
有没有一种非常干净的方法来创建只初始化一次但仅在其他一些变量诞生时才初始化的只读变量?任何 init once
关键字?在 init 之后编译器知道它是不可变的?
我知道这里潜在的并发问题,但如果我敢在 init 之前访问它,我肯定活该被扔掉。
我相信没有这样的事情"init once"。变量要么是最终的,要么不是。 lateinit 变量不是最终的。因为,好吧,你在初始化阶段后重新分配了一些其他值。
如果有人找到解决方案,我将在这个问题上加注星标
您可以使用 lazy
。例如 TextView
val text by lazy<TextView?>{view?.findViewById(R.id.text_view)}
其中 view
是 getView()
。
在 onCreateView()
之后你可以使用 text
作为只读变量
如果你真的希望一个变量只设置一次你可以使用单例模式:
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)
}
然后您只需调用 getInstance(...)
,您将始终获得相同的对象。
如果你想将对象的生命周期绑定到周围的对象,只需删除伴随对象并将初始化程序放在你的 class 中。
同步块还负责处理并发问题。
您可以像这样实现自己的委托:
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 (this.value != EMPTY) {
throw IllegalStateException("Value is initialized")
}
this.value = value
}
}
之后您可以按如下方式使用它:
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
}
}
如果您在初始化之前尝试访问值,您将遇到异常以及尝试重新分配新值时。
对于Activity
,可以按照以下步骤操作:
private val textView: TextView by lazy { findViewById<TextView>(R.id.textView) }
对于 Fragment
,使用任何 View
类型创建 final
变量是没有意义的,因为在 onDestroyView
之后您将失去与该视图的连接。
P.S。使用 Kotlin synthetic properties 访问 Activity
和 Fragment
.
在此解决方案中,您实现了一个自定义委托,它在您的 class 上成为一个单独的 属性。代表内部有一个 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")
}
顺便说一句,如果你需要线程安全,解决方案略有不同:
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")
}
您可以像这样实现自己的委托:
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")
}
}
}
之后您可以按如下方式使用它:
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)
}
}
如果您在初始化之前尝试访问值,您将遇到异常以及尝试重新分配新值。
您可以像这样实现自己的委托:
private val maps = WeakHashMap<Any, MutableMap<String, Any>>()
object LateVal {
fun bindValue(any: Any, propertyName: String, value: Any) {
val map = maps.getOrPut(any) { mutableMapOf<String, Any>() }
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)
}
之后您可以按如下方式使用它:
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()
}
如果您在初始化之前尝试访问值,您将遇到异常以及尝试重新分配新值时。
此外,您可以编写 DSL 以使其可读。
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)
然后这样称呼它:
fun setHat(h: Hat) {
h assigned to property ::hat of this
}
安全委派,同步且有用的消息
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
interface InitOnce<T> : ReadWriteProperty<Any?, T> {
val isInitialized: Boolean
val value: T
}
class SynchronizedInitOnceImpl<T> : InitOnce<T> {
object UNINITIALIZED_VALUE
private var name: String? = null
@Volatile
private var _value: Any? = UNINITIALIZED_VALUE
override val value: T
@Suppress("UNCHECKED_CAST")
get() {
val _v = synchronized(this) { _value }
if (_v !== UNINITIALIZED_VALUE) return _v as T
else error("'$name' not initialized yet")
}
override val isInitialized: Boolean
get() = _value !== UNINITIALIZED_VALUE
override operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
if(name == null) name = property.name
return value
}
override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
synchronized(this) {
val _v = _value
if (_v !== UNINITIALIZED_VALUE) error("'${property.name}' already initialized")
else _value = value
}
}
}
fun <T> initOnce(): InitOnce<T> = SynchronizedInitOnceImpl()
用法
var hello: String by initOnce()
只需使用初始容量为 1 的 HashMap
(ConcurrentHashMap,如果线程是一个问题)和 computeIfAbsent
(它只会初始化给定键的值一次)
val fooByBar = HashMap<Bar, Foo>(1)
fun getFoo(bar: Bar) = fooByBar.computeIfAbsent(bar) {
fooFactory.create(bar)
}
try {
bazes.forEach { baz ->
val foo = getFoo(baz.bar) // assuming bar is same for all bazes, if not then it's still fine
foo.quux()
}
} catch (ex: Exception) {
logger.error(ex.message, ex)
return@whatever
} finally {
fooByBar.forEach { (_, foo) ->
foo.close() // if Foo : AutoCloseable
}
}