preface
Kotlin makes it easier to interact with Java (PY) by providing some annotation parameters that make calling Kotlin easier and friendlier for Java.
Kotlin official annotation address
Today we’ll learn and understand these common notes:JvmDefault
JvmField
JvmMultifileClass
JvmName
JvmOverloads
JvmStatic
Strictfp
Synchronized
Volatile
Transient
JvmDefault
Specifies that JVM default methods are generated for non-abstract Kotlin interface members. The use of this annotation requires the compiler parameter to be specified: -xJVM-default =enable or -xJVM-default =compatibility.
-Xjvm-default=enable
: For each@JvmDefault
Method generates default methods in the interface. In this mode, use@JvmDefault
Annotating an existing method may break binary compatibility because it will effectively be fromDefaultImpls
Class to remove the method.-Xjvm-default=compatibility
: In addition to the default interface methods, generated compatibility accessDefaultImpls
Class that calls methods that access the default interface through composition. In this mode, use@JvmDefault
Annotation existing methods are binary compatible, but more methods are generated in bytecode.
Removing this annotation from the interface member changes the binary incompatibility in both modes.
Only JVM target bytecode version 1.8 (-JVM-target 1.8) or higher can generate default methods.
Let’s try this annotation out and see how it works. Okay
First, let’s look at the unannotated case:
interface Animal {
var name: String?
var age: Int?
fun getDesc(a) = name + "This year already." + age + "Old ~"
}
class Dog(override varname: String? .override var age: Int?). : Animalfun main(args: Array<String>? {
Dog("White".3).getDesc()
}
Copy the code
Bytecode to Java code looks like this:
public interface Animal {
@Nullable
String getName(a);
void setName(@Nullable String var1);
@Nullable
Integer getAge(a);
void setAge(@Nullable Integer var1);
@NotNull
String getDesc(a);
public static final class DefaultImpls {
@NotNull
public static String getDesc(Animal $this) {
return $this.getName() + "This year already." + $this.getAge() + "Old ~"; }}}public final class Dog implements Animal {
@Nullable
private String name;
@Nullable
private Integer age;
@Nullable
public String getName(a) {
return this.name;
}
public void setName(@Nullable String var1) {
this.name = var1;
}
@Nullable
public Integer getAge(a) {
return this.age;
}
public void setAge(@Nullable Integer var1) {
this.age = var1;
}
public Dog(@Nullable String name, @Nullable Integer age) {
this.name = name;
this.age = age;
}
@NotNull
public String getDesc(a) {
return Animal.DefaultImpls.getDesc(this); }}public final class JvmKt {
public static final void main(@Nullable String[] args) {(new Dog("White".3)).getDesc(); }}Copy the code
As you can see from the above code, when we call the default method of the interface, we are actually calling the method of an anonymous static inner class.
Kotlin creates a static inner class that calls DefaultImpls to store the default implementation of methods that are static, and uses the “Self” sink type to simulate methods that belong to objects. Then, for each type that extends the interface, if the type does not implement the method itself, Kotlin will connect the method to the default implementation through a call at compile time.
Such an implementation has both advantages and disadvantages. The advantage is that it can provide the power of specific methods on interfaces on JVMS prior to Java 8. The disadvantages are:
- It is incompatible with Java processing, resulting in messy interoperability. We can call it directly in Java code
DefaultImpls
Class, this is an amazing thing…. - One of the main reasons for the existence of default methods in Java8 is the ability to avoid having to touch every subclass (for example, add
Collection.stream()
) to add methods to the interface. The Kotlin implementation does not support this because the default call must be generated on each concrete type. Adding new methods to the interface causes each implementer to have to be recompiled.
To solve this problem,Kotlin launched @jVMDefault to optimize the situation. Now let’s see what happens with annotations: Gradle files with compiled configurations added
-jvm-target=1.8 -Xjvm-default=enable
Copy the code
Kotlin code
interface Animal {
var name: String?
var age: Int?
@JvmDefault
fun getDesc(a) = name + "This year already." + age + "Old ~"
}
class Dog(override varname: String? .override var age: Int?). : Animalfun main(args: Array<String>? {
Dog("White".3).getDesc()
}
Copy the code
Corresponding Java code
public interface Animal {
@Nullable
String getName(a);
void setName(@Nullable String var1);
@Nullable
Integer getAge(a);
void setAge(@Nullable Integer var1);
@JvmDefault
@NotNull
default String getDesc(a) {
return this.getName() + "This year already." + this.getAge() + "Old ~"; }}public final class Dog implements Animal {
@Nullable
private String name;
@Nullable
private Integer age;
@Nullable
public String getName(a) {
return this.name;
}
public void setName(@Nullable String var1) {
this.name = var1;
}
@Nullable
public Integer getAge(a) {
return this.age;
}
public void setAge(@Nullable Integer var1) {
this.age = var1;
}
public Dog(@Nullable String name, @Nullable Integer age) {
this.name = name;
this.age = age; }}public final class JvmKt {
public static final void main(@Nullable String[] args) {(new Dog("White".3)).getDesc(); }}Copy the code
We can see that the annotation eliminates the anonymous static inner class to bridge the implementation of the default method, so that Java calls the default method of the Kotlind interface are basically the same as the default method of the Java interface.
Note that in addition to changing the compiler flags, compatibility modes were added using Kotlin 1.2.50. The compatibility flag (-xJVM-default = Compatibility) is designed to preserve binary compatibility with existing Kotlin classes while still being able to move to Java 8-style default methods. This flag is particularly useful when considering other generated projects that point to static bridging methods.
To achieve this, the Kotlin compiler uses the class file trick Invokespecial to call the default interface methods while still keeping the DefaultImpls bridge class. Let’s take a look at what it looks like:
public interface Animal {
@JvmDefault
@NotNull
default String getDesc(a) {
return "Meow, meow, meow, meow.";
}
public static final class DefaultImpls {
@NotNull
public static String getDesc(Animal $this) {
return $this.getDesc(); }}}// In the case of new compilation
public final class Dog implements Animal {}In some other projects, compilation already exists
public final class OldDog implements Animal {
@NotNull
public String getDesc(a) {
return Animal.DefaultImpls.getDesc(this); }}Copy the code
There is a good decompression here, especially since this is not valid Java syntax. Here are some DOS and don ‘ts:
- Generate the default method on the interface just as we did when we first used it
enable
- Newly compiled classes, such as
Dog
, will directly use the Java 8-style default interface. - like
OldDog
Such existing compiled code still works at the binary level because it points to the DefaultImpls class. - the
DefaultImpls
The implementation of a method cannot be represented in a real Java source because it is analogous to a call<init>
or<super>
; This method must call getDesc on the Animal interface on the provided instance. If it just calls “getDesc ()”, it will cause the old type (OldDog.getDesc()
->DefaultImpls.getDesc()
->OldDog.getDesc()
) stack overflow. Instead, it must call the interface directly:OldDog.getDesc()
->DefaultImpls.getDesc()
->Animal.getDesc()
, and only through interface methodsinvokespecial
Call to complete.
JvmField
Causes the Kotlin compiler to no longer generate getters/setters for this field and treat it as a public field (public)
The usage scenarios are as follows:
Kotlin code
class Bean(
@JvmField
varname:String? .var age:Int
)
Copy the code
Corresponding Java code
public final class Bean {
@JvmField
@Nullable
public String name;
private int age;
public final int getAge(a) {
return this.age;
}
public final void setAge(int var1) {
this.age = var1;
}
public Bean(@Nullable String name, int age) {
this.name = name;
this.age = age; }}Copy the code
By contrast, annotated field property modifiers change from private to public
JvmName
The main purpose of this annotation is to tell the compiler the name of the generated Java class or method
The usage scenarios are as follows:
Koltin code
@file:JvmName("JavaClass")
package com.example.maqiang.sss
var kotlinField: String? = null
// Change the name of the property's set method
@JvmName("setJavaField")
set(value) {
field = value
}
// Change the normal method name
@JvmName("JavaFunction")
fun kotlinFunction(a){}Copy the code
The corresponding Java code:
public final class JavaClass {
@Nullable
private static String kotlinField;
@Nullable
public static final String getKotlinField(a) {
return kotlinField;
}
@JvmName(
name = "setJavaField"
)
public static final void setJavaField(@Nullable String value) {
kotlinField = value;
}
@JvmName(
name = "JavaFunction"
)
public static final void JavaFunction(a) {}}Copy the code
Java calls kotlin code
public class JavaJvm{
public static void main(String[] args) {
// Class names and methods are modified after annotation
JavaClass.JavaFunction();
JavaClass.getKotlinField();
JavaClass.setJavaField("java"); }}Copy the code
This annotation is used to deal with compatibility issues after various class names are changed
JvmMultifileClass
This annotation lets the Kotlin compiler generate a multifile class with the top-level functions and attributes declared in the file as part of it, and the JvmName annotation provides the name of the corresponding multifile.
Usage Scenario analysis:
Kotlin code:
//A.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package com.example.maqiang.sss
fun getA(a) = "A"
//B.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package com.example.maqiang.sss
fun getB(a) = "B"
Copy the code
Java calls Kotlin’s top-level functions
public class JavaJvm {
public static void main(String[] args) { Utils.getA(); Utils.getB(); }}Copy the code
We can see that the annotation combines the methods in the A and B files into one Utils class. This annotation eliminates the need to manually create A Utils class and makes adding methods to the Utils class more flexible and convenient
JvmOverloads
Tells the Kotlin compiler to generate an overload to replace the default parameter values for this function
The usage scenarios are as follows:
Kotlin code
@JvmOverloads
fun goToActivity(
context: Context? , url:String? , bundle:Bundle? = null,
requestCode: Int = - 1
){}Copy the code
Corresponding Java code
public final class AKt {
@JvmOverloads
public static final void goToActivity(@Nullable Context context, @Nullable String url, @Nullable Bundle bundle, int requestCode) {}// $FF: synthetic method
// $FF: bridge method
@JvmOverloads
public static void goToActivity$default(Context var0, String var1, Bundle var2, int var3, int var4, Object var5) {
if ((var4 & 4) != 0) {
var2 = (Bundle)null;
}
if ((var4 & 8) != 0) {
var3 = -1;
}
goToActivity(var0, var1, var2, var3);
}
@JvmOverloads
public static final void goToActivity(@Nullable Context context, @Nullable String url, @Nullable Bundle bundle) {
goToActivity$default(context, url, bundle, 0.8, (Object)null);
}
@JvmOverloads
public static final void goToActivity(@Nullable Context context, @Nullable String url) {
goToActivity$default(context, url, (Bundle)null.0.12, (Object)null);
}
Copy the code
We can see that in order for Java to enjoy Koltin’s default parameter features, this annotation is used to generate the corresponding overloaded method.
The rule for overloading is sequential overloading, and only parameters with default values are reloaded.
JvmStatic
Use this annotation on a function, and the Kotlin compiler generates another static method
Using this annotation on a property, the Kotlin compiler generates additional setter and getter methods
This annotation eliminates the problem of Java calling Kotlin’s Companion Object without directly calling its static methods and properties.
Comparison of Usage Scenarios
Companion Object without annotations
class A {
companion object {
var string: String? = null
fun hello(a) = "hello,world"}}Copy the code
Corresponding Java code
public final class A {
@Nullable
private static String string;
public static final A.Companion Companion = new A.Companion((DefaultConstructorMarker)null);
public static final class Companion {
@Nullable
public final String getString(a) {
return A.string;
}
public final void setString(@Nullable String var1) {
A.string = var1;
}
@NotNull
public final String hello(a) {
return "hello,world";
}
private Companion(a) {}// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this(a); }}}Copy the code
We can see that when Java calls kotlin’s Companion object’s methods and properties, it needs Companion.
In the case of annotations in the Companion Object
class A {
companion object {
@JvmStatic
var string: String? = null
@JvmStatic
fun hello(a) = "hello,world"}}Copy the code
Corresponding Java code
public final class A {
@Nullable
private static String string;
public static final A.Companion Companion = new A.Companion((DefaultConstructorMarker)null);
@Nullable
public static final String getString(a) {
A.Companion var10000 = Companion;
return string;
}
public static final void setString(@Nullable String var0) {
A.Companion var10000 = Companion;
string = var0;
}
@JvmStatic
@NotNull
public static final String hello(a) {
return Companion.hello();
}
public static final class Companion {
/ * *@deprecated* /
// $FF: synthetic method
@JvmStatic
public static void string$annotations() {
}
@Nullable
public final String getString(a) {
return A.string;
}
public final void setString(@Nullable String var1) {
A.string = var1;
}
@JvmStatic
@NotNull
public final String hello(a) {
return "hello,world";
}
private Companion(a) {}// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
Copy the code
As you can see, while the Companion static inner class still exists, Java can now call the corresponding static methods and properties directly.
Comparison of Java calls before and after annotations
public class JavaJvm {
public static void main(String[] args) {
// Before using annotations
A.Companion.hello();
A.Companion.getString();
A.Companion.setString("hello,kotlin");
// After using annotations
A.hello();
A.getString();
A.setString("hello,kotlin"); }}Copy the code
Obvious annotations make the interaction between Java and Kotlin much friendlier
Strictfp
Marking the JVM methods generated from the annotation function as STRICtFP means that you need to limit the precision of floating point operations performed within the method for better portability.
Corresponding to strictFP keyword in Java
The usage scenarios are as follows:
// Can be used in constructors, getters/setters of properties, and normal methods
// The Target on the official website has a class, but the actual use of class does not annotate
class JvmAnnotation @Strictfp constructor() {
var a: Float = 0.0f
@Strictfp
get() {
return 1f
}
@Strictfp
set(value) {
field = value
}
@Strictfp
fun getFloatValue(a): Float = 0.0f
}
Copy the code
Synchronized
The JVM method generated from an annotated function is marked as synchronized, which means that the method is protected from concurrent execution by multiple threads of the monitor that defines the instance of the method (or, in the case of static methods, classes).
Corresponds to the Synchronized keyword in Java
The usage scenarios are as follows
class JvmAnnotation {
var syn: String = ""
@Synchronized
get() {
return "test"
}
@Synchronized
set(value) {
field = value
}
@Synchronized
fun getSynString(a): String = "test"
fun setSynString(str:String){
// Note that we are using an introverted function to lock the code block
synchronized(this){
println(str)
}
}
}
Copy the code
Volatile
Jvma-supported fields with annotated attributes are marked as volatile, which means that writes to the field are immediately visible to other threads.
Corresponds to the volatile keyword in Java
The usage scenarios are as follows
// The val variable cannot be annotated
@Volatile
var volatileStr: String = "volatile"
Copy the code
Transient
The JVM-supported field of the annotated property is marked transient, indicating that it is not part of the default serialization form of the object.
Corresponding to transient keyword in Java
The usage scenarios are as follows:
//:Serializable
data class XBean(
valname: String? .val age: Int? .// Does not participate in serialization
@Transient
val male: Boolean = true
): Serializable
//Parcelize(experimental = true in Gradle)
@Parcelize
data class XBean(
valname: String? .val age: Int? .// Does not participate in serialization
@Transient
val male: Boolean = true
)
Copy the code
These are some of the most commonly used comments in the daily development process. If you have any questions, please leave a comment