Convert static variables from Java to Kotlin - properties

I'm trying to convert the following code to Kotlin AND still have one of the classes (Foo) used by Java. What is the proper way of making this conversion?
Original Java:
public class Foo {
public static final String C_ID = "ID";
public static final String C_NAME = "NAME";
public static final String[] VALUES = {"X", "Y", "Z"};
public static String[] getAll() {
return new String[] {C_ID, C_NAME};
}
}
public class Bar {
public void doStuff() {
String var1 = Foo.C_ID;
String[] array1 = Foo.VALUES;
String[] array2 = Foo.getAll();
}
}
Auto conversion fo Foo to Kotlin
object Foo {
val C_ID = "ID"
val C_NAME = "NAME"
val VALUES = arrayOf("X", "Y", "Z")
val all: Array<String>
get() = arrayOf(C_ID, C_NAME)
}
Problem:
Bar class can no longer access C_ID or VALUES (error: "private access")
if I put "const" in front of C_ID, it works... but I cannot do the same with VALUES ("const" can ONLY be used on primatives or String)
Is there a different way I should be doing this (so both Java code and Kotlin code can access everything in Foo)?

The current semantics come from Kotlin Beta Candidate:
#JvmField and objects
We have made the strategy for generating pure fields (as opposed to get/set pairs) more predictable: from now on only properties annotated as #JvmField, lateinit or const are exposed as fields to Java clients. Older versions used heuristics and created static fields in objects unconditionally, which is against our initial design goal of having binary-compatibility-friendly APIs by default.
Also, singleton instances are now accessible by the name INSTANCE (instead of INSTANCE$).
According to this and to the reference, there are three ways of working with properties of a Kotlin object from Java:
Use Foo.INSTANCE.
By default, properties of object won't be static fields for Java, but Java can access the properties through Foo object instance -- Foo.INSTANCE.
So the expression will be Foo.INSTANCE.getC_ID().
Mark a property with #JvmStatic annotation:
object Foo {
#JvmStatic val C_ID = "ID"
//...
}
This will generate static getter for C_ID instead of Foo instance getter which will be accessible as Foo.getC_ID().
Use #JvmField annotation on property declaration:
object Foo {
#JvmField val C_ID = "ID"
//...
}
This will make Kotlin compiler generate a static field for Java instead of property.
Then in Java you can access it as a static field: Foo.C_ID.
But it won't work on properties without backing fields like all in your example.
For primitives, as you stated, one can use const which will have the same effect as #JvmField in terms of visibility in Java.
By the way, when it comes to methods, the situation is the same, and there is #JvmStatic annotation for them.

In your foo class you can put those properties and the method inside a companion object:
class Foo {
companion object {
val C_ID:String = "ID"
val C_NAME:String = "NAME"
#JvmField val VALUES = arrayOf("X", "Y", "Z")
fun getAll():Array<String> {
return arrayOf(C_ID, C_NAME)
}
}
}
Then you can call Foo.getAll(), and Foo.C_ID, Foo.C_NAME and Foo.VALUES.

You should be able to access the values "the kotlin way":
object Foo {
val C_ID = "ID"
val C_NAME = "NAME"
val VALUES = arrayOf("X", "Y", "Z")
val all: Array<String>
get() = arrayOf(C_ID, C_NAME)
}
fun main(args: Array<String>) {
Foo.all.forEach { it->println(it) }
}
With as result:
ID
NAME
Process finished with exit code 0

it's better if you create new kotlin file just for constants.
create Constants.kt file and paste below code.
object Constants {
val C_ID = "ID"
val C_NAME = "NAME"
val VALUES = arrayOf("X", "Y", "Z")
val all: Array<String>
get() = arrayOf(C_ID, C_NAME)
}
in your main activity you can access the constants by the constant name the android studio will automatically import the constants. here is my mainActivity:
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.example.architecturecompintro.Constants.C_ID
import com.example.architecturecompintro.Constants.C_NAME
import com.example.architecturecompintro.Constants.VALUES
import com.example.architecturecompintro.Constants.all
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val TAG = "info"
Log.i(TAG, C_ID)
Log.i(TAG,C_NAME)
for(item in VALUES) {
Log.i(TAG,item)
}
val arrayItem = all
for(item in arrayItem) {
Log.i(TAG,item)
}
}
}
I was able to get log output successfully

