I have a data class in my project. It is very very very important to keep this class always as "data" and all his properties must be data classes too (or primitives).
Is there way to figure out programmatically that some class in project is still data-class and it consists only of data-classes which all consists of data-classes etc..?
You have to use Kotlin Reflection. You can check KClass.isData and Class.isPrimitive methods. It will be something like that I guess:
fun isSafe(kClass: KClass<*>): Boolean {
if (!(kClass.isData || kClass.java.isPrimitive)) {
return false
}
for (property in kClass.memberProperties) {
val field = property.javaField ?: continue
if (!isSafe(field.type.kotlin)) {
return false
}
}
return true
}
class Test1
data class Test2(val v: Int, private val t: Test3)
data class Test3(val v: Int, private val t: Int)
data class Test4(val t: Test1)
fun main() {
println(isSafe(Test2::class)) // print true
println(isSafe(Test4::class)) // print false
}
Related
I have a situation where I need to create a copy of data class object. I don't know in advance which of the many data classes I have will come in into the function. I do know, however, that only data classes will be used as input to this function.
This is what didn't work:
fun doSomething(obj: Any): Any {
obj.copy(...) // <- there's no 'copy' on Any
...
}
This is what I really like to do:
fun doSomething(obj: KAnyDataClass): KAnyDataClass {
obj.copy(...) // <- works, data classes have a 'copy' method
...
}
I'm not a Kotlin developer, but it looks like the language does not support dynamic dispatch or traits. You might find success with the dynamic type, which just turns off the type-checker so it won't yell at you for using a method that it doesn't know about. However this opens up the possibility of a runtime error if you pass an argument that actually doesn't have that method.
There is no class or interface for data classes, but we know from the documentation of data classes that there are derived functions componentN and copy in each data class.
We can use that knowledge to write an abstract copy method that calls the copy method of a given arbitrary data class using reflection:
fun <T : Any> copy(data: T, vararg override: Pair<Int, Any?>): T {
val kClass = data::class
if (!kClass.isData) error("expected a data class")
val copyFun = kClass.functions.first { it.name == "copy" }
checkParameters(override, kClass)
val vals = determineComponentValues(copyFun, kClass, override, data)
#Suppress("UNCHECKED_CAST")
return copyFun.call(data, *vals) as T
}
/** check if override of parameter has the right type and nullability */
private fun <T : Any> checkParameters(
override: Array<out Pair<Int, Any?>>,
kClass: KClass<out T>
) {
override.forEach { (index, value) ->
val expectedType = kClass.functions.first { it.name == "component${index + 1}" }.returnType
if (value == null) {
if (!kClass.functions.first { it.name == "component${index + 1}" }.returnType.isMarkedNullable) {
error("value for parameter $index is null but parameter is not nullable")
}
} else {
if (!expectedType.jvmErasure.isSuperclassOf(value::class))
error("wrong type for parameter $index: expected $expectedType but was ${value::class}")
}
}
}
/** determine for each componentN the value from override or data element */
private fun <T : Any> determineComponentValues(
copyFun: KFunction<*>,
kClass: KClass<out T>,
override: Array<out Pair<Int, Any?>>,
data: T
): Array<Any?> {
val vals = (1 until copyFun.parameters.size)
.map { "component$it" }
.map { name -> kClass.functions.first { it.name == name } }
.mapIndexed { index, component ->
override.find { it.first == index }.let { if (it !== null) it.second else component.call(data) }
}
.toTypedArray()
return vals
}
Since this copy function is generic and not for a specific data class, it is not possible to specify overloads in the usual way, but I tried to support it in another way.
Let's say we have a data class and element
data class Example(
val a: Int,
val b: String,
)
val example: Any = Example(1, "x")
We can create a copy of example with copy(example) that has the same elements as the original.
If we want to override the first element, we cannot write copy(example, a = 2), but we can write copy(example, 0 to 2), saying that we want to override the first component with value 2.
Analogously we can write copy(example, 0 to 3, 1 to "y") to specify that we want to change the first and the second component.
I am not sure if this works for all cases since I just wrote it, but it should be a good start to work with.
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.
Using reflection I can iterate through my attributes:
data class AnotherDataClass(
val property: String,
val dataClass: DataClass
)
AnotherDataClass::class.memberProperties.filter {
return it::class.isData
}
But it::class.isData is always false since the type of it is jvm.internal.KProperty1Impl. Is there a way to check if this class is a data class?
Use
AnotherDataClass::class.memberProperties.filter {
(it.returnType.classifier as? KClass<*>)?.isData ?: false
}
In Kotlin if I define a method on an anonymous object, sometimes I am able to access it, while other times I am not. This seems to have something to do with scoping rules, but I am not sure what.
In the code example below, the access to example3.field.method() will cause a compilation error. Interestingly, example2.field.method() compiles just fine.
What could be the explanation for the below behaviour?
class Example3 {
val field = object {
fun method() {}
}
}
fun showcase() {
val example1 = object {
fun method() {}
}
example1.method()
println(example1::class.qualifiedName)
class Example2 {
val field = object {
fun method() {}
}
}
val example2 = Example2()
example2.field.method()
println(example2::class.qualifiedName)
val example3 = Example3()
// example3.field.method() // won't compile
println(example3::class.qualifiedName)
}
From docs Object Expressions and Declarations:
Note that anonymous objects can be used as types only in local and
private declarations. If you use an anonymous object as a return type
of a public function or the type of a public property, the actual type
of that function or property will be the declared supertype of the
anonymous object, or Any if you didn't declare any supertype. Members
added in the anonymous object will not be accessible.
Demonstrated in code sample below:
class Example4{
val publicObj = object{
val x = 1
}
private val privateObj = object{
val x = 2
}
fun showcase(){
val scopedObj = object{
val x = 3
}
println(publicObj.x) // ERROR : unresolved reference: x
println(privateObj.x) // OK
println(scopedObj.x) // OK
}
}
Pawel gave the correct answer to your question, pointing to the documentation:
the actual type of that function or property will be the declared supertype of the anonymous object, or Any if you didn't declare any supertype.
But just adding that if you really need to access example3.field.method() you could declare a supertype to field in Example3:
interface MyInterface {
fun method()
}
class Example3 {
val field = object: MyInterface {
override fun method() {}
}
}
fun main() {
val example3 = Example3()
example3.field.method()
}
I am aware that in Kotlin classes will have an equals and hashcode created automatically as follows:
data class CSVColumn(private val index: Int, val value: String) {
}
My question is, is there a way to have the implementation just use one of these properties (such as index) without writing the code yourself. What was otherwise a very succinct class now looks like this:
data class CSVColumn(private val index: Int, val value: String) {
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (javaClass != other?.javaClass) {
return false
}
other as CSVColumn
if (index != other.index) {
return false
}
return true
}
override fun hashCode(): Int {
return index
}
}
In Java with Lombok, I can do something like:
#Value
#EqualsAndHasCode(of="index")
public class CsvColumn {
private final int index;
private final String value;
}
Would be cool if there were a way to tell Kotlin something similar.
From the Data Classes documentation you get:
Note that the compiler only uses the properties defined inside the primary constructor for the automatically generated functions. To exclude a property from the generated implementations, declare it inside the class body
So you have to implement equals() and hashCode() manually or with the help of a Kotlin Compiler Plugin.
You can't do something like this for data classes, they always generate equals and hashCode the same way, there's no way to provide them such hints or options.
However, they only include properties that are in the primary constructor, so you could do this for them to only include index:
data class CSVColumn(private val index: Int, value: String) {
val value: String = value
}
... except you can't have parameters in the primary constructor that aren't properties when you're using data classes.
So you'd have to somehow introduce a secondary constructor that takes two parameters, like this:
class CSVColumn private constructor(private val index: Int) {
var value: String = ""
constructor(index: Int, value: String) : this(index) {
this.value = value
}
}
... but now your value property has to be a var for the secondary constructor to be able to set its value.
All this to say that it's probably not worth trying to work around it. If you need an non-default implementation for equals and hashCode, data classes can't help you, and you'll need to implement and maintain them manually.
Edit: as #tynn pointed out, a private setter could be a solution so that your value isn't mutable from outside the class:
class CSVColumn private constructor(private val index: Int) {
var value: String = ""
private set
constructor(index: Int, value: String) : this(index) {
this.value = value
}
}
I wrote a little utility called "stem", which allows you to select which properties to consider for equality and hashing. The resulting code is as small as it can get with manual equals()/hashCode() implementation:
class CSVColumn(private val index: Int, val value: String) {
private val stem = Stem(this, { index })
override fun equals(other: Any?) = stem.eq(other)
override fun hashCode() = stem.hc()
}
You can see its implementation here.
I guess that we have to write equals()/hashCode() manually for now.
https://discuss.kotlinlang.org/t/automatically-generate-equals-hashcode-methods/3779
It is not supported and is planning to be, IMHO.
I guess that we have to write equals()/hashCode() manually for now. https://discuss.kotlinlang.org/t/automatically-generate-equals-hashcode-methods/3779
It is not supported and is planning to be, IMHO.
Below are some reference which may be helpful.
https://discuss.kotlinlang.org/t/how-does-kotlin-implement-equals-and-hashcode/940
https://kotlinlang.org/docs/reference/data-classes.html
https://medium.com/#appmattus/effective-kotlin-item-11-always-override-hashcode-when-you-override-equals-608a090aeaed
See the following performance optimized way (with the use of value classes and inlining) of implementing a generic equals/hashcode for any Kotlin class:
#file:Suppress("EXPERIMENTAL_FEATURE_WARNING")
package org.beatkit.common
import kotlin.jvm.JvmInline
#Suppress("NOTHING_TO_INLINE")
#JvmInline
value class HashCode(val value: Int = 0) {
inline fun combineHash(hash: Int): HashCode = HashCode(31 * value + hash)
inline fun combine(obj: Any?): HashCode = combineHash(obj.hashCode())
}
#Suppress("NOTHING_TO_INLINE")
#JvmInline
value class Equals(val value: Boolean = true) {
inline fun combineEquals(equalsImpl: () -> Boolean): Equals = if (!value) this else Equals(equalsImpl())
inline fun <A : Any> combine(lhs: A?, rhs: A?): Equals = combineEquals { lhs == rhs }
}
#Suppress("NOTHING_TO_INLINE")
object Objects {
inline fun hashCode(builder: HashCode.() -> HashCode): Int = builder(HashCode()).value
inline fun hashCode(vararg objects: Any?): Int = hashCode {
var hash = this
objects.forEach {
hash = hash.combine(it)
}
hash
}
inline fun hashCode(vararg hashes: Int): Int = hashCode {
var hash = this
hashes.forEach {
hash = hash.combineHash(it)
}
hash
}
inline fun <T : Any> equals(
lhs: T,
rhs: Any?,
allowSubclasses: Boolean = false,
builder: Equals.(T, T) -> Equals
): Boolean {
if (rhs == null) return false
if (lhs === rhs) return true
if (allowSubclasses) {
if (!lhs::class.isInstance(rhs)) return false
} else {
if (lhs::class != rhs::class) return false
}
#Suppress("unchecked_cast")
return builder(Equals(), lhs, rhs as T).value
}
}
This allows you to write a equals/hashcode implementation as follows:
data class Foo(val title: String, val bytes: ByteArray, val ignore: Long) {
override fun equals(other: Any?): Boolean {
return Objects.equals(this, other) { lhs, rhs ->
combine(lhs.title, rhs.title)
.combineEquals { lhs.bytes contentEquals rhs.bytes }
}
}
override fun hashCode(): Int {
return Objects.hashCode(title, bytes.contentHashCode())
}
}