“This is the 10th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”
Chapter outline
- Annotations in Java
- Notes from Kotlin
Java annotations
Java Annotation is an Annotation mechanism introduced in JDK5.0. In essence, it labels specific code. Annotations can be used on classes, interfaces, methods, variables, parameters and packages. Annotations annotate specific code with additional information. This information can be retained at different times, such as source time, compile time, or run time.
Java built-in annotations
Java comes with 10 built-in annotations to see what they can do:
- Act on the code
annotations | role |
---|---|
@Override | Check if the method is an override method. If the method is not found in the parent class or in the referenced interface, a compilation error is reported. |
@Deprecated | Flag outdated content. If this class or method is used, a compile warning is issued. |
@SuppressWarnings | Instructs the compiler to ignore warnings declared in annotations. |
@SafeVarargs | Java 7 began to support ignoring any warnings generated by method or constructor calls that take arguments as generic variables. |
@FunctionalInterface | Java 8 began to support identifying an anonymous function or functional interface. |
- Acting on annotations (known as meta-annotations)
annotations | role |
---|---|
@Documented | Annotated annotations that can appear in Javadoc. |
@Retention | Identify how the annotation is stored (lifecycle), whether it is only in code, marshaled into a class file, or accessible at run time via reflection. |
@Target | Marks which Java member the annotation should be. |
@Inherited | Mark which annotation class the annotation inherits from (the default annotation does not inherit from any subclasses) |
@Repeatable | Java 8 began to support the idea that an annotation can be used more than once on the same declaration |
Structure of annotations
As can be seen from the picture above:
- All annotations are implemented
Annotation
interface - One annotation will correspond to another
RetentionPolicy
(Each annotation has a unique RetentionPolicy property), corresponding to@Retention
The meaning of this annotation indicates how an annotation is saved (lifecycle). - One annotation can correspond to 1 to N
ElementType
(Each annotation can have multiple ElementType attributes), corresponding to@Target
The meaning of this annotation indicates which Java elements an annotation can apply to.
Let’s look at the key annotation classes:
Annotation
All annotations implement the Annotation interface.
public interface Annotation {
boolean equals(Object obj);
int hashCode(a);
String toString(a);
Class<? extends Annotation> annotationType();
}
Copy the code
RetentionPolicy
An enumeration class where the enumeration defined is the value of the @Retention annotation:
public enum RetentionPolicy {
/* The Annotation information exists only for the duration of the compiler's processing; it is no longer available after the compiler's processing
SOURCE,
/* The compiler stores the annotations in the corresponding.class file of the class. The default behavior */
CLASS,
/* The compiler stores the annotations in a class file that can be read in by the JVM and can be accessed by reflection */
RUNTIME
}
Copy the code
ElementType
An enumeration class in which the enumeration defined is the value of the @Target annotation
public enum ElementType {
TYPE, /* Class, interface (including annotation types), or enumeration declaration */
FIELD, /* Field declarations (including enumeration constants) */
METHOD, /* Method declaration */
PARAMETER, /* Parameter declaration */
CONSTRUCTOR, /* Constructor declaration */
LOCAL_VARIABLE, /* Local variable declaration */
ANNOTATION_TYPE, /* Comments the type declaration */
PACKAGE, /* Package declaration */
TYPE_PARAMETER, /* Type parameter declaration (1.8 new added) */
TYPE_USE, /* Type usage declaration (added in 1.8) */
}
Copy the code
Here’s an example of a built-in annotation:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
Copy the code
@interface
Is the way annotations are defined in Java (more on that later)- The @terget annotations include elementType. CONSTRUCTOR and elementType. METHOD, indicating that the SafeVarargs annotation applies to constructors and methods.
- The @Retention annotation has the value retentionPolicy.runtime, which means that SafeVarargs annotation information is stored by the compiler in the class and can be accessed by reflection at RUNTIME.
Customize a Java annotation
We can customize annotations to implement specific functions, just by
- Implement the Annotation interface: use
@interface
To modify - Set Retention and Target information for annotations
- Add attributes to annotations (optional, as required)
Look at an example:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation { // Define an @myAnnotation annotation
String value(a) default "unkonwn";
}
Copy the code
Use this annotation in a class:
public class Dog {
public String name;
public Dog(String name) {
this.name = name;
}
/ / 👇
@MyAnnotation("Dog") If the 👈 annotation has only one value method, value may not be written. If the value of value is not written, the default value unknown is used
public void showName(a) { System.out.println(name); }}Copy the code
Get the information in this annotation from reflection in the code:
public class TestMain {
public static void main(String[] args) {
Dog dog = new Dog("keji");
Class<Dog> c = Dog.class;
try {
Method method = c.getMethod("showName");
method.invoke(dog);
iteratorAnnotations(method);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch(InvocationTargetException e) { e.printStackTrace(); }}private static void iteratorAnnotations(Method method) {
if (method.isAnnotationPresent(MyAnnotation.class)) { // 👈 MyAnnotation annotation Retention must be RUNTIME or annotation information cannot be accessed at RUNTIME
System.out.println("Method is decorated with MyAnnotation annotation.");
MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class); // 👈 gets the MyAnnotation annotation via method
String value = myAnnotation.value();
System.out.println("Annotated value is :"+ value);
} else {
System.out.println("Method not decorated with MyAnnotation annotation."); }}}// Run the resultThe value of the Keji annotation is: DogCopy the code
Kotlin annotations
Annotations in Kotlin are similar to Java.
Define a Kotlin annotation
Java uses @Interface to define an annotation, Kotlin uses the Annotation Class keyword to define an annotation.
/ / Java version
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
/ / 👇
public @interface MyAnnotation { // Define an @myAnnotation annotation
String value(a) default "unkonwn";
}
/ / Kotlin version
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
/ / 👇
annotation class MyAnnotation(val value: String = "unknown")
Copy the code
The above two annotations are completely equivalent. The difference in code is that the @target value in Kotlin is defined in AnnotationTarget, and the @retention value is defined in AnnotationRetention, so let’s look at the two classes separately.
AnnotationTarget
Enumeration classes, which correspond to ElementType in Java, have slightly different names and locations for enumerated values
public enum class AnnotationTarget {
CLASS, Class, interface or object, annotation Class is also included */
ANNOTATION_CLASS, /* Annotation class only */
TYPE_PARAMETER, /* Generic type parameter (unsupported yet) */
PROPERTY, /* For properties Property */
FIELD, /* Backing Field, including property's backing Field */
LOCAL_VARIABLE, /* Local variable */
VALUE_PARAMETER, /* Value parameter of a function or a constructor */
CONSTRUCTOR, Constructor only (primary or secondary) */
FUNCTION, Function (constructors are not included) */
PROPERTY_GETTER, Property getter only */ is used only on Property getter methods
PROPERTY_SETTER, /* Property setter only */ is used only on setter methods of properties
TYPE, /* The object is a Type, such as class, interface, enumeration Type usage */
EXPRESSION, /* applies to Any expression */
FILE, /* File */
@ SinceKotlin (" 1.1 ")
TYPEALIAS /* On the Type alias Type alias */
}
Copy the code
AnnotationRetention
Enumeration class, corresponding to Java RetentionPolicy, enumeration value name, meaning is the same.
public enum class AnnotationRetention {
SOURCE, Annotation isn't stored in binary output */
BINARY, /* The Annotation is stored in the binary output, but invisible for Reflection */
RUNTIME /* The Annotation is stored in binary output and visible for Reflection (default Retention) */
}
Copy the code
The corresponding relationship is as follows:
- [Kotlin]AnnotationRetention.SOURCE = [Java]RetentionPolicy.SOURCE
- [Kotlin]AnnotationRetention.BINARY = [Java]RetentionPolicy.CLASS
- [Kotlin]AnnotationRetention.RUNTIME = [Java]RetentionPolicy.RUNTIME
Kotlin yuan notes
As in Java, Kotlin also has meta-annotations.
annotations | role |
---|---|
@Target | Represents the target objects in the code on which the tag is applied. You can specify more than one target object at a time. |
@Retention | In both Java and Kotlin, there are three types of phases: SOURCE, CLASS/BINARY, and RUNTIME. |
@MustBeDocumented | Represents an annotation class as part of the public API and makes the annotation exist in the generated API documentation. |
@Repeatable | Indicates that an annotation can be applied more than once to a code element. |
Kotlin dropped the @Inherited meta-annotation
Kotlin presets annotations
There are a number of @JVM-starting annotations built into Kotlin to address some of the call habits and control API calls in Java.
@JvmDefault
In Kotlin, interfaces can add non-abstract members, and the @jVMDefault annotation is the default method for generating non-abstract interface members. Using this annotation requires specifying an explicit compilation parameter: -xJVM-default =enable or -xJVM-default =compatibility
@JvmField
This annotation is used on the field to expose the property as a public Java field with no accessors; Or in the properties of a Companion Object.
Scenario 1: Common class
In Kotlin, by default, the Kotlin class does not expose fields but attributes, and Kotlin provides behind the scenes fields for attributes.
/ / kotlin class
class Person {
var age = 18 // 👈 Person class defines an age property. The age property is public by default, but decompile into Java code to see the fields behind it.
set(value) {
if (value > 20) {
field = value
}
}
}
Copy the code
After compiling to a class file, decompile to Java code:
public final class Person {
private int age = 18; //👈 this is the field behind the age property of the Person class
// External access is operated through setter and getter accessors. Because Kotlin automatically generates setter and getter accessors, external operations can be directly analogous to public property operations,
// It is actually implemented internally through setter and getter accessors
public final int getAge(a) {
return this.age;
}
public final void setAge(int value) {
if (value > 20) {
this.age = value; }}}Copy the code
The @jVMField annotation removes setter and getter accessors for the field and changes the field to public.
class Person {
@JvmField
var age = 18
}
Copy the code
Decompiling into Java code:
public final class Person {
@JvmField
public int age = 18;// Eliminates setter and getter accessors, and the age field is public
}
Copy the code
Scenario 2: Companion Object without @jVMField annotation:
class Person {
companion object {
val MAX_AGE = 120
}
}
Copy the code
Decompiled Java code:
public final class Person {
private static final int MAX_AGE = 120;// Note: the default is private MAX_AGE, so calls in Java cannot be made directly through the Person class name. Variable name access
public static final Person.Companion Companion = new Person.Companion((DefaultConstructorMarker)null);
public static final class Companion {
// Calls in Java cannot be made directly through the Person class name. Variable name access,
// It is accessed indirectly through the static Companion inner class getMAX_AGE, like person.panion.getmax_age ();
public final int getMAX_AGE(a) {
return Person.MAX_AGE;
}
private Companion(a) {}// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this(a); }}}Copy the code
@JvmName
This annotation can change Java methods, fields, and class names generated by Kotlin by default (class names are changed by acting on kt files).
class Cat {
@set:JvmName("setCatName") // Change the name of the property setter method
@get:JvmName("getCatName") // Change the name of the property getter method
var name: String = "ketty"
@JvmName("getCatAge") // Change the name of the normal method
fun getAge(a): Int {
return 5}}Copy the code
Decompiling into Java code:
public final class Cat {
@NotNull
private String name = "ketty";
@JvmName(name = "getCatName")
@NotNull
public final String getCatName(a) { // 👈 has been changed from getName to getCatName
return this.name;
}
@JvmName(name = "setCatName")
public final void setCatName(@NotNull String var1) { // 👈 has been changed from setName to setCatName
Intrinsics.checkParameterIsNotNull(var1, "
"
?>);
this.name = var1;
}
@JvmName(name = "getCatAge")
public final int getCatAge(a) { 👈 has been changed from getAge to getCatAge
return 5; }}Copy the code
@JvmMultifileClass
This annotation merges top-level methods defined in multiple files into the same class, in conjunction with @jVMName.
/ / UtilsA. Kt file
@file:JvmName("Utils") // Note that these two must precede the package declaration
@file:JvmMultifileClass
package cc.lixiaoyu.fanxing
fun funcA(a){
println("funcA")}/ / UtilsB. Kt file
@file:JvmName("Utils")
@file:JvmMultifileClass // Note that these two must precede the package declaration
package cc.lixiaoyu.fanxing
fun funcB(a) {
println("funcB")}Copy the code
There are two Kotlin files, UtilsA and UtilsB, which each define a top-level method funcA and funcB. Without the @file:JvmName and @File :JvmMultifileClass annotations, Two class files, utilsakt. class and utilsbkt. class, are generated. Let’s first look at how these two methods are called with annotations.
public static void main(String[] args) {
Utils.funcA(); // UtilsAKt has become Utils, where @file:JvmName is used
Utils.funcB(); // And the methods defined in two different files are merged into the same class. Here is what @file:JvmMultifileClass does
}
Copy the code
Decompiler to Java and see:
// utilsa.kt decompiled to Java
public final class Utils {
public static final void funcA(a) { Utils__UtilsAKt.funcA(); }}// Utils__UtilsAKt.java
package cc.lixiaoyu.fanxing;
final class Utils__UtilsAKt {
public static final void funcA(a) {
String var0 = "funcA"; System.out.println(var0); }}// utilsb.kt decompiled to Java
final class Utils__UtilsBKt {
public static final void funcB(a) {
String var0 = "funcB"; System.out.println(var0); }}// Utils.java
package cc.lixiaoyu.fanxing;
public final class Utils {
public static final void funcB(a) { Utils__UtilsBKt.funcB(); }}Copy the code
You can see that a new Utils class and a method with the same signature are created and then the corresponding method in Utils _ _UtilsAKt or Utils _ _UtilsBKt is invoked.
@JvmOverloads
This annotation lets the Kotlin compiler generate multiple overloaded functions for functions (including constructors) that take default parameter values.
// UtilsA.kt
fun funcA(foo: Int = 5) {
println("funcA: $foo")}// called in Kotlin
fun main(a) {
funcA() // Use the default values
funcA(10) // Pass in parameters
}
// call in Java
public static void main(String[] args) {
UtilsAKt.funcA(6); // Parameters must be passed in. Default values cannot be used
}
Copy the code
Annotate funcA with @jvmoverloads:
// UtilsA.kt
@JvmOverloads
fun funcA(foo: Int = 5) {
println("funcA: $foo")}// call in Java
public static void main(String[] args) {
UtilsAKt.funcA(); // Use the default value
UtilsAKt.funcA(6);
}
Copy the code
Decompiler to Java:
public final class UtilsAKt {
@JvmOverloads
public static final void funcA(int foo) {
String var1 = "funcA: " + foo;
System.out.println(var1);
}
// $FF: synthetic method
@JvmOverloads
public static void funcA$default(int var0, int var1, Object var2) {
if ((var1 & 1) != 0) {
var0 = 5;
}
funcA(var0);
}
@JvmOverloads
public static final void funcA(a) { // 👈 adds an overloaded method with no arguments
funcA$default(0.1, (Object)null); }}Copy the code
@JvmPackageName
Files that use this annotation change the fully qualified name (package name) of the generated.class file. This does not affect how the Kotlin code sees the file declared, but Java and other JVM languages see the file in the package specified by the annotation. Files that use this annotation can contain only alias declarations for functions, attributes, and classes, but not classes.
@JvmStatic
This annotation can be used on methods of object declarations or Companion objects of ordinary classes to expose them as Java static methods. This annotation is often used on methods of associated objects for Java code to call.
class Cat {
companion object {
fun getCatName(a): String {
return "Cat"}}}// call in Java
public static void main(String[] args) {
// Only cat.panion.xxx can be called
System.out.println(Cat.Companion.getCatName());
}
Copy the code
Decompiled Java code:
public final class Cat {
public static final Cat.Companion Companion = new Cat.Companion((DefaultConstructorMarker)null);
public static final class Companion {
@NotNull
public final String getCatName(a) {
return "Cat";
}
private Companion(a) {}// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this(a); }}}Copy the code
With the @jVMstatic annotation:
class Cat {
companion object {
@JvmStatic
fun getCatName(a): String {
return "Cat"}}}// call in Java
public static void main(String[] args) {
// You can call cat.xxx directly like this
System.out.println(Cat.getCatName());
}
Copy the code
Decompiling into Java code:
public final class Cat {
public static final Cat.Companion Companion = new Cat.Companion((DefaultConstructorMarker)null);
@JvmStatic
@NotNull
public static final String getCatName(a) { // 👈 generates a method of the same name in the Cat class to call Companion's corresponding method.
return Companion.getCatName();
}
public static final class Companion {
@JvmStatic
@NotNull
public final String getCatName(a) {
return "Cat";
}
private Companion(a) {}// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this(a); }}}Copy the code
As you can see from the decomcompiled code, this annotation works by generating a method with the same signature in the Cat class that calls the Companion object’s corresponding method.
@ JvmSuppressWildcards and @ JvmWildcard
Used to indicate whether the compiler generates or omits wildcards for type parameters, JvmSuppressWildcards for parameter generics and JvmWildcard for return value types. The JvmSuppressWildcards annotation has a suppress attribute, which indicates whether a wildcard is generated. The suppress attribute is true, false, and true by default.
Such as:
/ / kotlin code
interface ICovert {
fun covertData(datas: List<@JvmSuppressWildcards(suppress = false) String>) // @jVMSuppresswildCardsd Used for parameter types
fun getData(a): List<@JvmWildcard String> // @jvmwildcard is used to return the value type
}
Copy the code
Write an interface implementation class in Java:
/ / Java code
class CovertImpl implements ICovert {
@Override
public void covertData(List<? extends String> datas) { // Parameter types generate wildcards
}
@Override
public List<? extends String> getData() { // The return value type generates wildcards
return null; }}Copy the code
If JvmSuppressWildcards suppress is true:
/ / kotlin code
interface ICovert {
/ / 👇
fun covertData(datas: List<@JvmSuppressWildcards(suppress = true) String>) // @jVMSuppresswildCardsd Used for parameter types
fun getData(a): List<@JvmWildcard String> // @jvmwildcard is used to return the value type
}
Copy the code
Write an interface implementation class in Java:
/ / Java code
class CovertImpl implements ICovert {
@Override
// 👇 there are no wildcards
public void covertData(List<String> datas) {// Parameter types generate wildcards
}
@Override
public List<? extends String> getData() {// The return value type generates wildcards
return null; }}Copy the code
@JvmSynthetic
This annotation marks the appropriate elements as Synthetic in the generated class file, and any elements marked as Synthetic by the compiler will not be accessible from the Java language.
Synthetic property: The ACC_SYNTHETIC property of the JVM bytecode identifier is used to indicate that the element does not actually exist in the original source code, but is generated by the compiler.
Purpose: Composite properties are generally used to support code generation, allowing the compiler to generate fields and methods that should not be exposed to other developers but need to support the actual exposed interface.
@Throws
This annotation is used for functions in Kotlin, setters or getters for properties, and constructor functions to throw exceptions.
@Throws(IOException::class) The 👈 annotation argument is the class to throw the exception
fun closeQuietly(output: Writer?) { output? .close() }Copy the code
@Transient
This annotation is equivalent to the Transient keyword in Java
@Strictfp
This annotation is equivalent to the StrictFP keyword in Java
@Synchronized
This annotation is equivalent to the Synchronized keyword in Java
@Volatile
This annotation is equivalent to the Volatile keyword in Java
Application of annotations
- Provide information to the compiler: The compiler can use annotations to handle things like warnings, errors, etc
- Compile-time processing: Generating code using annotation information is common in Annotation processors, Kapt, Kotlin, and other built-in annotations that generate additional code at compile time for interoperability with Java apis.
- Runtime processing: Some annotations can be used to process some program logic while the program is running, using reflection to retrieve annotation information.
The resources
Java Annotations
How do you fully parse annotations in Kotlin