I know how to get an annotation of an enum value in Java.
However Kotlin has its own reflection library and I feel there should be a better way to do the job.
Could please anybody post an example.
Just to be specific let's define an enum class
enum class Enum {
#SerialName("constant")
Constant
}
I need a function f(e: Enum): String so that f(Enum.Constant) == "constant".
You can use a similar approach with java by getting the field by name and then reaching out to the annotation using annotation class.
So if you have below enum and annotation class definitions;
enum class Enum {
#SerialName("constant")
Constant
}
annotation class SerialName(val value: String)
Then you can define the below function and call it as shown below;
fun getAnnotationValue(enum:Enum):String = enum.declaringClass.getField(enum.name).getAnnotation(SerialName::class.java).value
fun main(args: Array<String>) {
println(getAnnotationValue(Enum.Constant))
}
Hope this helps.
I did only a little research here, but it appears there isn't support for this in the Kotlin reflection library. In fact, I discovered the linter doesn't even correctly suggest an annotation target of FIELD for your annotation if you give it one that doesn't work for Enum values, and instead incorrectly offers to automatically add a target of CLASS.
The problem is that Enum values are basically static member fields, which don't exist in Kotlin except in Enum classes. And the reflection classes don't seem to provide a way to access this special case.
I am struggling however to come up with a use case for Enum value annotations that can't be solved using properties in the Enum constructor(s).
enum class MyEnum(val someConstant: String? = null) {
SomeValue("myConstant")
}
Related
I would like to semantically constrain a map to only accept "data" class object types as the value in kotlin like so:
class Test(
val test : Int
)
data class Test2 (
val test : Int
)
fun test(map : Map<String, /* compile error on Test, but accept Test2 or any other data class */>) {
}
I'm mainly trying to do this so that I can keep everything in the map cloneable, but when I do this:
fun <T: Cloneable> test(map : Map<String, T>) {
// test clone
map.map { it.key.uuid to it.value.clone() } .toMap() // error on .clone() Cannot access 'clone': it is protected in 'Cloneable'
}
but I thought implementing the Cloneable interface made your clone method public? Essentially I'm looking for a compile time guarantee that all data is copyable in that method invocation, (is a primitive type, a data class that I can call .copy() on, or any object that has implemented Cloneable). Is my only option reflection and runtime assertions?
I thought implementing the Cloneable interface made your clone method public?
No, it's simply a marker interface, which tells the protected Object.clone() method not to throw a CloneNotSupportedException. In practice, classes that implement Cloneable will usually override clone() and make it public, but that's not necessary. And of course that's no help when you don't know the exact type!
The cloning mechanism was an early part of Java, and not very well-designed. (Effective Java calls it “a highly atypical use of interfaces and not one to be emulated”.) But it's still used, so we're stuck with it…
(See also these related answers.)
I don't know whether this is the best way or not, but how about you to use property like below.
SomeClass::class.isData
Kdoc says
true if this class is a data class.
Given:
enum class Foo {
BAR,
BAZ { fun qux(foo: Any) {} }
}
It appears to be legal code, but when I try to access qux() like so:
val foo = Foo.BAZ
foo.qux("blah")
...it doesn't work as the member function is not visible.
Is there a way I can access qux() outside of the enum class? If not, then what would be a use case for a custom member of an enum entry?
You can use it to implement interfaces and abstract methods, but since those body blocks are anonymous classes You won't be able to explicitly access per-entry values.
Perhaps what you're looking for can be achieved by using sealed classes?
How can I pass an annotion instance to a function?
I would like to call the java method AbstractCDI.select(Class<T> type, Annotation... qualifiers). But I don't know how to pass an annotation instance to this method.
Calling the constructor like
cdiInstance.select(MyClass::javaClass, MyAnnotation())
is not allowed and the #Annotation-Syntax cdiInstance.select(MyClass::javaClass, #MyAnnotation) is not allowed as parameter, too. How can I archive this?
When working with CDI you usually also have AnnotationLiteral available or at least you can implement something similar rather easy.
If you want to select a class using your annotation the following should do the trick:
cdiInstance.select(MyClass::class.java, object : AnnotationLiteral<MyAnnotation>() {})
Or you may need to implement your specific AnnotationLiteral-class if you require a specific value. In Java that would work as follows:
class MyAnnotationLiteral extends AnnotationLiteral<MyAnnotation> implements MyAnnotation {
private String value;
public MyAnnotationLiteral(String value) {
this.value = value;
}
#Override
public String[] value() {
return new String[] { value };
}
}
In Kotlin however, you can't implement the annotation and extend AnnotationLiteral or maybe I just did not see how (see also related question: Implement (/inherit/~extend) annotation in Kotlin).
If you rather want to continue using reflection to access the annotation then you should probably rather use the Kotlin reflection way instead:
ClassWithAnno::class.annotations
ClassWithAnno::methodWithAnno.annotations
Calling filter, etc. to get the Annotation you desire or if you know there is only one Annotation there, you can also just call the following (findAnnotation is an extension function on KAnnotatedElement):
ClassWithAnno::class.findAnnotation<MyAnnotation>()
ClassWithAnno::methodWithAnno.findAnnotation<MyAnnotation>()
One could annotate a method or field with the annotation an get it per Reflection:
this.javaClass.getMethod("annotatedMethod").getAnnotation(MyAnnotation::class.java)
Or According to Roland's suggestion the kotlin version of the above:
MyClass::annotatedMethod.findAnnotation<MyAnnotation>()!!
As suggested by Roland for CDI it is better to use AnnotationLiteral (see his post).
Is it possible to add a new static method to the java.lang.Math class in Kotlin? Usually, such things are possible in Kotlin thanks to Kotlin Extensions.
I already tried doing the following in a file I made called Extensions.kt:
fun Math.Companion.clamp(value:Double,minValue:Double,maxValue:Double):Double
{
return Math.max(Math.min(value,maxValue),minValue)
}
but Math.Companion could not be resolved...
As of Kotlin 1.3, this is not possible. However, it's being considered for a future release!
To help this feature get implemented, go vote on this issue: https://youtrack.jetbrains.com/issue/KT-11968
Because all proposals are basically in limbo right now, I wouldn't hold my breath that this will get in any time soon
I think this is not possible. Documentation says the following:
If a class has a companion object defined, you can also define extension functions and properties for the companion object.
The Math class is a Java class, not a Kotlin one and does not have a companion object in it. You can add a clamp method to the Double class instead.
As of Kotlin 1.2 it is still not possible.
As a workaround, to statically "extend" Environment class I am currently using:
Class EnvironmentExtensions {
companion object {
#JvmStatic
fun getSomething(): File {
...
return Environment.something()
}
}
}
It is not an ideal solution but IntelliJ/Android Studio code completion helps with the usage:
val something = EnvironmentExtensions.getSomething()
Is there a way to use Parceler with Kotlin data classes and constructor for serialization without using #ParcelProperty annotation for each field?
If I try and use library like this:
#Parcel
data class Valve #ParcelConstructor constructor(val size: Int)
I get Error:Parceler: No corresponding property found for constructor parameter arg0. But if I add #ParcelProperty("size") it works just fine.
Why is that?
Update:
There are other another way to use this library.
I could just remove #ParcelConstructor annotation, but then I will get error
Error:Parceler: No #ParcelConstructor annotated constructor and no default empty bean constructor found.
I think (haven't tested it) I also could make all constructor parameters optional and add #JvmOverloads but that has a side effect that I have to check all properties of the class if they are null or not.
Update 2:
This is what worked for me:
#Parcel
data class Valve(val size: Int? = null)
In short generated Java class must have default empty constructor. One way to achieve that is to do as above - all variables should have default values.
According to the docs, Parceler by default works with public fields. But a usual Kotlin data class (as in your example) is rather a "traditional getter/setter bean", since every Kotlin property is represented by a private field and a getter/[setter].
TL; DR: I think this will work:
#Parcel(Serialization.BEAN)
data class Valve(val size: Int = 10)
Note the default value, it allows Kotlin to automatically generate an additional empty constructor, which is required by the Java Been specification.
Another way would be to mark the constructor that we already have:
#Parcel(Serialization.BEAN)
data class Driver #ParcelConstructor constructor(val name: String)
The specific document: https://github.com/johncarl81/parceler#gettersetter-serialization
I know this question already has an answer, but for future viewers who are also struggling to get Parceler to work with kotlin data objects, I wrote a new annotation processor to generate the Parcelable boilerplate for Kotlin data classes. It's designed to massively reduce the boilerplate code in making your data classes Parcelable:
https://github.com/grandstaish/paperparcel
Usage:
Annotate your data class with #PaperParcel, implement PaperParcelable, and add a JVM static instance of the generated CREATOR e.g.:
#PaperParcel
data class Example(
val test: Int,
...
) : PaperParcelable {
companion object {
#JvmField val CREATOR = PaperParcelExample.CREATOR
}
}
Now your data class is Parcelable and can be passed directly to a Bundle or Intent
Edit: Update with latest API
Just add the default constructor:
#Parcel
data class Valve(val size: Int) {
constructor() : this(0)
}
if you use Kotlin 1.1.4 or above it's easier to use #Parcelize annotation
For doing this first add this to build.gradle
android {
//other codes
//for using latest experimental build of Android Extensions
androidExtensions {
experimental = true
}
}
Then change your class like this
#Parcelize
data class Valve(val size: Int? = null) : Parcelable