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
Related
I created extension properties and I'm having this problem
fun main(args: Array<String>) {
println(Animal("Mia",1,1.0))
}
class Animal(var name : String, var age : Int, var weight : Double)
var Animal.getXXX : String // compiler : Property must be initialized
get() = "$name, $age, $weight"
val Animal.getXXX : String // the compiler is running properly
get() = "$name, $age, $weight"
in the code above. why should i use val instead of var?
The error message is perhaps a bit confusing. For extension fields using var they are expected to have both a getter and a setter. Fields using val only need to have a getter (and can't have a setter). The following code works:
var Animal.getFoo : String
get() = "$name, $age, $weight"
set(value) { /* do something */ }
var is mutable and we can reassign or change its value. But we can't change val value.
The difference between var and val in extension properties is that while writing extension property if you use val you can only use get because you can not set value to it as it is immutable constant variable you can not use set() in val extension property
For Example
val String.extensionProperty
get() = "Value"
And if you want to make an extension property with var which you want to be mutable so you can set value into it as well and perform any other action while updating varaible.
For Example
var String.extensionProperty
get() = "Value"
set(value) {
println("variable has been updated with this data $value")
}
I am new to kotlin. I wonder if this is possible
I wish to create a function that will change the value of the properties of the object and return the object itself. The main benefit is that I can chain this setter.
class Person {
var name:String? = null
var age:Int? = null
fun setter(propName:String, value:Any): Person{
return this.apply {
try {
// the line below caused error
this[propName] = value
} catch(e:Exception){
println(e.printStackTrace())
}
}
}
}
//usage
var person = Person(null,null)
person
.setter(name, "Baby")
.setter(age, 20)
But I get error "unknown references"
This question is marked as duplicate, however the possible duplicate question specifically want to change the property of "name", but I wish to change anyProperty that is pass from the function to object. Can't seem to connect the dot between two questions. #Moira Kindly provide answer that explain it. thankyou
Why not just simplify your answer to
fun setter(propName: String, value: Any): Person {
val property = this::class.memberProperties.find { it.name == propName }
when (property) {
is KMutableProperty<*> ->
property.setter.call(this, value)
null ->
// no such property
else ->
// immutable property
}
}
Java reflection isn't needed, its only effect is to stop non-trivial properties from being supported.
Also, if you call it operator fun set instead of fun setter, the
this[propName] = value
syntax can be used to call it.
After googling around, I think I can provide an answer, but relying on java instead of kotlin purely. It will be great if someone can provide a better answer in kotlin.
class Person(
var name: String,
val age: Int
){
fun setter(propName: String, value: Any): Person{
var isFieldExistAndNotFinal = false
try{
val field = this.javaClass.getDeclaredField(propName)
val isFieldFinal = (field.getModifiers() and java.lang.reflect.Modifier.FINAL == java.lang.reflect.Modifier.FINAL)
if(!isFieldFinal) {
// not final
isFieldExistAndNotFinal = true
}
// final variable cannot be changed
else throw ( Exception("field '$propName' is constant, in ${this.toString()}"))
} catch (e: Exception) {
// object does not have property
println("$e in ${this.toString()}")
}
if(isFieldExistAndNotFinal){
val property = this::class.memberProperties.find { it.name == propName }
if (property is KMutableProperty<*>) {
property.setter.call(this, value)
}
}
return this;
}
}
usage like this
person
.setter(propName = "age", value = 30.00)
.setter(propName = "asdf", value = "asdf")
.setter(propName = "name", value = "A Vidy")
You have error because when you do this[propName] = value you are trying to use this as a list, but it is not a list, it is a Person and it doesn't overload the [] operator.
What you can do is to add a check for the property that is setted:
class Person {
privavar name:String? = null
var age:Int? = null
fun setter(propName:String, value:Any): Person{
return this.apply {
if (propName == "name" && value is String?) {
it.name = value as String?
} else if (propName == "age" && value is Int?) {
it.age = value as Int?
} else {
// handle unknown property or value has incorrect type
}
}
}
}
Another more dynamic solution without reflection:
class Person {
private var fields: Map<String, Any?> = HashMap()
fun setter(propName:String, value:Any): Person{
return this.apply {
it.fields[propName] = value;
}
}
fun getName() = fields["name"]
}
If you want to get rid of the getters as well then you need to use reflection.
num should be nullable when set, but what it returns should always be non-nullable (have a default value).
class Test {
var num: Int? = null
get() = field ?: 5 // default value if null
}
The following does not compile even though the returned value is always non-null which makes sense to me, because the type is not inferred but taken from the backing field:
val a: Int = Test().num
Type mismatch: inferred type is Int? but Int was expected
The question is how can I change the return type of that getter to be non-nullable? If I do so, the compiler says:
Getter return type must be equal to the type of the property, i.e.
'Int?'
I know that I could solve it with another property numNotNullable (without a backing field).
class Test {
var num: Int? = null
get() = field ?: 5 // default value if null
val numNotNullable: Int
get() = num ?: 5
}
val c: Int = Test().numNotNullable
But this is not what I want. Is there another way?
var num: Int? = null
This is your property signature. It doesn't matter, if you internally ensure that no null value is returned. The signature says, that the value is nullable.
This implicates:
You are allowed to set null to this field
All classes using this field, must handle the fact that the property can return null
Your Solution with a second property is good.
You of course can replace the property with plain old java bean, but I wouldn't advise that, because than you have to access the prop with getNumb and setNum.
class Test {
private var num: Int = 5
fun setNum(num: Int?) {
this.num = num ?: 5
}
fun getNum() = num
}
I don't believe this is possible in Kotlin. You can't override the type of the the property for get/set. So if your property is an Int? you're going to have to return an Int? and check if it is null when you use it.
There's technically a feature request for what you're looking for, but it's been years since it was made.
You can achive this using delegated properties
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class LazyVar<T : Any>(private var initializer: () -> T) : ReadWriteProperty<Any?, T> {
private var value: T? = null
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
if (value == null) {
value = initializer()
print(value)
}
return value as T
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
}
class Test {
var num: Int by LazyVar { 5 }
}
val a: Int = Test().num
Note, that this code is not thread-safe. Also with this code sample you can't set null value for you field (so no way back to default value).
I have a data class
data class MyModel(private val _data: MyData? = null)
And I want to ensure my data is only accessible when it is not null, else throw.
I use the below which is good.
fun getData(): MyData {
return checkNotNull(_data) { "data shouldn't be null" }
}
However, if I follow the guide as per Override getter for Kotlin data class, the below complaints I need to return MyData? instead of MyData
val data = _data
get(): MyData {
return checkNotNull(field) { "data shouldn't be null" }
}
Is it true that field can't be cast to the Non-null version of it when return?
If your goal is to declare a getter for a Any? property that returns a Any, it's not possible. You'll get the following error:
Getter return type must be equal to the type of the property
So attempting to do something like
val test : String?
get() : String = "hi"
Wouldn't work.
However, you could hide the nullable property and expose a non-nullable property which references the nullable value via casting:
private val test : String? = "hi"
val testNotNull : String = test as String
If test referenced null, an exception will be thrown.
For example:
fun main(args: Array<String>) = print(Demo().testNotNull)
class Demo(private var test: String? = "hi") {
val testNotNull : String
. get() = test as String
}
You can test this snippit out at try.kotlin.org
Although this is not safe. You should rethink your design. If you're not interoping with Java, you shouldn't punish yourself with nullable types.
I don’t think you can. What you did with the fun getData() is a valid approach IMO. Or you could just not use a data class and create a normal class, obviously.
What I think it may work is with something like this:
typealias notNullType = MyData
data class Test(private val _value: MyData? = null) {
val v: notNullType = _value as notNullType
get() { return field }
}
This would totally allow you to do:
fun play() {
val t = Test(null)
print(t.v) //see what I did? :-)
}
THAT BEING SAID… I don’t think “hiding” the ? optional is necessarily a good idea.
It doesn't necessarily mean that the MyData class is null if you cast it like MyData?
The '?' Just allows the object to be null in the instance that it actually becomes null to avoid an exception at runtime.
You can make your class nullable and it can still contain your data.
Given the following Kotlin class:
data class Test(val value: Int)
How would I override the Int getter so that it returns 0 if the value negative?
If this isn't possible, what are some techniques to achieve a suitable result?
After spending almost a full year of writing Kotlin daily I've found that attempting to override data classes like this is a bad practice. There are 3 valid approaches to this, and after I present them, I'll explain why the approach other answers have suggested is bad.
Have your business logic that creates the data class alter the value to be 0 or greater before calling the constructor with the bad value. This is probably the best approach for most cases.
Don't use a data class. Use a regular class and have your IDE generate the equals and hashCode methods for you (or don't, if you don't need them). Yes, you'll have to re-generate it if any of the properties are changed on the object, but you are left with total control of the object.
class Test(value: Int) {
val value: Int = value
get() = if (field < 0) 0 else field
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Test) return false
return true
}
override fun hashCode(): Int {
return javaClass.hashCode()
}
}
Create an additional safe property on the object that does what you want instead of having a private value that's effectively overriden.
data class Test(val value: Int) {
val safeValue: Int
get() = if (value < 0) 0 else value
}
A bad approach that other answers are suggesting:
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
The problem with this approach is that data classes aren't really meant for altering data like this. They are really just for holding data. Overriding the getter for a data class like this would mean that Test(0) and Test(-1) wouldn't equal one another and would have different hashCodes, but when you called .value, they would have the same result. This is inconsistent, and while it may work for you, other people on your team who see this is a data class, may accidentally misuse it without realizing how you've altered it / made it not work as expected (i.e. this approach wouldn't work correctly in a Map or a Set).
You could try something like this:
data class Test(private val _value: Int) {
val value = _value
get(): Int {
return if (field < 0) 0 else field
}
}
assert(1 == Test(1).value)
assert(0 == Test(0).value)
assert(0 == Test(-1).value)
assert(1 == Test(1)._value) // Fail because _value is private
assert(0 == Test(0)._value) // Fail because _value is private
assert(0 == Test(-1)._value) // Fail because _value is private
In a data class you must to mark the primary constructor's parameters with either val or var.
I'm assigning the value of _value to value in order to use the desired name for the property.
I defined a custom accessor for the property with the logic you described.
The answer depends on what capabilities you actually use that data provides. #EPadron mentioned a nifty trick (improved version):
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
That will works as expected, e.i it has one field, one getter, right equals, hashcode and component1. The catch is that toString and copy are weird:
println(Test(1)) // prints: Test(_value=1)
Test(1).copy(_value = 5) // <- weird naming
To fix the problem with toString you may redefine it by hands. I know of no way to fix the parameter naming but not to use data at all.
I have seen your answer, I agree that data classes are meant for holding data only, but sometimes we need to make somethings out of them.
Here is what i'm doing with my data class, I changed some properties from val to var, and overid them in the constructor.
like so:
data class Recording(
val id: Int = 0,
val createdAt: Date = Date(),
val path: String,
val deleted: Boolean = false,
var fileName: String = "",
val duration: Int = 0,
var format: String = " "
) {
init {
if (fileName.isEmpty())
fileName = path.substring(path.lastIndexOf('\\'))
if (format.isEmpty())
format = path.substring(path.lastIndexOf('.'))
}
fun asEntity(): rc {
return rc(id, createdAt, path, deleted, fileName, duration, format)
}
}
I know this is an old question but it seems nobody mentioned the possibility to make value private and writing custom getter like this:
data class Test(private val value: Int) {
fun getValue(): Int = if (value < 0) 0 else value
}
This should be perfectly valid as Kotlin will not generate default getter for private field.
But otherwise I definitely agree with spierce7 that data classes are for holding data and you should avoid hardcoding "business" logic there.
I found the following to be the best approach to achieve what you need without breaking equals and hashCode:
data class TestData(private var _value: Int) {
init {
_value = if (_value < 0) 0 else _value
}
val value: Int
get() = _value
}
// Test value
assert(1 == TestData(1).value)
assert(0 == TestData(-1).value)
assert(0 == TestData(0).value)
// Test copy()
assert(0 == TestData(-1).copy().value)
assert(0 == TestData(1).copy(-1).value)
assert(1 == TestData(-1).copy(1).value)
// Test toString()
assert("TestData(_value=1)" == TestData(1).toString())
assert("TestData(_value=0)" == TestData(-1).toString())
assert("TestData(_value=0)" == TestData(0).toString())
assert(TestData(0).toString() == TestData(-1).toString())
// Test equals
assert(TestData(0) == TestData(-1))
assert(TestData(0) == TestData(-1).copy())
assert(TestData(0) == TestData(1).copy(-1))
assert(TestData(1) == TestData(-1).copy(1))
// Test hashCode()
assert(TestData(0).hashCode() == TestData(-1).hashCode())
assert(TestData(1).hashCode() != TestData(-1).hashCode())
However,
First, note that _value is var, not val, but on the other hand, since it's private and data classes cannot be inherited from, it's fairly easy to make sure that it is not modified within the class.
Second, toString() produces a slightly different result than it would if _value was named value, but it's consistent and TestData(0).toString() == TestData(-1).toString().
Seems to be an old but interesting question.
Just want to contribute an option:
data class Test(#JvmField val value: Int){
fun getValue() = if(value<0) 0 else value
}
Now you can override getValue, and still have component1() working.
This seems to be one (among other) annoying drawbacks of Kotlin.
It seems that the only reasonable solution, which completely keeps backward compatibility of the class is to convert it into a regular class (not a "data" class), and implement by hand (with the aid of the IDE) the methods: hashCode(), equals(), toString(), copy() and componentN()
class Data3(i: Int)
{
var i: Int = i
override fun equals(other: Any?): Boolean
{
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Data3
if (i != other.i) return false
return true
}
override fun hashCode(): Int
{
return i
}
override fun toString(): String
{
return "Data3(i=$i)"
}
fun component1():Int = i
fun copy(i: Int = this.i): Data3
{
return Data3(i)
}
}
You can follow the Builder Pattern for this I think it'd be much better.
Here is an example:
data class Test(
// Fields:
val email: String,
val password: String
) {
// Builder(User):
class Builder(private val email: String) {
// Fields:
private lateinit var password: String
// Methods:
fun setPassword(password: String): Builder {
// Some operation like encrypting
this.password = password
// Returning:
return this
}
fun build(): Test = Test(email, password)
}
}