Why the setter function is not running in this Kotlin program - kotlin

The following is the kotlin program. I have confusion that why the setter function is not being called on running line x.b=20.
class Num(value: Int) {
var a = value + 4
var b = value + 6
var c = value + 1
set(value) {
println("field is : ${field}")
field = value + b
println("field is : ${field}")
println("inside setter am here")
}
}
fun main(args: Array<String>) {
var x = Num(3)
x.b = 20
println(x.b)
}

It would be much clearer if your indentation were correct:
class Num(value: Int) {
var a = value + 4
var b = value + 6
var c = value + 1
set(value) {
println("field is : ${field}")
field = value + b
println("field is : ${field}")
println("inside setter am here")
}
}
You have only defined a setter on c. There is no way to make one setter for all the values, though factoring out a method might help.

Louis explained why it's not working, but this statement:
There is no way to make one setter for all the values
is not correct. It is possible by using delegated properties:
import kotlin.reflect.KProperty
class Num(value: Int) {
var a by MySetter(value + 4)
var b by MySetter(value + 6)
var c by MySetter(value + 1)
}
class MySetter(private var field) {
operator fun getValue(thisRef: Num, property: KProperty<*>) = field
operator fun setValue(thisRef: Num, property: KProperty<*>, value: Int) {
println("field is : $field")
field += value
println("field is : $field")
println("inside setter am here")
}
}
fun main(args: Array<String>) {
var x = Num(3)
x.b = 20
println(x.b)
}
This prints:
field is : 9
field is : 29
inside setter am here
29

Related

Re-initialize object properties in Kotlin

I often have a class with properties that are initialized at instantiation, like
class C {
var x = 10
var y = v * 2 // v is some variable
}
val c = C()
then properties of c are changed, and later I need to re-initialize the properties (so that c.x is 10 again and c.y is v*2, where the value of v may have changed).
My current approach is to initialize the properties with dummy values (or alternatively use lateinit and type annotations), and assign the desired values in an extra function ini like
class C {
var x = 0
var y = 0
init {
ini()
}
fun ini() {
x = 10
y = v * 2
}
}
then I call c.ini() to re-initialize.
Is there a better (more succinct) way that avoids the dummy values?
Note that in JavaScript I can simply write
class C {
constructor() {
this.ini()
}
ini() {
this.x = 10
this.y = v * 2
}
}
What you are doing right now is totally fine. I wouldn't change it.
If you want to avoid the placeholder values of 0, or if the type of the property has no placeholder value that make sense, you can store the initial values in private lambdas:
class C {
private val xInit = { 10 }
private val yInit = { v * 2 }
var x = xInit()
var y = yInit()
fun reset() {
x = xInit()
y = yInit()
}
}
If you really want to reduce the boilerplate, I can only think of this rather hacky and slow solution that relies on reflection. This could be usable if the instances don't need to be reset that often, and you have lots of these classes with resettable properties, and you just hate writing boilerplate reset methods.
class Resettable<T>(val supplier: () -> T) {
var wrapped = supplier()
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return wrapped
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
wrapped = value
}
fun reset() {
wrapped = supplier()
}
}
fun <T: Any> T.reset() {
// look for properties delegated with Resettable
this::class.memberProperties.mapNotNull {
it.isAccessible = true
(it as KProperty1<T, *>).getDelegate(this)
}.filterIsInstance<Resettable<*>>().forEach {
it.reset()
}
}
Now you just need to do:
var v = 10
class C {
var x by Resettable { 10 }
var y by Resettable { v * 2 }
}
fun main() {
val x = C()
println(x.y) // 20
v = 20
x.reset()
println(x.y) // 40
v = 40
x.reset()
println(x.y) // 80
}
Note that the first reset call would load all the kotlin reflection classes, and would take a noticeable amount of time.

Different Property setter for three same class objects in Kotlin

