Kotlin - keeping a list of properties without repeating class name - kotlin

In Kotlin, I keep a list of the names for a subset of the properties in class. Example:
listOf(
Car::model.name,
Car::make.name,
Car::company.name,
Car::year.name,
Car::power.name,
Car::cylinders.name,
Car::gearType.name,
Car::seats.name,
)
This can be shortened as follows, so that Car and .name are not repeated per list element.
with (Car()) {
listOf(
::model,
::make,
::company,
::year,
::power,
::cylinders,
::gearType,
::seats,
)
}.map { it.name }
I don't like this approach though, because it creates an object from the class. This may not be possible in all situations (private constructors, non-default constructor with many dependencies, or the object may be expensive to construct).
Is there a better solution?

You need to check why the it.name is needed. You're using a reflectional way to get the name of all/some fields. First of all you're having a reflective property element in your hands and then you just want to get the properties name.
If you want to shorten the mapping (+1 iteration) you can simply add .name already in the listOf() part.
Please notice that this is a very static way. If you want to have all fields OR/and properties you can use this as well:
Car::class.members // Members and Fields are different!!
Or you access the generated java class fields: Car::class.java.declaredFields and map it to list.
Shortest example for everything: Car::class.java.declaredFields.map{ it.name }

Related

Kotlin modifying dataclass object key from map changes the reference after modifying variable

I have a MutableMap that its keys are objects from a DataClass (User dataclass), and the values are arrays from other Dataclass (Dog dataclass). If i have a variable with a User object, and i put it in the MutableMap and i test if the map contains the User, it says that is true. But after putting the user in the MutableMap if i change one of the attributes of the User object using the variable that holds the User object, the Map says that it doesnt contains the user object.
This is an example
data class User(
var name: String,
var project: String,
)
data class Dog(
var kind: String
)
fun main(args: Array<String>) {
var mapUserDogs: MutableMap<User, MutableList<Dog>> = mutableMapOf()
var userSelected = User("name2", "P2")
mapUserDogs.put(
User("name1", "P1"),
mutableListOf(Dog("R1"), Dog("R2"))
)
mapUserDogs.put(
userSelected,
mutableListOf(Dog("R21"), Dog("R31"))
)
println(userSelected)
println(mapUserDogs.keys.toString())
println(mapUserDogs.contains(userSelected))
println(mapUserDogs.values.toString())
println("\n")
userSelected.name = "Name3"
println(userSelected)
println(mapUserDogs.keys.toString())
println(mapUserDogs.contains(userSelected))
println(mapUserDogs.values.toString())
}
The prints statements show this:
User(name=name2, project=P2)
[User(name=name1, project=P1), User(name=name2, project=P2)]
true
[[Dog(kind=R1), Dog(kind=R2)], [Dog(kind=R21), Dog(kind=R31)]]
User(name=Name3, project=P2)
[User(name=name1, project=P1), User(name=Name3, project=P2)]
false
[[Dog(kind=R1), Dog(kind=R2)], [Dog(kind=R21), Dog(kind=R31)]]
Process finished with exit code 0
But it doesn't make sense. Why the map says that it doesn't contains the user object if its clear that it still holds the reference to it after being modified?
User(name=Name3, project=P2)
[User(name=name1, project=P1), User(name=Name3, project=P2)]
The user in the keys collection was also changed when i modified the userSelected variable, so now the object has it attribute name as "Name3" in both the variable and in the Map keys, but it still says that it doesnt contains it.
What can i do so that i can change the attributes in the userSelected object and the Map still return true when using the "contains" method?. And doing the same process in reverse shows the same. If i get from the map the user and i modify it, the userVariable is also modified but if i later test if the map contains the userVariable, it says false.
What can i do so that i can change the attributes in the userSelected object and the Map still return true when using the "contains" method?
There is nothing you can do that preserves both your ability to look up the entry in the map and your ability to modify the key.
Make your data class immutable (val instead of var, etc.), and when you need to change a mapping, remove the old key and put in the new key. That's really the only useful thing you can do.
To add to Louis Wasserman's correct answer:
This is simply the way that maps work in Kotlin: their contract requires that keys don't change significantly once stored. The docs for java.util.Map* spell this out:
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.
The safest approach is to use only immutable objects as keys. (Note that not just the object itself, but any objects it references, and so on, must all be immutable for it to be completely safe.)
You can get away with mutable keys as long as, once the key is stored in the map, you're careful never to change anything that would affect the results of calling equals() on it. (This may be appropriate if the object needs some initial set-up that can't all be done in its constructor, or to avoid having both mutable and immutable variants of a class.) But it's not easy to guarantee, and leaves potential problems for future maintenance, so full immutability is preferable.
The effects of mutating keys can be obvious or subtle. As OP noticed, mappings may appear to vanish, and maybe later reappear. But depending on the exact map implementation, it may cause further problems such as errors when fetching/adding/removing unrelated mappings, memory leaks, or even infinite loops. (“The behaviour… is not specified” means that anything can happen!)
What can i do so that i can change the attributes in the userSelected object and the Map still return true when using the "contains" method?
What you're trying to do there is to change the mapping. If you store a map from key K1 to value V, and you mutate the key to hold K2, then you're effectively saying “K1 no longer maps to V; instead, K2 now maps to V.”
So the correct way to do that is to remove the old mapping, and then add the new one. If the key is immutable, that's what you have to do — but even if the key is mutable, you must remove the old mapping before changing it, and then add a new mapping after changing it, so that it never changes while it's stored in the map.
(* The Kotlin library docs don't address this, unfortunately — IMHO this is one of many areas in which they're lacking, as compared to the exemplary Java docs…)
That happens because data classes in Kotlin are compared by value, unlike regular classes which are compared by reference. When you use a data class as a key, the map gets searched for a User with the same string values for the name and project fields, not for the object itself in memory.
For example:
data class User(
var name: String,
var project: String,
)
val user1 = User("Daniel", "Something Cool")
val user2 = User("Daniel", "Something Cool")
println(user1 == user2) // true
works because, even though they are different objects (and thus different references), they have the same name and project values.
However, if I were to do this:
user1.name = "Christian"
println(user1 == user2) // false
the answer would be false because they don't share the same value for all of their fields.
If I made User a standard class:
class User(
var name: String,
var project: String,
)
val user1 = User("Daniel", "Something Cool")
val user2 = User("Daniel", "Something Cool")
println(user1 == user2) // false
the answer would be false because they are different references, even though they share the same values.
For your code to work the way you want, make User a regular class instead of a data class.
That's the key difference between regular classes and data classes: a class is passed by reference, a data class is passed by value. Data classes are nothing more than collections of values with (optionally) some methods attached to them, classes are individual objects.

