private set(value) requires initilization before init{ } - kotlin

I am trying to add a private set(value) to a var, but this requires to init the field before init { } has run. Why?
fun main(args: Array<String>) {
println("Hello fellow Stackoverflowers!")
val data = Data(listOf<Point>(Point(1, 1.0, 1.0), Point(2, 2.0, 2.0), Point(3, 3.0, 3.0)))
val testInit = TestInit(data)
testInit.magicMethod()
println("Relevant Point Id: ${testInit.relevantPoint.id}")
}
class TestInit(val someData: Data) {
var relevantPoint: Point // = Point(0,0.0,0.0) // or do this, but why? It is set in init{}, lateinit also not allowed
// private set // this works
private set(value) { // Why can't I do this? -> "Property must be initialized - Error"
if (value.id < 100)
field = value
else
field = someData.points.first()
}
init {
if (someData.points.size < 3) // doing validation before setting the point
throw IllegalArgumentException("Need at least three points!")
relevantPoint = someData.points.first() // here the point gets initialized
}
fun magicMethod() {
// do other calculations
relevantPoint = someData.points[someData.points.size / 2] // just assign some point
}
}
data class Data(var points: List<Point> = mutableListOf())
data class Point(val id: Int, val x: Double, val y: Double)

When you call relevantPoint = someData.points.first(), the field will only get initialised if it's id is less than 100 (due to your setter logic). So there is a chance that you won't have initialised the field.
You can either use a backing field if you need to keep the initialisation in init, or simply initialise the value inline, which won't go through the setter:
var relevantPoint = someData.points.first()
private set(value) {
...
}
Edit
It's interesting that this problem occurs even after you adding an else branch to your setter and I am not sure why that is. However for a solution (if you can't initialise it as above) you can easily use a backing field:
private var _relevantPoint: Point
var relevantPoint: Point
private set(value) {
if (value.id < 100) _relevantPoint = value
}
get() = _relevantPoint
init {
_relevantPoint = someData.points.first()
}

In Kotlin, variables must be either initialized on declaration or (only in case of var) get marked with lateinit.
According to the Documentation:
The modifier (lateinit) can be used on var properties declared inside the body of
a class (not in the primary constructor, and only when the property
does not have a custom getter or setter) as well as for top-level
properties and local variables.
So You can't do The latter
Your best bet is what Henry suggested:
var relevantPoint = someData.points.first()
private set(value) {
if (value.id < 100)
field = value
}
The other option is that you don't use custom setter and check your criteria elsewhere.

I think the best option is what was presented earlier.
But I thought of an alternative, this way.
data class Data(var points: List<Point> = mutableListOf())
data class Point(var id: Int, val x: Double, val y: Double)
class RelevantPoint(_point: Point) {
var point: Point = _point
set(value) {
if (value.id < 100)
field = value
}
}
fun main(args: Array<String>) {
println("Hello fellow Stackoverflowers!")
val data = Data(listOf<Point>(Point(1, 1.0, 1.0), Point(2, 2.0, 2.0), Point(3, 3.0, 3.0)))
val testInit = TestInit(data)
testInit.magicMethod()
println("Relevant Point Id: ${testInit.relevantPoint.point.id}")
}
class TestInit(val someData: Data) {
var relevantPoint: RelevantPoint
init {
if (someData.points.size < 3) // doing validation before setting the point
throw IllegalArgumentException("Need at least three points!")
relevantPoint = RelevantPoint(someData.points.first())
}
fun magicMethod() {
// do other calculations
relevantPoint.point = someData.points[someData.points.size / 2] // just assign some point
}
}

Related

Kotlin adapter pattern: Duplicate method name error on getter function

