I have a class like this:
open class User(var firstName: String, var lastName: String) {
var fullName = "$firstName $lastName"
get() = "Name: $field"
set(value){
if(value.startsWith("Jon")){
field = "Jon Doe"
}else{
field = value
}
}
}
Why fullNamedoes not change if I change the firstName like:
val person1 = User("Mark", "Zuck")
println(person1.fullName)
//Mark Zuck
person1.firstName = "Bill"
println(person1.firstName)
//Bill
println(person1.fullName)
//Mark Zuck
person1.lastName = "Gates"
println(person1.fullName)
//prints Mark Zuck
Is it because fullName are bounded by getters and setters that's why the only way to change it is by accessing fullname directly???
when we say
get() = "Name: $field"
it just return the value that have been initialize during the instantiation, to fix that I just change it to
get() = "$firstName $lastName"
so that it calculates again and return the updated value
full code:
var fullName = "$firstName $lastName"
get() = "$firstName $lastName"
set(value){
field = value
}
Have a read of this: Getters and setters - basically because you're referring to field in your getter (and the setter), there's a backing field storing a value. And your getter (which is a function) is reading from that backing field when it calculates a value to return.
The code you posted as an answer:
var fullName = "$firstName $lastName"
get() = "$firstName $lastName"
set(value){
field = value
}
is pretty much equivalent to this:
val fullName get() = "$firstName $lastName"
because your getter never references the backing field. Your setter really does nothing - it stores the value you pass in the backing field, but that field is never read. If you remove the reference to the backing field:
var fullName
get() = "$firstName $lastName"
set(value){ }
then no backing field is created, because your getter and setter both do their own thing without involving the field. So you don't need to initialise it with a value - there's no field to initialise! The getter was never using it anyway.
And since your setter does nothing, you may as well delete it and make your property a val since you could never (directly) change its value anyway - fullName = "Some Guy" had no visible effect, so making it a var is pretty misleading!
And that's how you end up with a simple getter that returns a value derived from other properties:
val fullName get() = "$firstName $lastName"
If you wanted, you could make that setter update firstName and lastName:
var fullName
get() = "$firstName $lastName"
set(value){
// you'd need to handle the possible errors here
val names = value.split(' ')
firstName = names[0]
lastName = names[1]
}
Again that's not using a backing field, the data is stored somewhere else (in firstName and lastName in this case) and the getter and setter are just functions that handle storing and retrieving the data in some form. This is just an example, you probably wouldn't want to do this here (updating firstName and lastName directly is way better and less work to handle) but this is just to show you could! Sometimes you do this kind of thing with a setter
Related
Is there a syntactic sugar in Kotlin to iterate on each field/property value of a data class?
Sample:
data class User(
var firstName: String = DEFAULT_VALUE_STRING,
var middleName: String = DEFAULT_VALUE_STRING,
var lastName: String = DEFAULT_VALUE_STRING
)
val user = User()
Then check if any of the property's value is empty, considering all of it is String data type with something like this
if (user.properties.any{ it.isBlank() }) {
// TODO ...
}
Probably the closest you'll get is checking all the values of all the generated componentX() functions (since they're only created for the constructor parameter properties, the "data" in a data class) but yeah that involves reflection.
If I were you, I'd create an interface with a properties property and make all your data classes implement that - something like this:
import kotlin.reflect.KProperty0
interface HasStringProperties {
val properties: List<KProperty0<String>>
}
data class User(
var firstName: String = "",
var middleName: String = "",
var lastName: String = ""
) : HasStringProperties {
override val properties = listOf(::firstName, ::middleName, ::lastName)
}
fun main() {
val user = User("Funny", "", "Name")
println(user.properties.any {it.get().isBlank()})
}
So no, it's not automatic - but specifying which properties you want to include is simple, and required if you're going to access it on a particular class, so there's an element of safety there.
Also, because you're explicitly specifying String properties, there's type safety included as well. Your example code is implicitly assuming all properties on your data classes will be Strings (or at least, they're a type with an isBlank() function) which isn't necessarily going to be true. You'd have to write type-checking into your reflection code - if you say "I don't need to, the classes will only have String parameters" then maybe that's true, until it isn't. And then the reflection code has to be written just because you want to add a single age field or whatever.
You don't actually have to use property references in Kotlin either, you could just grab the current values:
interface HasStringProperties {
val properties: List<String>
}
data class User(
var firstName: String = "",
var middleName: String = "",
var lastName: String = ""
) : HasStringProperties {
// getter function creating a new list of current values every time it's accessed
override val properties get() = listOf(firstName, middleName, lastName)
}
fun main() {
val user = User("Funny", "", "Name")
println(user.properties.any {it.isBlank()})
}
It depends whether you want to be able to reference the actual properties on the class itself, or delegate to a getter to fetch the current values.
And of course you could use generics if you want, list all the properties and use filterIsInstance<String> to pull all the strings. And you could put a function in the interface to handle a generic isEmpty check for different types. Put all the "check these properties aren't 'empty'" code in one place, so callers don't need to concern themselves with working that out and what it means for each property
Here's the code:
class Person(var firstName: String, var lastName: String) {
var fullName: String = firstName + lastName
fun fullName() = firstName + lastName
override fun toString(): String {
return fullName()
}
}
fun main(){
val test = Person("test", "fortest1")
test.lastName = "fortest2"
println(test.fullName)
}
The result will only be testfortest1.
It looks like you are working with a copy of test once test is created.
This is because fullName is not observing any changes to firstName or lastName. It is initialized when Person is created and stays the same unless explicitly modified.
One easy fix for this is to provide a custom getter like this:
val fullName get() = firstName + lastName
Now it will work as you expect because everytime you read fullName that expression will be evaluated and the result will be returned.
(Also, prefer using vals over vars in data class fields)
I'm trying to create a function that returns any data class object setting it's property values with its property names (if all strings) without changing it's default values
I have an example on how it is:
Imagine this data class:
data class StudentProfile(
var fullName: String = "",
var mobilePhone: String = "",
var birthDate: String = "",
var email: String = ""
)
I want to keep this empty default values, but I wanted a generic function that should work for any class and returns (in this case) this:
return StudentProfile(
mobilePhone = "mobilePhone",
fullName = "fullName",
email = "email",
birthDate = "birthDate"
)
Is it possible?
This does sound like an X-Y problem (I can't imagine how it would be useful), but I thought it'd be fun to solve anyway.
I'm unclear about whether you want to replace default values or not (since you say you don't but your example does), so this example lets you choose.
Explanation: Make sure all of the constructor's parameters are Strings or optional (have defaults). Otherwise, this is impossible because non-String parameter values could not be specified. Then filter the parameters list to include only the ones we are setting to their own name, and associate them to their names to create a Map<KParameter, String> that we can pass to constructor.callBy.
fun <T: Any> produceWithPropertiesByOwnName(type: KClass<T>, overrideDefaults: Boolean): T {
val constructor = type.primaryConstructor!!
val parameters = constructor.parameters
if (!parameters.all { param -> param.type.classifier == String::class || param.isOptional }){
error("Class $type primary constructor has required non-String parameters.")
}
val valuesByParameter = parameters.filter { it.type.classifier == String::class && (!it.isOptional || overrideDefaults) }
.associateWith(KParameter::name)
return constructor.callBy(valuesByParameter)
}
in the below code I am trying to create a getter method as a backing field.
so that, when the getLastNameLen property is invoked it should return the length of the lastNameset.
please refer to the code below and help me to fix the bug.
how to display output of the backing fields
code:
class Thomas (val nickname: String?, val age : Int?) {
//backing field 1
var lastName : String? = null
set(value) {
if (value?.length == 0) throw IllegalArgumentException("negative values are not allowed")
field = value
println("lastname backing field set: ${field} ")
}
val getLastNameLen
get() = {
this.lastName?.length
}
}
output
lastname backing field set: jr.stephan
lastName is jr.stephan
lastNameLen is () -> kotlin.Int?
This is because you are using the = operator which is setting the getter to be a lambda.
You have two options:
val getLastNameLen
get() {
return this.lastName?.length
}
OR
val getLastNameLen
get() = this.lastName?.length
basically use the brackets right after get() to make a getter function, or if you can do it in one line use an = right after the get() but don't include the {} otherwise it will treat it like its a lambda
Consider this Kotlin code:
var parent: T? = null
get() = if (isParent) this as T else field
set(value) { field = if (value == null) null else value.parent }
val isParent: Boolean
get() = parent == null
var description = ""
get() = if (isParent) field else parent!!.description
set(value) { if (isParent) field = value else parent!!.description = value }
Assume that isParent returns true if this instance is a parent instance. If not getParent() will return the parent instance. In Java you are allowed to access directly field of a different instance of same class like this:
String getDescription() { return getParent().description; }
void setDescription(String value) { getParent().description = value; }
(I am not saying that is a best thing to do, I simplified it for demostration). Comparing to Java, it would be nice to be able to do following:
var description = ""
get() = parent.field
set(value) { parent.field = value }
However this does not work and unfortunately it makes the code less readable. Especially if you have a lot of such variables, which are bound to this parent.
A backing field of a property can only be accessed from a getter or setter of that property, and only for the instance on which the getter or setter has been invoked. If you need to provide multiple ways to access an attribute of a class, you need to define two distinct properties, one of which has a backing field to store the data and another has a getter and setter referring to the first property.
class Foo {
var parent: Foo? = null
val parentOrSelf: Foo get() = parent ?: this
private var _description: String? = null
var description = ""
get() = parentOrSelf._description
set(value) { parentOrSelf._description = value }
}