Kotlin: Evaluation of getter-/setter-methods - kotlin

I've got following example class:
fun main(args: Array<String>) {
var car = Car("Ford", 50, 8000)
println(car.vendor)
println("Speed: ${car.speed}")
car.speed = 65 // How does it know which setter to invoke?
println("New speed: ${car.speed}")
}
class Car(vendor: String = "Unknown", speed: Int = 0, price: Int = 0) {
var vendor = vendor
get() = field
set(newVendor: String) {
field = newVendor
}
var speed = speed
get() = field
set(speed: Int) {
field = speed
}
var price = price
get() = field
set(newPrice: Int) {
field = price
}
}
When I change the speed-attribute (please see the commented line): Where does Kotlin know from, which setter-method it has actually to invoke?
There are two setter-methods within my class (speed, price) which both are named "set" and both expect an integer-value.
Is the order, in which the methods are defined, crucial?
The respective getter-/setter-methods have to be written directly after the attribute-definition? Or does it somehow work differently? If so: How?
Are the indentations just a covention? Or are the indentations needed for the compiler?

car.speed = 65 is called Property-access syntax. It is equivalent to car.setSpeed(65).
You don't have two methods named set; you've two mutable properties speed and price both of type Int. They each have corresponding getter and setter methods; in Java Beans convention, the getter for speed would be getSpeed and the setter setSpeed(Int).
See https://kotlinlang.org/docs/reference/properties.html for more details.

Related

Can I set an object variable with a reflection value to a property of another object?

I have a class that has a property whose type is KMutableVar1. The objects of that class have a variable assigned to a reflection of another class's property. I have a function that is supposed take in an object of the first class and uses its variable of type KMutableVar1 to determine which property of an object of the second class to edit.
jeez that paragraph is awful im so so sorry ><
I have already tried assigning the object's KMutableVar1 variable to another variable and then trying to tie that variable to an object using dot notation, but that variable name isn't in the primary constructor for the class and thus an error occurs.
class Thing(var amount: Int, var id: Int){
fun editAttributes(object: Thing, editor: RemoteEdit){
//My initial thought here was to do the following:
var editing = editor.attributeToEdit
object.editing = editor.newValue
//But this raises an error since class 'thing' has no attribute 'editing'
}
}
var bananas = Thing(amount = 12, id = 21)
class RemoteEdit(var attributeToEdit: KMutableVar1, var newValue: Int)
var remoteEditor = RemoteEdit(attributeToEdit = Thing::amount, newValue = 23)
My intent is for the function to change bananas.amount to 23.
Sorry, I'm not fully understanding why you need it but it will work I guess:
import kotlin.reflect.KMutableProperty1
class Thing(var amount: Int, var id: Int) {
fun editAttributes(editor: RemoteEdit) {
val editing = editor.attributeToEdit
editing.set(this, editor.newValue)
}
}
class RemoteEdit(var attributeToEdit: KMutableProperty1<Thing, Int>, var newValue: Int)
fun main() {
val bananas = Thing(amount = 12, id = 21)
val remoteEditor = RemoteEdit(attributeToEdit = Thing::amount, newValue = 23)
bananas.editAttributes(remoteEditor)
println(bananas.amount) // prints 23
}
I have a feeling this might be an XY problem because there are so many unusual things going on in your code. Why would the implementation of changing the property value through reflection be in the class that's being edited?
I suppose if there is some reason you need to be able to pass these parameters for editing around, you would need a class, but then it makes sense for it to implement the function for using it by itself:
class RemoteEdit<T, R>(var attributeToEdit: KMutableProperty1<T, R>, var newValue: R) {
fun execute(item: T) {
attributeToEdit.set(item, newValue)
}
}
val bananas = Thing(amount = 12, id = 21)
val edit23 = RemoteEdit(Thing::amount, 23)
edit23.execute(bananas)
If you don't need to pass these around, all you need is a top level function:
fun <T, R> editProperty(item: T, attributeToEdit: KMutableProperty1<T, R>, newValue: R) =
attributeToEdit.set(item, newValue)
val bananas = Thing(amount = 12, id = 21)
editProperty(bananas, Thing::amount, 23)

How to have a custom setter with a named argument and default

