“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 implementedAnnotationinterface
  • One annotation will correspond to anotherRetentionPolicy(Each annotation has a unique RetentionPolicy property), corresponding to@RetentionThe meaning of this annotation indicates how an annotation is saved (lifecycle).
  • One annotation can correspond to 1 to NElementType(Each annotation can have multiple ElementType attributes), corresponding to@TargetThe 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
  • @interfaceIs 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@interfaceTo 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

  1. Provide information to the compiler: The compiler can use annotations to handle things like warnings, errors, etc
  2. 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.
  3. 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