What I am trying to implement are three different temperature values depending on the city name.
The following class:
class City(val name: String) {
var degrees: Int = 0
set(value) {
when(this.name){
("Dubai") -> 30
"Moscow" -> 5
"Hanoi" -> 20
}
field = value
}}
And main func:
fun main() {
val firstCity = City("Dubai")
val secondCity = City("Moscow")
val thirdCity = City("Hanoi")
println(firstCity.degrees) // 0
}
Why is it set to default value 0? For Dubai it should have been 30.
The degrees are initialized with 0 and never changed due to no invocation of the setter, which lacks a value for cities that are not expected (maybe that's why you initialized the degrees?).
You could do what you want way shorter:
class City(val name: String) {
var degrees: Int = when(name) {
"Dubai" -> 30
"Moscow" -> 5
"Hanoi" -> 20
else -> 0 // default value for unpredictable cities
}
}
fun main() {
val firstCity = City("Dubai")
val secondCity = City("Moscow")
val thirdCity = City("Hanoi")
println(firstCity.degrees)
}
This will output 30

How to print multiple attrributes from a hashMap that is a property inside a toString override

I am learning Kotlin and writing code to check my understanding. I'm trying to use a toString override to print the values of a hashMap that is a property of a class. I can't get it to work. Instead I get output like "kotlin.Unit() -> kotlin.Unit". Also, I don't understand why the values of the hashMap ARE printing out before the toString output. I don't know where that output is coming from. Please help me. Thanks. Below is my code and the output I'm getting.
Code:
package ch07.ExpandoObject
import java.beans.PropertyChangeListener
import java.beans.PropertyChangeSupport
import kotlin.properties.Delegates
import kotlin.reflect.KProperty
class Person(
val name: String = "",
age: Int? = null,
var isMarried: Boolean? = null ,_attributes: kotlin.collections.HashMap<String,String>? = hashMapOf<String, String>()
)
:PropertyChangeAware()
{
var _attributes : kotlin.collections.HashMap<String,String>? = hashMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
_attributes!!.set(attrName, value)
_attributes!!.set("name", this.name)
}
override fun toString() = "Person(name=\"${name?:""}\", age=${age?:99999}, isMarried=$isMarried) " +
"${_attributes?.get("name")} " + "$name " +
this._attributes!!.forEach { (attrName, value) -> println("$attrName = $value") } +
{
for ((attrName, value) in this._attributes!!) {
println("attribute $attrName = ${this._attributes!![attrName]}")
}
}
val _age = ObservableProperty(propName = "age", propValue = age, changeSupport = changeSupport)
private val observer = {
prop: KProperty<*>, oldValue: Int, newValue: Int ->
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
var age: Int by Delegates.observable(initialValue = age?:99999,onChange = observer)
}
class ObservableProperty(val propName: String,
var propValue: Int?, val changeSupport: PropertyChangeSupport
) {
fun getValue(): Int? = propValue
fun setValue( newValue: Int) {
val oldValue = propValue
propValue = newValue
changeSupport.firePropertyChange(propName, oldValue, newValue)
}
}
open class PropertyChangeAware {
val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.removePropertyChangeListener(listener)
}
}
fun main(args: Array<String>) {
val p = Person("Bob", 89, isMarried = false)
val data = mapOf("lastname" to "Jones", "company" to "JetBrains")
for ((attrName, value) in data)
p.setAttribute(attrName, value)
println(p)
}
Here is the current output:
name = Bob
company = JetBrains
lastname = Jones
Person(name="Bob", age=89, isMarried=false) Bob Bob kotlin.Unit() -> kotlin.Unit
Thanks, again, for any help.
You should not use print() or println() functions inside toString() because they output their arguments to the standard output immediately instead of appending them to the string returned to the caller.
Let's examine the output kotlin.Unit() -> kotlin.Unit you're getting. It consists of two parts:
kotlin.Unit is the string representation of attributes!!.forEach { ... } expression. forEach function returns without value, and in Kotlin it's expressed by returning the Unit object value. Its string representation is appended to the string you're returning.
the second part, () -> kotlin.Unit, is also the string representation of the lambda function expression { for((attrName, value) in ...) }. This function takes no parameters, and returns without value, which means that its type is () -> Unit. Note that in Kotlin the block { ... } declares a local lambda function. If you instead want to run the code inside of that block, use the run function: run { ... }
The goal of toString function is to build the string representation of an object. And for that you can use buildString function:
override fun toString() = buildString {
append("Person(name=\"${name?:""}\", age=${age?:99999}, isMarried=$isMarried) ")
append("${_attributes?.get("name")} ").append("$name ")
this._attributes!!.forEach { (attrName, value) -> append("$attrName = $value") }
for ((attrName, value) in this._attributes!!) {
append("attribute $attrName = ${this._attributes!![attrName]}")
}
}
This function creates a StringBuilder and passes it as a receiver to its functional argument, where you call append or appendln on that receiver. Then buildString converts that string builder to a string and returns it.

How to display values from the backing fields

i would liek to run the simple example mentioned below. eclipse generates an error says:
main class cant be found or loaded
please let me know how to fix this error and why it happens
in the below code I am trying to use the backing fields. however, the way they are used in the code does not provide the expected output.
please refer to the output section.
how to display output of the backing fields
code:
fun main(args: Array<String>) {
println("Hello, World!")
val p1 = Person_1("jack", 21);
p1.lastName = "stephan"
p1.month = "may"
println("lastName is ${p1.getLastName}")
println("month is ${p1.getMonth}")
val p2 = Person_1("jack", 21);
p2.lastName = "knauth"
p2.month = "june"
println(p2.getLastName)
println(p2.getMonth)
class Person_1 (val name: 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
}
val getLastName
get() = {
lastName
}
//backing field 2
var month : String? = null
set(value) {
field = value
}
val getMonth
get() = {
month
}
}
output:
Hello, World!
lastName is () -> kotlin.String?
month is () -> kotlin.String?
() -> kotlin.String?
() -> kotlin.String?
You can just get rid of your getters like this:
class Person_1 (val name: 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
}
//backing field 2
var month : String? = null
set(value) {
field = value
}
}
If later you'll need them you can add it like this without api changes:
var lastName : String? = null
get() = field
set(value) {
if (value?.length == 0) throw IllegalArgumentException("negative values are not allowed")
field = value
}

type-safe builder example kotlin

I want to have following person object in Kotlin :
var p = person {
age = 22
gender = "male"
name {
first = "Ali"
last = "Rezaei"
}
}
I have following code to build it :
data class Person(var age: Int? = null, var gender: String? = null
, var name : Name? = null) {
}
fun name(init: Name.() -> Unit): Name {
val n = Name()
n.init()
return n
}
data class Name(var first: String? = null, var last : String? = null)
fun person(init: Person.() -> Unit): Person {
val p = Person()
p.init()
return p
}
But when I print it, the result is following :
Person(age=22, gender="male", name=null)
What is wrong with my code?
You could make name an extension function on Person that assigns the Name to the Person instead of returning it:
fun Person.name(init: Name.() -> Unit) {
val n = Name()
n.init()
this.name = n
}
You could even consider a more concise syntax for the same, like this:
fun Person.name(init: Name.() -> Unit) {
this.name = Name().apply(init)
}
Shameless plug for my repository discussing DSL design and containing examples.
You need to assign to name. This ended up working for me...
var p = person {
age = 22
gender = "male"
name = name {
first = "Ali"
last = "Rezaei"
}
}