Everything about JVMField, JVMOverloads, JVMName, and JVMStatic annotations in Kotlin
Sagar Malhotra
Android dev by day, Creator by night.???? Sharing my passion through videos, blogs and conferences.??
The mentioned four JVMField, JVMOverloads, JVMName, and JVMStatic annotations are used for easier and convenient inter-operability between Kotlin and JAVA code.? Used in examples like:
There is a very high chance that you have already used a JAVA-based library while working with Kotlin or Android. Some of these libraries might expect some different behavior from your Kotlin compiled code(they are expecting JAVA) that can lead to an unexpected error, but you can still magically modify your compiled code by using these annotations and have a seamless experience working with both Kotlin and JAVA.
Note: I have never used these Annotations in any of my projects till now, but they are really interesting and important concepts to understand as a Kotlin/Android developer. It’s better to be safe than sorry when it comes to Java codebases lurking in the shadows.
JVMField:
What:
According to docs, it is an annotation class that Instructs the Kotlin compiler not to generate getters/setters for this property and expose it as a field. To understand this, you need to first understand how the Kotlin fields work for JAVA.
A Kotlin property is compiled to the following Java elements:
For example,
class SomeClass(var firstName: String)
compiles to the following Java declarations:
//In java
private String firstName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
That means when using your Kotlin classes in JAVA code, you can only access functions and not fields.
class KotlinClass {
val normalProperty: String = "This is a normal property"
}
Used In Java classes like:
KotlinClass kotlinObject = new KotlinClass();
// Accessing the property requires a getter
String value = kotlinObject.getNormalProperty();
But, we can use our JVMField annotation to instruct our compiler to treat Kotlin fields as Java fields.
class KotlinClass {
@JvmField
val exposedField: String = "This is a JVM field"
}
Now, in Java:
KotlinClass kotlinObject = new KotlinClass();
// Accessing the field directly
String value = kotlinObject.exposedField;
Use:
It can be used with
Why:
1. Potential Performance Optimization:
2. Workaround for Specific Java Libraries:
3. Potential Memory Optimization:
Drawbacks/Cautions:
2. Potential for Tight Coupling:
JVMOverloads:
What:
According to docs, it is an annotation class that instructs the Kotlin compiler to generate overloads for the function that substitutes default parameter values.
This explains it all but if you still didn’t get it, I assume you are already familiar with Kotlin and the default parameters in Kotlin functions, let us see an example…
fun draw(label: String, lineWidth: Int = 1, color: String = "red")
This is a normal function in Kotlin which 2 parameters with default values. Now, since Java does not support default values, we will get the same function with 3 parameters in Java.
@JvmOverloads fun draw(label: String, lineWidth: Int = 1, color: String = "red")
But, after applying the annotation your compiler will generate multiple overloads of these functions, as shown below…
void draw(String label, int lineWidth, String color) { }
void draw(String label, int lineWidth) { }
void draw(String label) { }
Use:
It can be used with
Drawbacks:
领英推荐
JVMName:
According to docs, it is used to specify the name of a class or method that is generated from one element.
This is not enough to understand so let us take an example, given two functions in Kotlin:
fun List<Any>.filler(): List<Int> = listOf(0)
fun <T> List<T>.filler() : List<String> = listOf("0")
They have the same name but they are used with different types of lists.
In kotlin, by default always the first function will get called, and if you want to call the second one then explicitly define the T type while using.
list.filler()//Calls first
list.filler<String>()//Calls second
In java it will show us an Ambigious method call error, so in cases like these, you can consider using the JVMName annotation in one or both of the functions.
@JvmName("fillerString")
fun <T> List<T>.filler() : List<String> = listOf("0")
Another example is having a field that exposes the same name to our Java classes.
val x: Int
get() = 15
fun getX() = 10
Here, for field X, we can have a getter and setter in our Java class, and for the getter, it will be getX() method name, but the problem is you can see that a method with the same name already exists, so we can use the JVMName annotation to define a different name for our Java class.
val x: Int
@JvmName("getX_prop")
get() = 15
Extending this, we can also set different names for getter and setter:
@get:JvmName("x")
@set:JvmName("changeX")
var x: Int = 23
You can also use it on a file name.
JVMStatic:
According to docs,
It simply means, if you were thinking like defining a companion object in kotlin is equal to the static object or function in JAVA, then you are somewhat wrong. It does not work the exact similar way.
Once we define a companion object in Kotlin, it will not directly exposes that field or function in Java classes as a static field, but it will only exposes a Companion class object, which will then help us to access those methods.
Example:
class KotlinClass {
companion object {
fun first(): Unit {}
fun second(): Unit {}
}
}
public class JavaClass {
public static void main(String[] args) {
KotlinClass.first();//Compilation error
KotlinClass.Companion.second();//Works this way
}
}
So, now you got the idea why JVMStatic annotation exists, we can use this to annotate the functions and expose them directly as static values in Java classes.
@JvmStatic
fun first(): Unit {}
public static void main(String[] args) {
KotlinClass.first();//Works fine
KotlinClass.Companion.second();//Also works fine
}
This also works for fields:
class KotlinClass {
companion object {
@JvmStatic
var a = 1
}
}
public class JavaClass {
public static void main(String[] args) {
KotlinClass.getA();
}
}
But, this was a field, right? What if we don’t want this getters and setter, we already know we can use the JVMField annotation…
But, we can not use both of these annotations at one time. So, I am using only JVMField.
class KotlinClass {
companion object {
@JvmField
var a = 1
}
}
And now this annotation also helps us in exposing this as a static field directly.
public class JavaClass {
public static void main(String[] args) {
int a = KotlinClass.a;
}
}
JVMWildcard:
As always, there is something extra to learn in my articles and here is this annotation “JvmWildcard”.
Not jumping deeply into the explanation, just simple usecase s when we are using inheritance in kotlin and expecting a child class in parent definition, then we can use this for generating <? extends Something> type.
In koltin:
fun boxDerived(value: Derived): Box<Derived> = Box(value)
fun unboxBase(box: Box<Base>): Base = box.value
//This is fine to use in kotlin
unboxBase(boxDerived(Derived()))
But in java it is not gonna work…
Box<Derived> boxDerived(Derived value) { ... }
Base unboxBase(Box<Base> box) { ... }
because in Java the class Box is invariant in its parameter T, and thus Box<Derived> is not a subtype of Box<Base>.
To make this work in Java, you would have to define unboxBase as follows:
Base unboxBase(Box<? extends Base> box) { ... }
This declaration uses Java’s wildcards types (? extends Base) to emulate declaration-site variance through use-site variance.
To make it possible, simply use this in kotlin:
fun boxDerived(value: Derived): Box<@JvmWildcard Derived> = Box(value)
// is translated to
// Box<? extends Derived> boxDerived(Derived value) { ... }
That’s all for today.
I hope you learnt something new from this article, if yes then make sure to Like this and follow “Sagar Malhotra” for more such useful content.
Pre Final Year of BE in Computer Science at Panjab University
1 年Thanks Sir but can I request for an article on the architecture and dependency injection needed a simple explanation on it