I need to check if any variables inside of my data class are null. To do this I need retrieve them first but I can't access them directly (e.g. myDataClass.name) because I need it to be generic. Is there a way to access these variables without directly naming them. For example, like accessing a member of an array (myArray[0]).
The mechanism you're looking for is called "reflection" and it allows to introspect objects at runtime. You'll find a lot of information on the internet, but just to give you a link you may want to check this answer.
In your case you could do something like this:
data class MyDataClass(
val first: String?,
val second: String?,
val third: Int?
)
fun main() {
val a = MyDataClass("firstValue", "secondValue", 1)
val b = MyDataClass("firstValue", null, null)
printProperties(a)
printProperties(b)
}
fun printProperties(target: MyDataClass) {
val properties = target::class.memberProperties
for (property in properties) {
val value = property.getter.call(target)
val propertyName = property.name
println("$propertyName=$value")
}
}
Note that for this code to work you must add kotlin-reflect package as a dependency.
Related
Preface: Google+SO+docu search did not appear to give relevant information.
Domain Model:
My domain model tries to picture an ProductionPlan, containing a List of Machines.
Each Machine has a list of chained jobs, thus a job which has a getNextEntry():Job Method, creating a list of jobs.
I tried to solve this problem with chained PlanningVariables, but apparently do not understand the concept of chainedVariables/shadowVariables/anchorVariables.
With my understanding, all jobs are getting chained and the anchorShadowVariable points to the beginning of the list, thus the machine.
To implement chaining, Job and Machine need to implement an interface or extend superclass, thus i created ChainSuperClass. Additionally i do not know if i have to overwrite getter/setter to set Annotations in Machine-Class, my guess was that because Machine extends ChainSuperClass, those Annotations carry over.
Edit: Kotlin specific improvements are also appreciated.
The full error log of my code execution is now:
Exception in thread "main" java.lang.IllegalArgumentException: The entityClass (class optaplanner.productionPlan.domain.ChainSuperClass) has a InverseRelationShadowVariable annotated property (nextEntry) which does not return a Collection with sourceVariableName (machine) which is not chained. Only a chained variable supports a singleton inverse.
ChainSuperClass:
#PlanningEntity
abstract class ChainSuperClass {
#PlanningId
open val id = Random.nextInt().toString()
#InverseRelationShadowVariable(sourceVariableName = "machine")
abstract fun getNextEntry(): Job?
abstract fun setNextEntry(job: Job)
}
Job:
#PlanningEntity
class Job(
val jobType: JobType,
val itemNumber: String,
val orderNumber: String,
val setupTime: Int,
val productionTime: Int
) : ChainSuperClass() {
#AnchorShadowVariable(sourceVariableName = "machine")
var machine: Machine? = null
private var nextEntry: Job? = null
#PlanningVariable(
valueRangeProviderRefs = ["jobList"],
graphType = PlanningVariableGraphType.CHAINED
)
override fun getNextEntry(): Job? {
return nextEntry
}
override fun setNextEntry(job: Job) {
this.nextEntry = nextEntry
}
}
Machine:
class Machine(override val id: String, val jobTypes: List<JobType>) : ChainSuperClass() {
private var nextEntry: Job? = null
override fun setNextEntry(job: Job) {
this.nextEntry = job
}
override fun getNextEntry(): Job? {
return nextEntry!!
}
}
I think the most important thing to realize with chained variables is this: When you have an entity, say Job A and solver assigns a value (job/machine) to its variable, it's not like the chain is being built forward starting from Job A. It's the other way around. By assigning a value to Job A's planning variable, Job A gets connected at the end of an existing chain.
Please take a look at the documentation to find more details about chaining and examples of valid chains.
By having understood this it should be clear that Job's planning variable name should be something like previousJobOrMachine (you'll probably want something simpler, for example previousStep) whereas the nextJob property is an inverse relation shadow variable derived from that (so when Job X gets connected to an existing chain ending with Job C by assigning Job X.previousStep=Job C, an inverse relation is established automatically: Job C.nextJob=Job X).
Based on that information, your model should look more like this:
#PlanningEntity
abstract class ChainSuperClass {
#PlanningId
open val id = Random.nextInt().toString()
// changed sourceVariableName to point to the planning variable
#InverseRelationShadowVariable(sourceVariableName = "previousStep")
abstract fun getNextEntry(): Job?
abstract fun setNextEntry(job: Job)
}
#PlanningEntity
class Job(
val jobType: JobType,
val itemNumber: String,
val orderNumber: String,
val setupTime: Int,
val productionTime: Int
) : ChainSuperClass() {
// changed sourceVariableName to point to the planning variable
#AnchorShadowVariable(sourceVariableName = "previousStep")
var machine: Machine? = null
// added planning variable
private var previousStep: ChainSuperClass? = null
private var nextEntry: Job? = null
#PlanningVariable(
// added machineList value range provider
valueRangeProviderRefs = ["jobList", "machineList"],
graphType = PlanningVariableGraphType.CHAINED
)
// getter for the new planning variable
fun getPreviousStep(): ChainSuperClass {
return previousStep
}
override fun getNextEntry(): Job? {
return nextEntry
}
override fun setNextEntry(job: Job) {
this.nextEntry = nextEntry
}
}
Notice that I added machineList as the source of possible values of the previousStep planning variable as previous step might be either a Job at the end of a non-empty chain or a Machine representing an empty chain.
Your domain is very similar to the vehicle routing domain, where you can see a working example of chaining. With good understanding of chaining principles you should be able to replicate it on your domain.
I have an immutable object:
class Foo(
val name: String,
val things: List<Thing>
)
A third party lib creates the Foo object with some 'null' Thing objects.
I am creating a new object:
val foo = thirdPartyGetFoo()
val filteredFoo = Foo(foo.name, foo.things.filterNotNull())
That works, however AndroidStudio greys out the filterNotNull function call and presents a warning:
Useless call on collection type: The inspection reports filter-like
calls on already filtered collections.
Is this the right way to filter that list? Should I ignore the warning or is there a better way?
You do not specify what library creates the object with nulls. Some deserialization libraries can use static factory methods which you could configure, and then have the factory method strip the null. For example, if this were Jackson you would simply:
class Foo(val name: String, val things: List<Thing>) {
companion object {
#JsonCreator
#JvmName("createFromNullable")
fun create(name: String, things: List<Thing?>) = Foo(name, things.filterNotNull())
fun create(name: String, things: List<Thing>) = Foo(name, things)
}
}
Then...
val goodFoo = jacksonObjectMapper().readValue<Foo>(someJsonWithNulls)
Maybe your library has options that are similar?
If not, and you don't have 100 of these things with this problem, I would probably create a temporary class to hold the results and convert that to the final class:
open class FooNullable(val name: String, open val things: List<Thing?>) {
open fun withoutNulls(): Foo = Foo(name, things.filterNotNull())
}
class Foo(name: String, override val things: List<Thing>) : FooNullable(name, things) {
override fun withoutNulls(): Foo = this
}
Then you can deserialize into FooNullable and just call withoutNulls() to get the other flavor that is clean. And if you accidentally call it on one without nulls already, it just does nothing.
val goodFoo = Foo("", emptyList<Thing>())
val alsoGoodFoo = goodFoo.withoutNulls() // NOOP does nothing
val badFoo = thirdPartyGetFoo()
val betterFoo = badFoo.withoutNulls() // clean up the instance
val safeFoo = thirdPartyGetFoo().withoutNulls() // all at once!
Not the cleanest, but does work. The downsides is this second step, although it looks like you were already planning on doing that anyway. But this model is safer than what you proposed since you KNOW which type of object you have and therefore you continue to be typesafe and have the compiler helping you avoid a mistake.
You don't have to use inheritance as in the above example, I was just trying to unify the API in case there was a reason to have either version in hand and know which is which, and also act upon them in a similar way.
I can use Code A to change the value of val a .
In my mind that val property is thread-safe, but it seems that it's not thread -safe in Code A,
it's just like var property, any thread can change the variable aImpl, different thread maybe get different value of val a
Code A
var aImpl = 0
val a: Int get() = aImpl
fun seta(){
aImpl=5
}
You fail to make a clear distinction between a val with and without a custom getter. If you lump those two together, like in your question, then val is not thread-safe; however Kotlin does make this distinction, as you can observe in this example:
val simpleVal: Int? = 3
val customVal: Int? get() = simpleVal
fun main(args: Array<String>) {
if (simpleVal != null) {
println(simpleVal + 1)
}
if (customVal != null) {
println(customVal + 1) // ERROR!
}
}
Error:(12, 21) Kotlin: Smart cast to 'Int' is impossible, because 'customVal' is a property that has open or custom getter
The smart cast is not allowed, among other reasons, due to the potential of another thread mutating the result of the custom get() call.
Therefore:
A simple val is thread-safe;
A val with a custom or open getter is not (necessarily) thread-safe.
Of course not. It will work as a dynamic getter for aImpl, not as property.
And aImpl isn't #Volatile, so there is no any guarantees of thread-safety.
The Kotlin documentation describes cloning only in accessing Java and in enum class. In latter case clone is just throwing an exception.
So, how would I / should I clone arbitrary Kotlin object?
Should I just use clone() as in Java?
For a data class, you can use the compiler-generated copy() method. Note that it will perform a shallow copy.
To create a copy of a collection, use the toList() or toSet() methods, depending on the collection type you need. These methods always create a new copy of a collection; they also perform a shallow copy.
For other classes, there is no Kotlin-specific cloning solution. You can use .clone() if it suits your requirements, or build a different solution if it doesn't.
You can use Gson library to convert the original object to a String and then convert back that String to an actual Object type, and you'll have a clone. Although this is not the intended usage of the Gson library which is actually used to convert between JSON and other object types, but I have devised this method to solve the cloning problem in many of my Kotlin based Android applications.
See my example. Put this function in the class/model of which you want to create a clone. In my example I'm cloning an Animal type object so I'll put it in the Animal class
class Animal{
fun clone(): Animal
{
val stringAnimal = Gson().toJson(this, Animal::class.java)
return Gson().fromJson<Animal>(stringAnimal, Animal::class.java)
}
}
Then use it like this:
val originalAnimal = Animal()
val clonedAnimal = originalAnimal.clone()
A Kotlin data class is easy to clone using .copy()
All values will be shallow copied, be sure to handle any list/array contents carefully.
A useful feature of .copy() is the ability to change any of the values at copy time. With this class:
data class MyData(
val count: Int,
val peanuts: Int?,
val name: String
)
val data = MyData(1, null, "Monkey")
You could set values for any of the properties
val copy = data.copy(peanuts = 100, name = "Elephant")
The result in copy would have values (1, 100, "Elephant")
If the class you are trying to clone does not implement Cloneable or is not a data class and is a part of an outside library, you can create an extension method that returns a new instance. For example:
class Person {
var id: String? = null
var name: String? = null
}
fun Person.clone(): Person {
val person = Person()
person.id = id
person.name = name
return person
}
It requires to implement Cloneable for your class then override clone() as a public like:
public override fun clone(): Any {<your_clone_code>}
https://discuss.kotlinlang.org/t/how-to-use-cloneable/2364/3
fun <T : Any> clone (obj: T): T {
if (!obj::class.isData) {
println(obj)
throw Error("clone is only supported for data classes")
}
val copy = obj::class.memberFunctions.first { it.name == "copy" }
val instanceParam = copy.instanceParameter!!
return copy.callBy(mapOf(
instanceParam to obj
)) as T
}
I've voted for #yole for nice answer, but other ways if you don't (or can't) use data class. You can write helper method like this:
object ModelHelper {
inline fun <reified T : Serializable> mergeFields(from: T, to: T) {
from::class.java.declaredFields.forEach { field ->
val isLocked = field.isAccessible
field.isAccessible = true
field.set(to, field.get(from))
field.isAccessible = isLocked
}
}
}
So you can "copy" instance A into B by:
val bInstance = AClassType()
ModelHelper.mergeFields(aInstance, bInstance)
Sometimes, I use this way to merge data from many instances into one object which value available (not null).
Here is a consistent solution that works for any object type:
Kotlin's Array data structure provides a clone() method that can be used to clone the contents of the array:
val a = arrayOf(1)
//Prints one object reference
println(a)
//Prints a different object reference
println(a.clone())
As of Kotlin 1.3, the clone method has been supported on all major targets, so it should be usable across platforms.
It's also possible to clone an object using kotlinx.serialization
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
#Serializable
class A
{
val name: String = "Cloneable class A"
fun clone(): A {
val json = Json(JsonConfiguration.Stable)
val jsonStr = json.stringify(serializer(), this)
return json.parse(serializer(), jsonStr)
}
}
Collection copying functions, such as toList(), toMutableList(), toSet() and others, create a snapshot of a collection at a specific moment. Their result is a new collection of the same elements. If you add or remove elements from the original collection, this won't affect the copies. Copies may be changed independently of the source as well.
val alice = Person("Alice")
val sourceList = mutableListOf(alice, Person("Bob"))
val copyList = sourceList.toList()
sourceList.add(Person("Charles"))
alice.name = "Alicia"
println("First item's name is: ${sourceList[0].name} in source and ${copyList[0].name} in copy")
println("List size is: ${sourceList.size} in source and ${copyList.size} in copy")
First item's name is: Alicia in source and Alicia in copy
List size is: 3 in source and 2 in copy
Kotlin Official Document
Sample Screenshot
i'm new in kotlin and i want to know if we can transform a content value at initialisation : with this example :
#Document
data class Category(
#Id val id: Id? = null,
val label: String
)
Category is a document (entity for mongodb) and when i'm instanciating this object, i want to transform label property in uppercase. How can i do that to stay idiomatic with the language ? The point is to keep the immutable properties of the val keyword.
val categ = Category(label = "Test")
println(categ.label) // --> TEST
Thanks.
You can encapsulate the "upperCasing" into a factory:
data class Category constructor(val label: String) {
init {
if (label != label.toUpperCase()) {
throw IllegalStateException("Label must be uppercase")
}
}
companion object {
fun createInstance(str: String) = Category(str.toUpperCase())
}
}
The init block ensures, that clients don't create unwanted instances with non-upper labels (which should be documented).
Create an instance like this:
val instance = Category.createInstance("xy")
You might want to make explicit that you do transformations if the parameter is not upper case already by naming the factory accordingly, e.g. withTransformedLabel or simply add some documentation ;-)