I have a Kotlin class with named arguments and defaults for non-specified arguments. I am trying to figure out how to create a custom setter for one argument and just can't seem to figure out what I'm doing wrong - although it's probably simple. Here is a simplified version of the class:
class ItemObject(var itemNumber: String = "",
var itemQty: Int = 0)
I can use the properties of this class without issues itemObject.itemQty = itemObject.itemQty + 1 (accessing both the getter and setter).
However, I'd like to make a custom setter to prevent the itemQty from going below zero. So I've tried many variations on the following theme:
class ItemObject(var itemNumber: String = "",
itemQty: Int = 0) {
var itemQty: Int = 0
set(value: Int) =
if (value >= 0) {
itemQty = value // Don't want to go negative
} else {
}
}
This compiles without issue, but seems to keep defaulting itemQty to zero (or something like this - I haven't tracked it down).
Can anyone point me in the correct direction? I'd certainly appreciate it - this is my first Kotlin project and could use a bit of help. :)
This is what vetoable is for:
var quantity: Int by Delegates.vetoable(0) { _, _, new ->
new >= 0
}
Return true to accept the value, return false to reject it.
Well, you initualize the real field to 0 and ignore the passed value ... Instead, initialize the property with the passed constructor parameter:
class Item(_itemQty: Int = 0) {
var itemQty: Int = Math.max(0, _itemQty)
set(v) { field = Math.max(0, v) }
}
(I used two different Identifiers to seperate the parameter and the property, as mentioned in the comments you can also use the same name for the property and the parameter [but be careful, that could add confusion]).
You should also set the backing field, not the setter itself which will end in endless recursion.
If the setter is rather complicated and also needs to be applied to the initial value, then you could initialize the property, and execute the setter afterwards with the parameter:
class Item(_itemQty: Int = 0) {
var itemQty: Int = 0
set(v) { field = Math.max(0, v) }
init { itemQty = _itemQty }
}
As an opinion-based side note, item.itemQty is not really descriptive, item.quantity would be way more readable.
There are different ways of preventing value going below zero.
One is unsigned types. Experimental at the moment. UInt in your case
class ItemObject(var itemNumber: String = "",
var itemQty: UInt = 0u
)
If you don't care about value overflow - it might be an option
Another way is Property Delegation
class ItemObject(var itemNumber: String = "", itemQty: Int = 0) {
var itemQty: Int by PositiveDelegate(itemQty)
}
class PositiveDelegate(private var prop: Int) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Int = prop
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
if (value >= 0) prop = value
}
}
Third one with custom setters is described in other answers.

Deserialize a nested json field with Jackon in Kotlin

I've already deserialized some nested field in the past in Java, following instructions from https://www.baeldung.com/jackson-nested-values (section 5) :
#JsonProperty("brand")
private void unpackNested(Map<String,Object> brand) {
this.brandName = (String)brand.get("name");
Map<String,String> owner = (Map<String,String>)brand.get("owner");
this.ownerName = owner.get("name");
}
ownerName being a field in the bean.
Now, I need to do something similar in Kotlin, but I am not happy with what I have so far. Assuming I have a MyPojo class that has a createdAt field, but in the JSON that represents it, the field is nested under a metadata attribute:
data class MyPojo(var createdAt: LocalDateTime = LocalDateTime.MIN) {
#JsonProperty("metadata")
private fun unpackNested(metadata: Map<String, Any>) {
var createdAtAsString = metadata["createdAt"] as String
this.createdAt = LocalDateTime.parse(createdAtAsString,DateTimeFormatter.ISO_DATE_TIME)
}
}
One of the thing I don't like here is that I am forced to make createdAt a var, not a val.
Is there a Kotlin trick to make things overall better here?
For the sake of simplicity, I used Int as type for createdAt.
You could do it like this:
class JsonData(createdAt: Int = 0) {
private var _createdAt: Int = createdAt
val createdAt: Int
get() = _createdAt
#JsonProperty("metadata")
private fun unpackNested(metadata: Map<String, Any>) {
_createdAt = metadata["createdAt"] as Int
}
}
createdAt will be a parameter with a default value. Since a data classe's constructor can only have properties (var/val) you will loose the advantages of a data class (toString() out of the box etc.).
You will assign this parameter to a private var _createdAt when the class is instantiated.
The only thing that will be exposed to the outside is a property without a backing field createAt (just a getter in Java terms). So, _createdAt cannot be changed after instantiation.
There are two cases now:
If you instantiate the class, _createdAt will be set to the value you specify.
If Jackson instantiates the class the value of _createdAt will be overwritten by the unpackNested call.
Here is an example:
val jsonStr = """{
"metadata": {
"createdAt": 1
}
}
""".trimIndent()
fun main() {
val objectMapper = ObjectMapper()
// Jackson does instantiation
val jsonData = objectMapper.readValue(jsonStr, JsonData::class.java)
// you do it directly
JsonData(5)
}

How to make field required in kotlin DSL builders