Just simple kotlin code to demo Adapter Pattern in Gang of Four Design Pattern. I have a presentation today about this but i can't done it. So sad. I don't want to add much details but they don't allow me to post without much details.
Exception:
Exception in thread "main" java.lang.ClassFormatError: Duplicate method name "getRadius" with signature "()I" in class file RoundHole
at java.lang.ClassLoader.defineClass1 (ClassLoader.java:-2)
at java.lang.ClassLoader.defineClass (ClassLoader.java:756)
at java.security.SecureClassLoader.defineClass (SecureClassLoader.java:142)
Code:
interface WorkingWithRound {
fun getRadius(): Int
}
open class RoundPeg(val radius: Int = 0): WorkingWithRound {
override fun getRadius() = radius
}
class RoundHole(val radius: Int = 0): WorkingWithRound {
override fun getRadius() = radius
fun fit(peg: RoundPeg) {
println(getRadius() >= peg.getRadius())
}
}
class SquarePeg(val width: Int = 0)
class SquarePegAdapter(val speg: SquarePeg): RoundPeg() {
override fun getRadius() = (speg.width / 2 * 1.4).toInt()
}
fun main() {
val hole = RoundHole(5)
val rpeg = RoundPeg(5)
hole.fit(rpeg)
val small_sqpeg = SquarePeg(5)
val large_sqpeg = SquarePeg(10)
//hole.fit(small_sqpeg) // this won't compile (incompatible types)
val small_sqpeg_adapter = SquarePegAdapter(small_sqpeg)
val large_sqpeg_adapter = SquarePegAdapter(large_sqpeg)
hole.fit(small_sqpeg_adapter) // true
hole.fit(large_sqpeg_adapter) // false
}
Kotlin generates getter method for instance variables, hence the error. Couple of options to fix the issue
Make radius variable private
open class RoundPeg(private val radius: Int = 0): WorkingWithRound
Mark radius with #JvmField to instruct compiler not to generate any getter
class RoundHole(#JvmField val radius: Int = 0): WorkingWithRound
I think this would be simpler (as well as avoiding the compilation problems) if the interface defined a property, rather than a getter method:
interface WorkingWithRound {
val radius: Int
}
That compiles down to pretty much the same bytecode; but the intent is clearer, and it can then be implemented directly in the constructors:
open class RoundPeg(override val radius: Int = 0): WorkingWithRound
class RoundHole(override val radius: Int = 0): WorkingWithRound {
fun fit(peg: RoundPeg) {
println(radius >= peg.radius)
}
}
The SquarePegAdapter can be simplified, as it no longer needs to store the speg value, but can simply use it as an initialiser for the property:
class SquarePegAdapter(speg: SquarePeg): RoundPeg() {
override val radius = (speg.width / 2 * 1.4).toInt()
}
And the rest of the code needs no changes.

How to address a getter from inside the class in Kotlin

Consider following Kotlin-Code:
class Foo(input: Int) {
private var someField: Int = input
get() = -field
set(value) {
field = -value
}
fun bar() {
println(someField)
}
}
fun main() {
Foo(1).bar()
}
This prints -1 in the console which means that inside method bar() someField references the attribute and not the corresponding getter. Is there a way that allows me to use the get()-method as if I was referencing this field from outside?
Perhaps you could track the "raw" value separately from the negative value? Something like this:
class Foo(input: Int) {
private var _someField: Int = input
var someField: Int
get() = -_someField
set(value) {
_someField = -value
}
fun bar() {
println(someField)
}
}
Now the class internals can reference _someField to deal directly with the raw value, while outside clients can only "see" someField.

How do I bind a custom property to a textfield bidirectionally?

I have a complex object that I want to display in a textfield. This is working fine with a stringBinding. But I don't know how to make it two-way so that the textfield is editable.
package com.example.demo.view
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import tornadofx.*
class MainView : View("Hello TornadoFX") {
val complexThing: Int = 1
val complexProperty = SimpleObjectProperty<Int>(complexThing)
val complexString = complexProperty.stringBinding { complexProperty.toString() }
val plainString = "asdf"
val plainProperty = SimpleStringProperty(plainString)
override val root = vbox {
textfield(complexString)
label(plainProperty)
textfield(plainProperty)
}
}
When I run this, the plainString is editable and I see the label change because the edits are going back into the property.
How can I write a custom handler or what class do I need to use to make the stringBinding be read and write? I looked through a lot of the Property and binding documentation but did not see anything obvious.
Ta-Da
class Point(val x: Int, val y: Int) //You can put properties in constructor
class PointConverter: StringConverter<Point?>() {
override fun fromString(string: String?): Point? {
if(string.isNullOrBlank()) return null //Empty strings aren't valid
val xy = string.split(",", limit = 2) //Only using 2 coordinate values so max is 2
if(xy.size < 2) return null //Min values is also 2
val x = xy[0].trim().toIntOrNull() //Trim white space, try to convert
val y = xy[1].trim().toIntOrNull()
return if(x == null || y == null) null //If either conversion fails, count as invalid
else Point(x, y)
}
override fun toString(point: Point?): String {
return "${point?.x},${point?.y}"
}
}
class MainView : View("Hello TornadoFX") {
val point = Point(5, 6) //Probably doesn't need to be its own member
val pointProperty = SimpleObjectProperty<Point>(point)
val pc = PointConverter()
override val root = vbox {
label(pointProperty, converter = pc) //Avoid extra properties, put converter in construction
textfield(pointProperty, pc)
}
}
I made edits to your converter to "account" for invalid input by just returning null. This is just a simple band-aid solution that doesn't enforce correct input, but it does refuse to put bad values in your property.
This can probably be done more cleanly. I bet there is a way around the extra property. The example is fragile because it doesn't do input checking in the interest of keeping it simple. But it works to demonstrate the solution:
class Point(x: Int, y: Int) {
val x: Int = x
val y: Int = y
}
class PointConverter: StringConverter<Point?>() {
override fun fromString(string: String?): Point? {
val xy = string?.split(",")
return Point(xy[0].toInt(), xy[1].toInt())
}
override fun toString(point: Point?): String {
return "${point?.x},${point?.y}"
}
}
class MainView : View("Hello TornadoFX") {
val point = Point(5, 6)
val pointProperty = SimpleObjectProperty<Point>(point)
val pointDisplayProperty = SimpleStringProperty()
val pointStringProperty = SimpleStringProperty()
val pc = PointConverter()
init {
pointDisplayProperty.set(pc.toString(pointProperty.value))
pointStringProperty.set(pc.toString(pointProperty.value))
pointStringProperty.addListener { observable, oldValue, newValue ->
pointProperty.set(pc.fromString(newValue))
pointDisplayProperty.set(pc.toString(pointProperty.value))
}
}
override val root = vbox {
label(pointDisplayProperty)
textfield(pointStringProperty)
}
}

List setter is not being called when appending a new value

I had a workaround solution for this problem which involves reassigning the variable to itself; however, this doesn't seem efficient.
fun main() {
val x = Example1()
x.listOfInt = mutableListOf(3,4)
x.listOfInt.add(455)
}
class Example1 {
var listOfInt: MutableList<Int> = mutableListOf()
set(value) {
println("setting it here to value => $value")
field = value
}
}
// It only prints:
// setting it here to value => [3, 4]
As you can see, the setter doesn't get triggered when the value is appended which effectively changing the value of the collection. Is there a better way to do this?
Here's a link to the code on Kotlin's playground
The reason this is odd to me because of the below similar code in Swift which does the expected when the Array is appended:
import Foundation
class Example1 {
var fieldProperty: Int = 0
var listOfInt: [Int] = [] {
didSet {
print("setting it here to value => \(listOfInt) vs oldValue: \(oldValue)")
}
}
}
let x = Example1()
x.listOfInt = [3,4]
x.listOfInt.append(455)
// this prints:
// setting it here to value => [3, 4] vs oldValue: []
// setting it here to value => [3, 4, 455] vs oldValue: [3, 4]
I know I might be comparing apples to oranges, but I'm just curious if there's a better solution.
The use of set(value) { } is the equivalent of implementing a setter on that variable.
The code you provided could be interpreted as:
class Example1 {
var listOfInt: MutableList<Int> = mutableListOf()
fun set(value: MutableList<Int>) {
println("setting it here to value => $value")
this.listOfInt = value
}
}
When you call x.listOfInt.add(455) you are calling the add method on the list defined in the x instance of Example1.
Try calling x.listOfInt = mutableListOf(1,2,3) and you'll see your setter will correctly get called.
To implement what you want to do, there is several ways:
Implement a new version of MutableList<> which overrides the method add (this might be overkill).
Create a add method in your Example1 class which will look like this:
fun add(value: Int) {
println("adding value to list => $value")
listOfInt.add(value)
}
You main will now look like this:
fun main() {
val x = Example1()
x.listOfInt = mutableListOf(3,4)
x.add(455)
}

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.