Related

JavaFx TableView shows long string representation of columns

I just start JavaFx and am a bit stuck with TableView, it shows very long string representation of each column like:
StringProperty [bean: com.plcsim2.PlcSimModel$ErpSheet#2c1ffe7b, name:name, value: big]
IntegerProperty [bean: com.plcsim2.PlcSimModel$ErpSheet#2c1ffe7b, name:sheet_long, value: 5000]
IntegerProperty [bean: com.plcsim2.PlcSimModel$ErpSheet#2c1ffe7b, name:sheet_short, value: 3000]
while I am expecting only "big", "5000", "3000" to appear in the cells.
Here is my model:
object PlcSimModel {
class ErpSheet {
val name = SimpleStringProperty(this, "name")
val sheet_long = SimpleIntegerProperty(this, "sheet_long")
val sheet_short = SimpleIntegerProperty(this, "sheet_short")
}
val erpSheets = ArrayList<ErpSheet>()
}
The fxml:
<VBox alignment="CENTER" prefHeight="562.0" prefWidth="812.0" spacing="20.0"
xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.plcsim2.PlcSimController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
<TableView fx:id="table_1" prefHeight="400.0" prefWidth="200.0">
</TableView>
<Button onAction="#onHelloButtonClick" text="Hello!" />
</VBox>
And finally the controller:
#FXML
private fun onHelloButtonClick() {
val rs = DB.populateSql("select name, sheet_long, sheet_short from erp_sheet")
PlcSimModel.erpSheets.clear()
if (rs != null) {
while (rs.next()) {
val sheet = PlcSimModel.ErpSheet()
sheet.name.set(rs.getString("name"))
sheet.sheet_long.set(rs.getInt("sheet_long"))
sheet.sheet_short.set(rs.getInt("sheet_short"))
PlcSimModel.erpSheets.add(sheet)
}
}
table_1.columns.clear()
val col0 = TableColumn<PlcSimModel.ErpSheet, String>("name")
col0.cellValueFactory = PropertyValueFactory("name")
table_1.columns.add(col0)
val col1 = TableColumn<PlcSimModel.ErpSheet, Int>("sheet_long")
col1.cellValueFactory = PropertyValueFactory("sheet_long")
table_1.columns.add(col1)
val col2 = TableColumn<PlcSimModel.ErpSheet, Int>("sheet_short")
col2.cellValueFactory = PropertyValueFactory("sheet_short")
table_1.columns.add(col2)
table_1.items = FXCollections.observableArrayList(PlcSimModel.erpSheets)
}
It seems controller is good, it is able to get the values from database and add rows to TableView, but why TableView shows Property object's string representation, instead of just show the value?
Thanks a lot!
JavaFX Properties
When a class exposes a JavaFX property, it should adhere to the following pattern:
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
public class Foo {
// a field holding the property
private final StringProperty name = new SimpleStringProperty(this, "name");
// a setter method (but ONLY if the property is writable)
public final void setName(String name) {
this.name.set(name);
}
// a getter method
public final String getName() {
return name.get();
}
// a "property getter" method
public final StringProperty nameProperty() {
return name;
}
}
Notice that the name of the property is name, and how that is used in the names of the getter, setter, and property getter methods. The method names must follow that format.
The PropertyValueFactory class uses reflection to get the needed property. It relies on the method naming pattern described above. Your ErpSheet class does not follow the above pattern. The implicit getter methods (not property getter methods) return the property objects, not the values of the properties.
Kotlin & JavaFX Properties
Kotlin does not work especially well with JavaFX properties. You need to create two Kotlin properties, one for the JavaFX property object, and the other as a delegate (manually or via the by keyword) for the JavaFX property's value.
Here is an example:
import javafx.beans.property.IntegerProperty
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
class Person(name: String = "", age: Int = 0) {
#get:JvmName("nameProperty")
val nameProperty: StringProperty = SimpleStringProperty(this, "name", name)
var name: String
get() = nameProperty.get()
set(value) = nameProperty.set(value)
#get:JvmName("ageProperty")
val ageProperty: IntegerProperty = SimpleIntegerProperty(this, "age", age)
var age: Int
get() = ageProperty.get()
set(value) = ageProperty.set(value)
}
You can see, for instance, that the name Kotlin property delegates its getter and setter to the nameProperty Kotlin property.
The #get:JvmName("nameProperty") annotation is necessary for Kotlin to generate the correct "property getter" method on the Java side (the JVM byte-code). Without that annotation, the getter would be named getNameProperty(), which does not match the pattern for JavaFX properties. You can get away with not using the annotation if you never plan to use your Kotlin code from Java, or use any class that relies on reflection (e.g., PropertyValueFactory) to get the property.
See the Kotlin documentation on delegated properties if you want to use the by keyword instead of manually writing the getter and setter (e.g., var name: String by nameProperty). You can write extension functions for ObservableValue / WritableValue (and ObservableIntegerValue / WritableIntegerValue, etc.) to implement this.
Runnable Example
Here is a runnable example using the above Person class. It also periodically increments the age of each Person so you can see that the TableView is observing the model items.
import javafx.animation.PauseTransition
import javafx.application.Application
import javafx.beans.property.IntegerProperty
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
import javafx.scene.Scene
import javafx.scene.control.TableColumn
import javafx.scene.control.TableView
import javafx.scene.control.cell.PropertyValueFactory
import javafx.stage.Stage
import javafx.util.Duration
fun main(args: Array<String>) = Application.launch(App::class.java, *args)
class App : Application() {
override fun start(primaryStage: Stage) {
val table = TableView<Person>()
table.columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY
table.items.addAll(
Person("John Doe", 35),
Person("Jane Doe", 42)
)
val nameCol = TableColumn<Person, String>("Name")
nameCol.cellValueFactory = PropertyValueFactory("name")
table.columns += nameCol
val ageCol = TableColumn<Person, Number>("Age")
ageCol.cellValueFactory = PropertyValueFactory("age")
table.columns += ageCol
primaryStage.scene = Scene(table, 600.0, 400.0)
primaryStage.show()
PauseTransition(Duration.seconds(1.0)).apply {
setOnFinished {
println("Incrementing age of each person...")
table.items.forEach { person -> person.age += 1 }
playFromStart()
}
play()
}
}
}
class Person(name: String = "", age: Int = 0) {
#get:JvmName("nameProperty")
val nameProperty: StringProperty = SimpleStringProperty(this, "name", name)
var name: String
get() = nameProperty.get()
set(value) = nameProperty.set(value)
#get:JvmName("ageProperty")
val ageProperty: IntegerProperty = SimpleIntegerProperty(this, "age", age)
var age: Int
get() = ageProperty.get()
set(value) = ageProperty.set(value)
}
Avoid PropertyValueFactory
With all that said, you should avoid using PropertyValueFactory, whether you're writing your application in Java or Kotlin. It was added when lambda expressions were not yet part of Java to help developers avoid writing verbose anonymous classes everywhere. However, it has two disadvantages: it relies on reflection and, more importantly, you lose compile-time validations (e.g., whether the property actually exists).
You should replace uses of PropertyValueFactory with lambdas. For example, from the above code, replace:
val nameCol = TableColumn<Person, String>("Name")
nameCol.cellValueFactory = PropertyValueFactory("name")
table.columns += nameCol
val ageCol = TableColumn<Person, Number>("Age")
ageCol.cellValueFactory = PropertyValueFactory("age")
table.columns += ageCol
With:
val nameCol = TableColumn<Person, String>("Name")
nameCol.setCellValueFactory { it.value.nameProperty }
table.columns += nameCol
val ageCol = TableColumn<Person, Number>("Age")
ageCol.setCellValueFactory { it.value.ageProperty }
table.columns += ageCol
Now I know PropertyValueFactory uses reflection to find the property. I thought it is the key defined to IntegerProperty or StringProperty. So simply changing the model class to following fixed the problem:
class ErpSheet {
var name = ""
var sheet_long = 0
var sheet_short = 0
}
The member variable name is the key to PropertyVaueFactory.

Kotlin pass through constructor parameters to parent without declaring in child

My use case:
I have a large number of POJO models that are different types of requests for a third-party API. All of them have several common fields and a couple unique ones.
I was hoping to build something that conceptually looks like this
class RequestBase(
val commonField1: String,
val commonField2: String,
...
val commonFieldX: String
)
class RequestA(
val uniqueFieldA: String
): RequestBase()
class RequestB(
val uniqueFieldB: String
): RequestBase()
fun main() {
val requestA = RequestA(
commonField1 = "1",
commonField2 = "2",
...
uniqueFieldA = "A"
)
}
I can of course override the common fields in every child request and then pass them to the parent constructor, but this ends up producing a lot of boilerplate code and bloats the model. Are there any options I can explore here?
Notice that what you are doing in the parentheses that follow a class declaration is not "declaring what properties this class has", but "declaring the parameters of this class' primary constructor". The former is just something you can do "along the way", by adding var or val.
Each class can have its own primary constructor that take any number and types of parameters that it likes, regardless of what class its superclass is. Therefore, it is not unreasonable to have to specify all the parameters of the constructor:
open class RequestBase(
val commonField1: String,
val commonField2: String,
...
val commonFieldX: String
)
class RequestA(
// notice that the parameters for the inherited properties don't have the
// "val" prefix, because you are not declaring them in the subclass again.
// These are just constructor parameters.
commonField1: String,
commonField2: String,
...
commonFieldX: String,
val uniqueFieldA: String,
): RequestBase(
commonField1,
commonField2,
...
commonFieldX,
)
If you find this unpleasant, there are a bunch of ways to work around this.
One way is to use composition and delegation - create an interface having the common properties. The specific requests' primary constructors will take a RequestBase and their unique properties, and implement the interface by delegating to the RequestBase:
interface Request {
val commonField1: String
val commonField2: String
val commonFieldX: String
}
open class RequestBase(
override val commonField1: String,
override val commonField2: String,
override val commonFieldX: String
): Request
class RequestA(
val requestBase: RequestBase,
val uniqueField: String
): Request by requestBase
This allows you to access someRequestA.commonFieldX directly, without doing someRequestA.requestBase.commonFieldX, but to create a RequestA, you need to create a RequestBase first:
RequestA(
RequestBase(...),
uniqueField = ...
)
Another way is to change your properties to vars, give them default values, and move them out of the constructor parameters:
open class RequestBase {
var commonField1: String = ""
var commonField2: String = ""
var commonFieldX: String = ""
}
class RequestA: RequestBase() {
var uniqueField: String = ""
}
Then to create an instance of RequestA, you would just call its parameterless constructor, and do an apply { ... } block:
RequestA().apply {
commonField1 = "foo"
commonField2 = "bar"
commonFieldX = "baz"
uniqueField = "boo"
}
The downside of this is of course that the properties are all mutable, and you have to think of a default value for every property. You might have to change some properties to nullable because of this, which might not be desirable.
You can't do it with constructors of base class. Without constructors it's possible:
open class RequestBase {
lateinit var commonField1: String
lateinit var commonField2: String
...
lateinit var commonFieldX: String
}
class RequestA(
val uniqueFieldA: String
): RequestBase()
class RequestB(
val uniqueFieldB: String
): RequestBase()
fun main() {
val requestA = RequestA(
uniqueFieldA = "A"
).apply {
commonField1 = "1"
commonField2 = "2"
...
commonFieldX = "X"
}
}

Map Key Values to Dataclass in Kotlin

how can I set properties of a dataclass by its name. For example, I have a raw HTTP GET response
propA=valueA
propB=valueB
and a data class in Kotlin
data class Test(var propA: String = "", var propB: String = ""){}
in my code i have an function that splits the response to a key value array
val test: Test = Test()
rawResp?.split('\n')?.forEach { item: String ->
run {
val keyValue = item.split('=')
TODO
}
}
In JavaScript I can do the following
response.split('\n').forEach(item => {
let keyValue = item.split('=');
this.test[keyValue[0]] = keyValue[1];
});
Is there a similar way in Kotlin?
You cannot readily do this in Kotlin the same way you would in JavaScript (unless you are prepared to handle reflection yourself), but there is a possibility of using a Kotlin feature called Delegated Properties (particularly, a use case Storing Properties in a Map of that feature).
Here is an example specific to code in your original question:
class Test(private val map: Map<String, String>) {
val propA: String by map
val propB: String by map
override fun toString() = "${javaClass.simpleName}(propA=$propA,propB=$propB)"
}
fun main() {
val rawResp: String? = """
propA=valueA
propB=valueB
""".trimIndent()
val props = rawResp?.split('\n')?.map { item ->
val (key, value) = item.split('=')
key to value
}?.toMap() ?: emptyMap()
val test = Test(props)
println("Property 'propA' of test is: ${test.propA}")
println("Or using toString: $test")
}
This outputs:
Property 'propA' of test is: valueA
Or using toString: Test(propA=valueA,propB=valueB)
Unfortunately, you cannot use data classes with property delegation the way you would expect, so you have to 'pay the price' and define the overridden methods (toString, equals, hashCode) on your own if you need them.
By the question, it was not clear for me if each line represents a Test instance or not. So
If not.
fun parse(rawResp: String): Test = rawResp.split("\n").flatMap { it.split("=") }.let { Test(it[0], it[1]) }
If yes.
fun parse(rawResp: String): List<Test> = rawResp.split("\n").map { it.split("=") }.map { Test(it[0], it[1]) }
For null safe alternative you can use nullableString.orEmpty()...

How to change a kotlin private val using reflection?

I can access a private val value using reflection as below
fun main() {
val mainClass = MainClass()
val f = MainClass::class.memberProperties.find { it.name == "info" }
f?.let {
it.isAccessible = true
val w = it.get(mainClass) as String
println(w)
}
}
class MainClass {
private val info: String = "Hello"
}
But if I want to change info, how could I do it using reflection?
Answer
In short, you have to use Java reflection APIs in this case, and here is how to do it:
fun main() {
val mainClass = MainClass()
val f = MainClass::class.java.getDeclaredField("info")
f.isAccessible = true
f.set(mainClass, "set from reflection")
mainClass.printInfo() // Prints "set from reflection"
}
class MainClass {
private val info: String = "Hello"
fun printInfo() = println(info)
}
Reason for using Java reflection APIs
It is not possible to do with Kotlin reflection APIs since no setter code is generated for a read-only (val) property. So to change it, we need to use Java reflection APIs which is more low-level. First, we use Tools -> Kotlin -> Show Kotlin Bytecode to see what the generated bytecode looks like. Then we see this:
// ================MainClass.class =================
// class version 50.0 (50)
// access flags 0x31
public final class MainClass {
// access flags 0x12
private final Ljava/lang/String; info = "Hello"
// ...
i.e that the info fields in the MainClass Kotlin class causes the compiler to emit JVM code for a regular MainClass Java class with a final String info field. So to change it, we can use Java reflection APIs, as in the code above.
Kotlin reflection API attempt
If the field would have been private var you would be able to Use Kotlin reflection APIs like this:
f?.let {
val mutableProp = it as KMutableProperty<*>
it.isAccessible = true
mutableProp.setter.call(mainClass, "set from Kotlin reflection")
val w = it.get(mainClass) as String
println(w)
}
but if you try this with private val you will get the below exception
Exception in thread "main" java.lang.ClassCastException: class kotlin.reflect.jvm.internal.KProperty1Impl cannot be cast to class kotlin.reflect.KMutableProperty (kotlin.reflect.jvm.internal.KProperty1Impl and kotlin.reflect.KMutableProperty are in unnamed module of loader 'app')
at MainKt.main(main.kt:107)
at MainKt.main(main.kt)
since no setter code is generated for val fields, and thus the info property will have a Kotlin Reflection API type of KProperty and not KMutableProperty.
This is working solution
import kotlin.reflect.KMutableProperty
import kotlin.reflect.full.memberProperties
class MySolution {
var name = ""
var email = ""
}
#Suppress("UNCHECKED_CAST")
fun main() {
//Dummy Result Set
val rs = mapOf<String,String>("name" to "My Name", "email" to "My Email")
val mySol = MySolution();
val xyzMp = MySolution::class.memberProperties;
xyzMp.forEach { mp ->
val prop = mp as KMutableProperty<String>
prop.setter.call(mySol, rs[mp.name])
}
println(mySol.name)
println(mySol.email)
println("*****Enjoy*******")
output
My Name
My Email
**** *Enjoy*******

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)
}