I need to know if this line code is right, my teacher told me it's correct, but I disagree 'cause "lateinit" can't be with a variable that could be null or not.
Line code:
lateinit var text : String?
Code:
val cadena = null
lateinit var text : String?
text = null
text = cadena ?: "Hola"
text?.let { println(text) }
You are correct and your teacher is wrong. Proof: lateinit var text : String? results in compilation error with Kotlin 1.3.50:
'lateinit' modifier is not allowed on properties of nullable types
How any teacher can possibly claim such code is correct is beyond me...
I would like to add a little deep dive into lateinit properties in Kotlin.
'lateinit' modifier is not allowed on properties of nullable type - this can be found in Kotlin documentation. This kind of modifier is for special kind of constructions. It's for fields that will be initialized somewhen after object creation. For example, via DI framework or mocking framework.
But, what is under that field? If we would check it, we will simply find that before initialization property has null value. Nothing more, nothing less, just null. But, if we would like to access that property before initialization UninitializedPropertyAccessException is thrown.
In Kotlin 1.3 lateinit properties got new property - isInitialized (to use it: ::lateinitiProperty.isInitilized). So, before we access that property we are able to check if under that field is null or something else, without throwing exception.
But, lateinit means that object will be initialized later as not null property. And a programmer guarantee that this value is not null after intialization. If it could be, why just not use nullable type?
If there is a way to uninialize lateinit property? Yes, it is. Via reflection we can set that value to null again (JVM is not null-safe). And accessing that field will not finish with NPE execption, but with UninitializedPropertyAccessException. And .isInitialized will return false for field that refers to null.
And how does it work?
class MyClass {
lateinit var lateinitObject: Any
fun test() {
println("Is initialized: ${::lateinitObject.isInitialized}") // false
lateinitObject = Unit
println("Is initialized: ${::lateinitObject.isInitialized}") // true
resetField(this, "lateinitObject")
println("Is initialized: ${::lateinitObject.isInitialized}") // false again
lateinitObject // this will throw UninitializedPropertyAccessException
}
}
fun resetField(target: Any, fieldName: String) {
val field = target.javaClass.getDeclaredField(fieldName)
with (field) {
isAccessible = true
set(target, null)
}
}
Ofc, using lateinit that way is probably not what you want, and treat is as a curio about lateinit design in JVM.
And due to your teacher - he wasn't right. Even if lateinit may refer to null (and it does actually), you cannot declare it as a nullable type. If you need to, you don't need lateinit modifier.
Related
I have a lateinit var as
lateinit var someVariable: SomeVariable
I initialize this value like someVariable = SomeVariable() and use it whenever I need.
At a certain point, I want to set everything to default and want to "uninitialize" someVariable. How can I do that?
I am not looking for changing its type to a nullable object and set null. I need to keep it a Non-Null type.
I don't think it's possible without some kind of wrapper (or reflection, but about it in a moment).
In fact, lateinit is design for compatibility with DI frameworks etc. If you know the value can be uninitialized in any moment then you should use nullable type.
So, what about that reflection thing? lateinit is in fact a kind of smart wrapper that makes nullable value to act like not nullable, and instead of throwing NullPointerException throws UninitializedPropertyAccessException. lateinit property at the moment of declaration in JVM is null, so, let's make it null again ;)
So...
class MyClass {
lateinit var lateinitObject: Any
fun test() {
println("Is initialized: ${::lateinitObject.isInitialized}") // false
lateinitObject = Unit
println("Is initialized: ${::lateinitObject.isInitialized}") // true
resetField(this, "lateinitObject")
println("Is initialized: ${::lateinitObject.isInitialized}") // false
lateinitObject // this will throw UninitializedPropertyAccessException
}
}
fun resetField(target: Any, fieldName: String) {
val field = target.javaClass.getDeclaredField(fieldName)
with (field) {
isAccessible = true
set(target, null)
}
}
fun main() {
MyClass().test()
}
So, setting that field to null (and it's possible only via reflection) makes this filed uninitialized again. And one important thing - treat it as a curiosity, not like thing that should be in your production code.
In Kotlin, we have lateinit modifier to lazy variable initialization instead of var something: Something? = null.
In my situation, I have a list of the Element, and I want to assign it to the lateinit variable when I having the first object.
So, I tried several methods to achieve this.
First, using 'firstOrNull()' methods
lateinit var applicationHolder: ApplicationHolder
applicationHolder = env.getElementsAnnotatedWith(InjectApplication::class.java)
.map {
ApplicationHolder(it, (it as TypeElement).asClassName(), it.simpleName.toString()).apply {
val component = it.getAnnotation(InjectApplication::class.java).getComponent()
componentClass = component
}
}.firstOrNull()
The first solution was failed because applicationHolder doesn't accept the Nullable type of ApplicationHolder. (Type inference failed. Expected type mismatch: inferred type is ApplicationHolder? but ApplicationHolder was expected.)
Although I can use first instead of firstOrNull to achieve this, it's too dangerous because the list can be empty.
Second, using if-condition
val item = env.getElementsAnnotatedWith(InjectApplication::class.java)
.map {
ApplicationHolder(it, (it as TypeElement).asClassName(), it.simpleName.toString()).apply {
val component = it.getAnnotation(InjectApplication::class.java).getComponent()
componentClass = component
}
}.firstOrNull()
if (item != null) {
applicationHolder = item
}
The second solution was succeeded to compile and working well.
Third, using backing properties (Actually, this solution don't use lateinit modifier)
val applicationHolder: ApplicationHolder
get() {
return _applicationHolder ?: throw NullPointerException("Not initialized")
}
private var _applicationHolder: ApplicationHolder? = null
_applicationHolder = env.getElementsAnnotatedWith(InjectApplication::class.java)
.map {
ApplicationHolder(it, (it as TypeElement).asClassName(), it.simpleName.toString()).apply {
val component = it.getAnnotation(InjectApplication::class.java).getComponent()
componentClass = component
}
}.firstOrNull()
The third solution was succeeded to compile and working well.
In short, my question is as follows.
Is there a better solution to achieve my goal than these solutions?
If another solution doesn't exist, which is a clean or better solution? I can use the second or third solution but I have no confidence which is clean or better.
Why are you using lateinit over a nullable type here?
This strikes warning bells to me :
it's too dangerous because the list can be empty.
If you try to access a lateinit object without initialising it, your application will crash. Lateinit should be use if it will definitely be initialised.
I would change the code to what you were avoiding : var something: Something? = null
Then use the firstOrNull() method. The kotlin null safety type system will enforce you deal with nulls, leading to safe code!
It is possible to check whether a lateinit has been initialised, by calling this::applicationHolder.isInitialized. (This was introduced in Kotlin 1.2; info here.)
However, as per #user8159708's answer, it still smells a bit. Reworking to make the property nullable will make it slightly more awkward to access, but much safer.
Suppose I define a class with a nullable property
class ABC {
var myProperty: String? = null
}
Is there a way to default it to null value?
Maybe something similar to SCALA?
var myProperty: String? = _ // compilation error
or simply:
var myProperty: String? // compilation error
I know we could have used a lateinit variable that from Kotlin 1.2 can be later checked for initilization like so:
lateinit var myProperty: String
if (::myProperty.isInitialized) {
//value is not-null
}
So is lateinit the preferred way? Is defaulting to null value possible or it's omitted on purpose?
Kotlin intentionally requires you to initialize all properties explicitly. There is no shorthand syntax. For a property of a nullable type, the preferred way is not to use lateinit, but to declare it with a null initializer.
The isInitialized method for lateinit properties is designed to handle complex cases like cleanup of resources; it's not intended to be used as a replacement for a null check for users who want to save on writing = null as part of a property declaration.
Short answer is no.
Unlike some other languages (Scala, Go), Kotlin doesn't assume it knows best what the default value for the type is (who said that it should be 0 for int anyway?)
You could also declare a global function
fun nullString(): String? {
return null
}
and use it like
class ABC {
var myProperty = nullString()
}
Since there is only one null reference in the language, a global variable will also work
var nullString : String? = null
class ABC {
var myProperty = nullString
}
P.S. -
unfortunately the spacial characters like '_' are reserved in Kotlin so you can't use it to name your variable and create var myProperty = _
In Kotlin, we have val that is final and can't be change. e.g.
val something = "Something"
If a value that is is initialized later, we use lateinit var.
lateinit var something: String
But this is var instead of val. I wanted to set something once (not in constructor), and have it as final. How could I achieve this?
Reading into the conventions of Kotlin, a late-initialized variable which is final is impossible.
Consider its use case:
Normally, properties declared as having a non-null type must be initialized in the constructor. However, fairly often this is not convenient. For example, properties can be initialized through dependency injection, or in the setup method of a unit test. In this case, you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class.
lateinit var is providing relative sanity when dealing with a variable that may not have yet been initialized, such as the case with injected fields (like Spring and #Autowired). Then, speaking strictly in the context of dependency injection, if you don't have a way to concretely instantiate the variable at compile time, then you cannot leave it as a final field.
From a Java to Kotlin world, having a late initialized variable come in as final would look as paradoxical as this from Spring:
#Autowired
private final Interface something;
What do you think the behavior should be when you attempt to set it again? Do you expect this to be enforced at compile time? Should it cause a crash at runtime or just do nothing?
If you expect it to happen at compile time, I'm pretty sure it's not possible for a compiler to catch something like that.
If you want some other behavior, you can make it a private variable with a public set method that does whatever you want if it's been already set.
Or you could encapsulate it in an instance of a custom class that does whatever behavior you want.
You can use following delegate class:
import kotlin.reflect.KProperty
class WriteOnce<T> {
private var holder = holdValue<T>()
private var value by holder
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
if (!holder.hasValue) {
throw IllegalStateException("Property must be initialized before use")
}
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
if (holder.hasValue) {
throw RuntimeException("Write-once property already has a value")
}
this.value = value
}
}
fun <T> holdValue() = ValueHolder<T>()
class ValueHolder<T> {
var value: T? = null
var hasValue: Boolean = false
private set
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
hasValue = true
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return this.value!!
}
}
Usage:
var example by WriteOnce<String>()
If you try to write a to the variable a second time it will produce a RuntimeException:
java.lang.RuntimeException: Write-once property already has a value
Not having any value also produces an exception, similar to as if you were using lateinit:
java.lang.IllegalStateException: Property must be initialized before use
Which means this is val and lateinit combined because you can set the value at any time, only once ever.
The downside to this implementation is that this is not checked at compile time, meaning that you will only ever see errors in runtime. If that's acceptable in your use case, it certainly would be a good solution for what you're looking for.
For me this is more of a way to make sure that a variable is only every assigned once by code I control – something I can catch during testing as well as in production as a way to improve security by preventing foreign code from changing a variable.
It is possible you can use You can create a custom delegate for the property that is a combination of the existing notNull delegate and your own idea of set once. Read more about property delegates for information on how to create a custom one that can do whatever you want, including the use case you want here. You would then not use lateinit but instead this delegate.
Just ver confused about casting and how to set up class variables. In java it was possible to do
private var mSectionsStatePageAdapter : SectionsStatePagerAdapter? = null
private val mViewPager : ViewPager? = null
now we're in kotlin
class MainActivity : AppCompatActivity() {
private var mSectionsStatePageAdapter : SectionsStatePagerAdapter? = null
private val mViewPager : ViewPager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mytoolbar:Toolbar = findViewById(R.id.top_toolbar)
setSupportActionBar(mytoolbar)
mSectionsStatePageAdapter = SectionsStatePagerAdapter(getSupportFragmentManager())
mViewPager = findViewById(R.id.viewpager1)
setupViewPager(mViewPager)
}
fun setupViewPager(viewPager :ViewPager):Unit {
var adapter : SectionsStatePagerAdapter = SectionsStatePagerAdapter(getSupportFragmentManager())
adapter.addFragment(Fragment1(),"Fragment1")
viewPager.setAdapter(adapter)
}
I'm getting val can't be reassigned...
Error:(65, 24) Smart cast to 'ViewPager!' is impossible, because 'mViewPager' is a mutable property that could have been changed by this time
For Android, it is common to use lateinit var because you create the object outside of constructor (in onCreate, etc). You can go with two route:
lateinit var variable:Type
var variable:Type?
I would recommend if your variable should be available when you are ready to use it. You do not need to do null check. lateinit mean late initialization. Kotlin use null to represent not yet initialized, so you cannot use nullable type and assign null on it.
If your variable is nullable, then you should go the second way.
Beside, if you are handling views, you should use Android extension to do it. You don't need to findViewById in Activity or Fragment yourself.
There are two things happening here.
First, as #gyosida points out, you have defined your mPager property as val instead of var so it cannot be reassigned in the line mViewPager = findViewById(R.id.viewpager1). As #Joshua points out, you either have to make it a var or you need to make it a lateinit val to solve the problem of it not being initialised with the class instance.
The second is represented by the actual error you describe of, 'mutable property that could have been changed', and you will continue to see this if you make it a var. The approach of using lateinit is most likely the better idea.
However, to explain this error, in your method declaration of:
fun setupViewPager(viewPager: ViewPager): Unit {
you have said that the argument for viewPager cannot be null. If it were, it would be viewPager: ViewPager?. So, if you pass something that could be null into it, you will get a compile error.
What Kotlin is telling you is that in between the lines:
mViewPager = findViewById(R.id.viewpager1)
and
setupViewPager(mViewPager)
something - imagine another method on another thread - could potentially have changed the value of mViewPager from that assigned instance to null. Therefore it's not safe to pass it in.
The only way to solve this without changing the method is supply a value that is guaranteed to be non-null. There are a few ways you could do that:
assign your value to a method-level variable that can't be interfered with, and supply that as the argument
only call your function if the value is non-null, e.g. mViewPager?.let{ pager -> setupViewPager(pager)}
assert that mViewPager will not be null, leaving any violations to fail at runtime, e.g. setupViewPager(mViewPager!!)