My expectation: I want to see something like that:
package com.example.myapplication
class ExampleGet {
val p2: String = "Black"
}
fun main(){
var ex = ExampleGet()
println(ex.p2)
}
I understand this example, it's work fine.
My problem
I don't know why do we need a word get in this class
package com.example.myapplication
class ExampleGet {
val p: String get() = "Black"
val p2: String = "Black"
}
fun main(){
var ex = ExampleGet()
println(ex.p)
println(ex.p2)
println(ex.p==ex.p2)
}
But I don't know what's difference between Line 1
val p: String get() = "Black"
and Line 2
val p2: String = "Black"
If we don't have any difference between Line 1 and Line 2 why do get() exist in kotlin? I ask because I have fond an example
package com.example.myapplication
import androidx.fragment.app.Fragment
import com.example.myapplication.databinding.FragmentThirdBinding
class ThirdFragment:Fragment() {
private var _binding : FragmentThirdBinding? = null
private val binding get() = _binding!!
}
I don't know why did people use
private val binding get() = _binding!!
but not
private val binding = _binding!!
CodePudding user response:
Properties in Kotlin can have an initializer, a getter, and a setter, but all of them are optional. When you write
val p2: String = "black"
the property p2
is initialized with value "black". It has an implicit getter that always returns the current value of the property, and it would have an implicit setter that sets that value, if it was a var
and not a val
.
When you write
val p: String get() = "black"
you defined an explicit getter for the property p
that always returns "black". So, in this example it does not become clear what the difference is, because "black" is a constant value.
Let's consider instead the following example:
val p1 : String = System.nanoTime()
val p2 : String get() = System.nanoTime()
When you use property p1
, it will always return the time in nanoseconds of the moment it was initialized.
However, when you use property p2
, it will always return the time in nanoseconds of the moment, you are calling p2
.
So, regarding your example with the property binding
, the definition with getter instead of an initializer, allows to always get the value of the internal variable _binding
instead of only its initial value. The variable _binding
is called a backing property.
CodePudding user response:
The difference you can see in decompiled Kotlin into Java code So the code:
class ExampleGet {
val p: String get() = "Black"
val p2: String = "Black"
}
Become (Java):
public final class ExampleGet {
@NotNull
private final String p2 = "Black";
@NotNull
public final String getP() {
return "Black";
}
@NotNull
public final String getP2() {
return this.p2;
}
}
As you can see, val with get() become method returning value.
In my practice, I use variable shadowing to make user's code operate with different type, for example:
val publicValue: List<String>
get() = _privateValue
private val _privateValue: MutableList<String>...
CodePudding user response:
Short answer: both lines define a property, but one has an initialiser while the other overrides the getter method.
In Kotlin, a property always has a getter method (and, if it's mutable, a setter method). When you refer to the property (e.g. myExampleGet.p
), you're actually calling the getter method. (This is unlike Java.) A property will usually (though not always) have a private field to store the value, as well (known as the ‘backing field’).
Let's take your two cases in reverse order. Your second case has an initialiser, which is the most common form:
val p2: String = "Black"
This defines a property called p2
, of type String
, and assigns it the initial value "Black"
. You don't specify a setter method, so you get the default one, which just returns the backing field.
Your first case provides a setter method, instead of an initialiser:
val p: String get() = "Black"
This says that p
is a property with type String
, and that its getter method always returns the hard-coded value "Black"
.
This property doesn't need a backing field, because it would never be used.
So, what's the difference? In your example, very little. The main one is that every instance of ExampleGet
has a field called p2
, all of which contain the same reference (to the hard-coded string); they do not have a field p
. So p
is more memory-efficient.
However, in other situations, the difference is much less subtle! For example, the setter might not return a constant value, e.g.:
class ExampleGet {
val p: String get() = ( counter).toString()
val p2: String = ( counter).toString()
private var counter = 0
}
Here p2
would always have the same value it was initialised with (probably "1"
), while p
would give a different value each time: "2"
, then "3"
, then "4"
, and so on. (In practice, such a getter might perform some calculation on another property, or get it from some other source, but I hope this illustrates the point.)
Another situation making the difference obvious would be if the properties were mutable, i.e. var
instead of val
:
class ExampleGet {
var p: String get() = "Black"
var p2: String = "Black"
}
Here p2
would behave as you expect, returning the value you set:
val eg = ExampleGet()
println(eg.p2) // prints "Black"
eg.p2 = "White"
println(eg.p2) // prints "White"
But p
would always return the same value:
eg.p = "White"
println(eg.p) // prints "Black"
(I think p
would have a backing field in this case, which would store whatever value you set — but you'd never be able to see that value, because the setter would always return the hard-coded value.)
So the two cases are doing very different things, even though the effect is practically the same in the code in the question.
CodePudding user response:
It's been covered, but specifically for this stuff in your example:
private var _binding : FragmentThirdBinding? = null
private val binding: FragmentThirdBinding get() = _binding!!
I've been explicit about the types here - _binding
is nullable, binding
is non-null. binding
is a getter that's casting the value of _binding
to another type (the non-null equivalent). When people access binding
, they "don't have to worry" about it being null, don't have to do any null handling etc.
Here's the thing - none of that makes any sense. It's only non-null because they've asserted that with the !!
operator (which should generally be seen as a red flag - it's circumventing the nullability checker, telling it it's wrong).
What they're probably doing is assigning binding
later (e.g. in onCreate
), but the variable needs to be initialised to something before that, so they make it nullable and set it to null as a placeholder. But that makes binding
nullable, and it needs to be null-checked every time, even if in reality, it will always have been assigned to something by then, and will never be null when something tries to use it.
So this solution creates another placeholder variable, called _binding
, which is nullable. But the code accesses binding
instead, which is non-null. It's all based on the idea that _binding
definitely won't be null when accessed, so the !!
will always be valid.
This situation is what lateinit
is for though:
lateinit var binding: FragmentThirdBinding
Same thing - a promise to assign it before it's read, no need for nullability. It's a var
instead of a val
but that's rarely going to matter, and not for something like this where you're only going to set it once anyway. To me it's more readable, uses the language features instead of working around them (!!
), etc.
I'm not sure where the "cast a nullable field" pattern came from, it looks a lot like the way you're recommended to expose a private MutableLiveData
as a public immutable LiveData
type instead, so I'm not sure if people are just adapting that. Maybe there's a benefit to it I don't know about!