Kotlin: get members of a data class by reflection in the order they have been defined

Assume the following simple example data class:
data class SomeDataClass(
var id: String,
var name: String,
var deleted: String
)
With the following code it is possible to get the properties (and set or get their values):
import kotlin.reflect.full.memberProperties
val properties = SomeDataClass::class.memberProperties
print(properties.map { it.name }) // prints: [deleted, id, name]
The map within the print statement will return a List with the name of the properties in alphabetical order. I need the list in the order they have been defined in the source code, in this case: [id, name, deleted].
It doesn't seem achievable purely through reflection. The only solution I could come up with is to use a helper class defining the order:
val SomeDataClass_Order = listOf("id", "name", "deleted")
This wouldn't be a problem for one or two classes, but it is for hundreds of data classes with the largest one having up to almost one hundred properties.
Any idea would be welcome. I do not need detailed code, rather hints (like parsing the source code, annotations, etc).
If all the properties are declared in the primary constructor, you could "cheat":
val propertyNames = SomeDataClass::class.primaryConstructor!!.parameters.map { it.name }
If you want the KPropertys:
val properties = propertyNames.map { name ->
SomeDataClass::class.memberProperties.find { it.name == name }
}
This unfortunately doesn't find the properties that are declared in the class body.
I don't know about other platforms, but on Kotlin/JVM, the order in which the backing fields for the properties are generated in the class file is not specified, and a quick experiment finds that the order (at least for the version of kotlinc that I'm using right now), the order is the same as the declaration order. So in theory, you could read the class file of the data class, and find the fields. See this related answer for getting the methods in order. Alternatively, you can use Java reflection, which also doesn't guarantee any order to the returned fields, but "just so happens" to return them in declaration order:
// not guaranteed, might break in the future
val fields = SomeDataClass::class.java.declaredFields.toList()
If you do want to get the properties declared inside the class body in order too, I would suggest that you don't depend on the order at all.

Recursively building a data class in Kotlin

