Is this initialization of overriden val still OK? ...
# codereview
a
Is this initialization of overriden val still OK?
Copy code
interface Foo {
    val xyz: String
}

class Bar : Foo {
    
    override val xyz: String
    
    init {
        xyz = compXyz("XYZ")
    }
    
    fun compXyz(key: String) = key.reversed()
}

fun main() {
    val s = Bar()
    println(s.xyz)
}
I'm asking because I seem to get this warning in Android Studio (I use Kotlin 1.9.24 in the project):
Property must be initialized, be final, or be abstract. This warning will become an error in future releases.
and not sure how to interpret this issue: https://youtrack.jetbrains.com/issue/KT-57553/Implement-deprecation-for-open-val-with-backing-field-and-deferred-initialization-in-K1 i.e. whether this is going to be deprecated soon or not I don't see any issues running it in Kotlin Playground though (tried both 1.9 and 2.0) 🤔
j
why not initialize on the spot, though?
Copy code
class Bar : Foo {
    
    override val xyz: String = compXyz("XYZ")
    
    fun compXyz(key: String) = key.reversed()
}
a
Because the actual situation in my project is more complicated than the example above 🙂
j
Then it would be interesting to share it, or at least a minimal example that demonstrates the need for deferred initialization
I think the real underlying issue should only occur if
Bar
was
open
, but it's not in your case. So if you still have the warning with this, it must be a false positive. Did you reproduce the warning in Android Studio with the example you gave here?
If
Bar
were
open
, then you would actually be exposed to the issue mentioned in the ticket, and you could prevent it by making
xyz
final
in
Bar
(to prevent custom setters).
a
I guess it was more about code style than anything else e.g. instead of
Copy code
interface BuildVariables {
    val foo: String
}

abstract class BuildVariablesService : BuildService<BuildVariablesService.Params>, BuildVariables {

    internal interface Params : BuildServiceParameters {
        val propertiesFile: Property<File>
    }

    private val properties: Properties = Properties().apply {
        val file = parameters.propertiesFile.get()
        file.bufferedReader().use { load(it) } // reading a file here
    }

    override val foo: String = getEnvOrProperty("FOO")

    private fun getEnvOrProperty(key: String, default: String? = null): String =
        getEnv(key) ?: properties.getProperty(key, default)
}
I was considering this:
Copy code
abstract class BuildVariablesService : BuildService<BuildVariablesService.Params>, BuildVariables {

    internal interface Params : BuildServiceParameters {
        val propertiesFile: Property<File>
    }

    private val properties: Properties = Properties()

    override val foo: String

    init {
        val file = parameters.propertiesFile.get()
        file.bufferedReader().use { properties.load(it) } // reading file here
        
        foo = getEnvOrProperty("FOO")
    }

    private fun getEnvOrProperty(key: String, default: String? = null): String =
        getEnv(key) ?: properties.getProperty(key, default)
}
I think I will just go with the first version and move on. It wasn't a big deal in this case. I was curious if that use is actually going to be deprecated in the upcoming releases.
j
It is actually an error in
2.0
in the playground: https://pl.kotl.in/AEYyF3DJM
a
image.png
a
🤦‍♂️
you're correct, I forgot about that
abstract
in my actual use case
👍 1
j
The crux of the problem with deferred initialization is that it's ambiguous whether the backing field should be set directly or via the generated setter. It behaves exactly the same in general, but if the class and the property are open, subclasses could override the setter, and the ambiguity matters
a
thanks, that clarifies a lot
c
Note that initialization runs in declaration order, so you could write:
Copy code
private val properties: Properties = Properties()

    init {
        val file = parameters.propertiesFile.get()
        file.bufferedReader().use { properties.load(it) } // reading file here
    }

    override val foo: String = getEnvOrProperty("FOO")
you're guaranteed that the init block will run before
foo
's initialization, so you should be good here
👍 1