In Kotlin, when creating a custom DSL, what is the best way to force filling required fields inside the builder's extension functions in compile time. E.g.:
person {
name = "John Doe" // this field needs to be set always, or compile error
age = 25
}
One way to force it is to set value in a function parameter instead of the body of the extension function.
person(name = "John Doe") {
age = 25
}
but that makes it a bit more unreadable if there are more required fields.
Is there any other way?
New type inference enables you to make a null-safe compile-time checked builder:
data class Person(val name: String, val age: Int?)
// Create a sealed builder class with all the properties that have default values
sealed class PersonBuilder {
var age: Int? = null // `null` can be a default value if the corresponding property of the data class is nullable
// For each property without default value create an interface with this property
interface Named {
var name: String
}
// Create a single private subclass of the sealed class
// Make this subclass implement all the interfaces corresponding to required properties
private class Impl : PersonBuilder(), Named {
override lateinit var name: String // implement required properties with `lateinit` keyword
}
companion object {
// Create a companion object function that returns new instance of the builder
operator fun invoke(): PersonBuilder = Impl()
}
}
// For each required property create an extension setter
fun PersonBuilder.name(name: String) {
contract {
// In the setter contract specify that after setter invocation the builder can be smart-casted to the corresponding interface type
returns() implies (this#name is PersonBuilder.Named)
}
// To set the property, you need to cast the builder to the type of the interface corresponding to the property
// The cast is safe since the only subclass of `sealed class PersonBuilder` implements all such interfaces
(this as PersonBuilder.Named).name = name
}
// Create an extension build function that can only be called on builders that can be smart-casted to all the interfaces corresponding to required properties
// If you forget to put any of these interface into where-clause compiler won't allow you to use corresponding property in the function body
fun <S> S.build(): Person where S : PersonBuilder, S : PersonBuilder.Named = Person(name, age)
Use case:
val builder = PersonBuilder() // creation of the builder via `invoke` operator looks like constructor call
builder.age = 25
// builder.build() // doesn't compile because of the receiver type mismatch (builder can't be smart-casted to `PersonBuilder.Named`)
builder.name("John Doe")
val john = builder.build() // compiles (builder is smart-casted to `PersonBuilder & PersonBuilder.Named`)
Now you can add a DSL function:
// Caller must call build() on the last line of the lambda
fun person(init: PersonBuilder.() -> Person) = PersonBuilder().init()
DSL use case:
person {
name("John Doe") // will not compile without this line
age = 25
build()
}
Finally, on JetBrains open day 2019 it was said that the Kotlin team researched contracts and tried to implement contracts that will allow creating safe DSL with required fields. Here is a talk recording in Russian. This feature isn't even an experimental one, so
maybe it will never be added to the language.
In case you're developing for Android I wrote a lightweight linter to verify mandatory DSL attributes.
To solve your use case you will only need to add an annotation #DSLMandatory to your name property setter and the linter will catch any place when it is not assigned and display an error:
#set:DSLMandatory
var name: String
You can take a look here:
https://github.com/hananrh/dslint/
Simple, throw an exception if it's not defined in your DLS after the block
fun person(block: (Person) -> Unit): Person {
val p = Person()
block(p)
if (p.name == null) {
// throw some exception
}
return p
}
Or if you want to enforce it at build time, just make it return something useless to the outer block if not defined, like null.
fun person(block: (Person) -> Unit): Person? {
val p = Person()
block(p)
if (p.name == null) {
return null
}
return p
}
I'm guessing your going off this example so maybe address would be the better example case:
fun Person.address(block: Address.() -> Unit) {
// city is required
var tempAddress = Address().apply(block)
if (tempAddress.city == null) {
// throw here
}
}
But what if we wanted to ensure everything was defined, but also wanted to let you do it in any order and break at compile time. Simple, have two types!
data class Person(var name: String = null,
var age: Int = null,
var address: Address = null)
data class PersonBuilder(var name: String? = null,
var age: Int? = null,
var address: Address? = null)
fun person(block: (PersonBuilder) -> Unit): Person {
val pb = PersonBuilder()
block(p)
val p = Person(pb.name, pb.age, pb.address)
return p
}
This way, you get to you the non-strict type to build, but it better be null-less by the end. This was a fun question, thanks.

Kotlin Data class copy extension

I am trying to find a solution for a nice kotlin data class solution. I have already this:
data class Object(
var classMember: Boolean,
var otherClassMember: Boolean,
var example: Int = 0) {
fun set(block: Object.() -> kotlin.Unit): Object {
val copiedObject = this.copy()
copiedObject.apply {
block()
}
return copiedObject
}
fun touch(block: Object.() -> kotlin.Unit): Object {
return this.set {
classMember = true
otherClassMember = false
block() }
}
}
val test = Object(true,true,1)
val changedTest = test.touch { example = 2 }
the result of this method is that the changedTest object has classMember = true, otherClassMember = false and example = 2
The problem with this solution is, the class properties are not immutable with var declaration. Does somebody have an idea how to optimize my methods to change var to val?
val says that a variable can't change it's value after initialization at the definition point. Kotlin's generated copy method does not modify an existing copy after construction: this method actually uses retrieved values from an object, replaces these values with ones that provided in copy method (if any), and after that just constructs a new object using these values.
So, it is not possible to perform such an optimization if you are going to change object's state after construction.
If I understood what you want correctly, you can do
data class Object(
val classMember: Boolean,
val otherClassMember: Boolean,
val example: Int = 0) {
fun touch(example: Int = this.example): Object {
return copy(
classMember = true,
otherClassMember = false,
example = example)
}
}
val test = Object(true,true,1)
val changedTest = test.touch(example = 2)
Though you need to repeat parameters other than classMember and otherClassMember but without reflection you can't do better.