I have am trying to create a recursive data class like so:
data class AttributeId (
val name: String,
val id: Int,
val children: List<AttributeId>?
)
The thing I'm struggling with now is building the data class by iterating over a source object.
How do I recursively build this object?? Is a data class the wrong solution here?
EDIT: Some more information about the Source object from which I want to construct my data class instance
The source object is a Java Stream that essentially* has the following shape:
public Category(final String value,
final Integer id,
final List<Category> children) {
this.value = value;
this.id = id;
this.children = children;
}
(For brevity the fields I don't care about have been removed from example)
I think I need to map over this stream and call a recursive function in order to construct the AttributeId data class, but my attempts seem to end in a stack overflow and a lot of confusion!
I don't think there's anything necessarily wrong with a data class that contains references to others.
There are certainly some gotchas.  For example:
If the list were mutable, or if its field was mutable (i.e. var rather than val), then you'd have to take care because its hashcode &c could change.
And if the chain of links could form a loop (i.e. you could follow the links and end up back at the original class), that could be very dangerous.  (E.g. calling a method such as toString() or hashCode() might either get stuck in an endless loop or crash the thread with a StackOverflowError.  You'd have to prevent that by overriding those methods to prevent them recursing.)  But that couldn't happen if the list and field were both immutable.
None of these issues are specific to data classes, though; a normal class could suffer the same issues (especially if you overrode methods like toString() or hashCode() without taking care).  So whether you make this a data class comes down to whether it feels like one: whether its primary purpose is to hold data, and/or whether the automatically-generated methods match how you want it to behave.
As Tenfour04 says, it depends what you're constructing these from.  If it naturally forms a tree structure, then this could be a good representation for it.
Obviously, you wouldn't be able to construct a parent before any of its children.  (In particular, the first instance you create would have to have either null or an empty list for its children.)  This would probably mean traversing the source in post-order.  The rest should fall out naturally from that.

Instantiate Kotlin class from string

I have a list of classes:
val availableClasses = listOf<Whatever>(
classA(),
classB(),
classC()
)
I am randomly selecting an item from this list using:
private var selection: Whatever = availableClasses.random()
Unfortunately, I think this approach is instantiating every class included in the list when the list is loaded.
I am hoping to work around this by replacing the list of classes with a list of strings:
val availableClasses = listOf<String>(
"classA",
"classB",
"classC"
)
Then once I have a selected string, instantiate only that one; something like:
private var selection: String = availableClasses.random()
// pseudo-code
val chosenClass = selection.toClass()
I can reference classes in Python using strings with the getattr function.
Is anything like this possible in Kotlin?
I'm also open to better approaches to this problem.
Instantiating classes by String name is more error-prone than using a constructor, because it relies on using a fully qualified, correctly spelled name, and the class having a specific constructor (either empty, or with specific arguments). So it can be done, but should be avoided when there are safer ways of doing it (ways where the compiler will give you an error if you're doing it wrong, instead of having an error occur only after you run the compiled program).
If I understand correctly, you want a list of classes that will only be instantiated one-at-a-time at random. One way to do this would be to make a list of class constructors.
val classConstructors = listOf<() -> Any>(
::ClassA,
::ClassB,
::ClassC
)
val randomInstantiatedClass = classConstructors.random()()

Confused about the following line #field:[Expose SerializedName("id")]

I have the following data class that will retrieve data from an API:
data class Users(
#field:[Expose SerializedName("id")]
val id: Int)
I am just wondering what the #field: means.
Normally, I have always done like this:
data class Users(
#Expose
#SerializedName("id")
val id: Int)
I understand the meaning of expose and serializedName.
Just a few questions:
My best guess would be for the #field:[] would be to take an array of annotations, instead of putting them on each line as in the second example?
But is the field a Kotlin keyword or an annotation as it's preceded by the #?
Where else could you use the #field?
The val id in your example is declaring several different things in one go:
A constructor parameter.
A property of the class, implemented as a getter method.
A backing field for the property.
So which of those does an annotation get applied to?  It defaults to the parameter, and that's what your second example does.
If you want it to apply to the field instead, as in your first example, you use the field: target.
(It usually applies to single annotations, but it can apply to an array of them, as in this case.)
For more details, see the link jonrsharpe provided: https://kotlinlang.org/docs/reference/annotations.html#annotation-use-site-targets
The field:, property:, file:, &c targets are only for use with annotations.  (field is also a keyword within getter/setter definitions.)