Kotlin Topic "Twenty Four": Notes

Preface: If you walk every day, you will not be afraid of thousands of miles; if you do it often, you will not be afraid of thousands of things.

I. Overview

  So far, you've seen a lot of features about classes and functions, but all of them require that the exact names of these classes and functions be stated when they are used, as part of the program code. To call a function, you need to know which class it is defined in, as well as its name and parameter types. Annotations have the ability to override this rule and let you write code that uses arbitrary classes that are not known in advance. These libraries can be given specific semantics using annotations.

Using annotations is pretty straightforward, but writing your own annotations and especially writing code to handle them is not so simple. The syntax for using annotations is exactly the same as in Java, but the syntax for declaring your own annotated classes is slightly different. The following is a detailed introduction to the application and definition of annotations , how to allow you to customize specific classes and properties to be serialized and deserialized by a certain library.

Most Java frameworks use annotations, and we often use them. The core concept of Kotlin's annotations is the same. An annotation allows you to associate additional metadata to a declaration, which can then be accessed by relevant source code tools, either through compiled class files or at runtime, depending on how the annotation is configured.

Map of this article:
insert image description here

2. Declaration and Application Notes

2.1 Declaration Notes

To declare an annotation, precede the Class keyword with the annotation modifier annotation. Because annotation classes are just structures used to define metadata associated with declarations and expressions , they cannot contain any code. Therefore, the compiler prohibits specifying a body for an annotated class.

	//声明注解,极简形式
	annotation class Suspendable

(1) For annotations with parameters, declare these parameters in the main constructor of the annotation class (using the regular declaration syntax of the main constructor, for all parameters of an annotation class, the keyword is valmandatory of):

    annotation class Animal(val name: String)

(2) In contrast, how to declare the same annotation in Java:

    // Java
    public @interface AnnNormal {
    
    
        String value();
    }

(3) If you need to annotate the main constructor of a class, you need to add the keyword in the constructor declaration constructor, and add annotations before it:

	//给 Mouse 类添加注解 @Animal
    class Mouse @Animal("m") constructor(val name: String) {
    
     }

(4) Annotations can also be used on lambdas, they will be applied to invoke()the method, and the body of the lambdas will be generated in this method. This is useful for frameworks like Quasar that use annotations for concurrency control.

    annotation class Suspendable

    val light = @Suspendable {
    
    
//        Fiber.sleep(10)
    }

(5) If there are multiple annotations and the same target, you can @set:[]put all the annotations in brackets in the form of a set to avoid repeating the target:

    class Teacher {
    
    
        @set:[Suspendable Animal("no")]//注解 Suspendable 和 Animal
        var name: String = ""
    }

(6) Note: Applying annotations in Kotlin is a regular constructor call. You can make the name of an argument explicit by using named-argument syntax. If you need to declare annotations from Java on Kotlin elements, you must use named-argument syntax for all arguments. Since the order of parameters written in annotations is not defined in Java, the normal function call syntax cannot be used to pass parameters.

    // Java
    public @interface AnnNormal {
    
    
        int age();

        String name();
    }

Call with named arguments:

	//kotlin 
    @AnnNormal(age = 10, value = "Kotlin")
    class Chicken {
    
    }

2.2 Application Notes

  The way to use annotations in Kotlin is the same as in Java. To reference an annotation, @prefix the (annotation) name with the character and place it at the beginning of the declaration you want to annotate . Different code elements such as functions and classes can be annotated.

(1) For example, using the JUnit framework (JUnit-related jar packages have been imported in the project), use @Testto mark a method:

    @Test fun testMethod() {
    
    
        Assert.assertTrue(true)
    }

The @Testannotation instructs the JUnit framework to testMethod()call this method as a test.

(2) For example @Deprecated, annotations have the same meaning in Kotlin as in Java. (If an annotation is used as a parameter of another annotation, its name is not prefixed with the @ character, as in the following ReplaceWithannotations)

@Target(CLASS, FUNCTION, PROPERTY, ANNOTATION_CLASS, CONSTRUCTOR, PROPERTY_SETTER, PROPERTY_GETTER, TYPEALIAS)
@MustBeDocumented
public annotation class Deprecated(
    val message: String,
    val replaceWith: ReplaceWith = ReplaceWith(""),
    val level: DeprecationLevel = DeprecationLevel.WARNING
)

But it's enhanced in Kotlin with the replaceWith parameter, allowing you to provide a replacement matching pattern to support a smooth transition to new versions of the API. Here's how to provide arguments to this annotation (a message indicating that the function is deprecated and a replacement pattern):

    @Deprecated("Use removeAt(index) instead", ReplaceWith("removeAt(index)"))
    fun remove(index: Int) {
    
    
        //TODO
    }
	//替代方法
    fun removeAt(index: Int) {
    
    
        //TODO
    }

Arguments are passed in parentheses, just like regular function calls. After the annotation is declared above, after someone calls remove(index: Int)the function, it will be prompted to use that function to replace it (the above is removeAt), and an automatic quick correction will also be provided.

	remove(0)

Call remove(0), you can see that the method is declared as obsolete, and it prompts: Use removeAt(index) instead, you can use the Android studio shortcut key Alt+Enterto select removeAt(index)Quick fix:
insert image description here
(3) Annotation constructor parameter types can only have the following types: basic data types, strings, enumerations Examples, class references, other annotation classes, and arrays of the preceding types . The syntax for specifying annotation arguments is slightly different from Java:

  • To specify a class as an annotation argument , add it after the class name, in the format: 类名::class, for example: @Test MyClass::class;
  • To specify an annotation as an actual parameter of another annotation , remove @the symbol in front of the annotation name. For example, ReplaceWith in the above example is an annotation, and you specify it as an @Deprecatedactual parameter without @the symbol;
  • To specify an array as an argument to an annotation , use arrayOf()function: @RequestMapping(path = arrayOf("/foo", "/bar")). If the annotated class is declared in Java, the parameter named value is automatically converted to a variable-length parameter as needed, so arrayOf()multiple arguments can be provided without a function.

Note: Annotation parameters cannot have a nullable type, because the JVM does not support storing nullas the value of an annotation attribute.

Annotation arguments need to be known at compile time, so you cannot reference arbitrary properties as arguments. If you want to use an attribute as an annotation argument, this attribute needs to be constdecorated with keywords to tell the compiler that it is a compile-time constant. The JUnit annotation is used below @Test, and timeout is used to specify the test timeout period in milliseconds:

	const val TIMEOUT = 100L//顶层

    @Test(timeout = TIMEOUT)
    fun testMethod() {
    
    
        Assert.assertTrue(true)
    }

Attributes modified with constcan be marked at the top level of the file or in an object, and must be initialized to values ​​of basic data types and String types. If you use ordinary attributes as annotation arguments, an error will be reported: annotation parameters must be compile-time constants.

2.3 Annotation goals

In many cases, a single declaration in Kotlin source corresponds to multiple Java declarations, and each of them can carry annotations. A Kotlin property corresponds to a Java field, a getter, and potentially a setter and its parameters. The attributes declared in a primary constructor have more corresponding elements: the parameters of the constructor. Therefore, it is necessary to explain which elements need annotations.

Use point target declarations are used to specify the elements to be annotated . The dot target is placed @between the symbol and the annotation name, and :separated by a colon and the annotation name, such as the following dot target get, which causes the annotation @Ruleto be applied to the getter of the property:
insert image description here
(1) Example of use: each test method can be specified in JUnit Rules that are executed before execution. Standard TemporaryFolder rules are used to create files and folders and delete them after testing. To specify a rule, you need to declare an annotated public field or method in Java @Rule. If you just annotate the property folder in the Kotlin test class @Rule, to apply it to the public getter, write it out explicitly @get:Rule, as follows:

class AnnotationsActivity{
    
    
    //@Rule注解应用到属性 getter 中,注解的是 getter 而不是属性
    @get:Rule
    val folder = TemporaryFolder()

    @Test
    fun testTemporaryFolder() {
    
    
        val file = folder.newFile("myFile.txt")
    }
}

If you annotate a property with an annotation declared in Java, it will be applied to the corresponding field by default, Kotlin also allows you to declare annotations that are directly mapped to the property :

  • file: the class containing the top-level functions and properties declared in the file;
  • property: Annotations with this target are invisible to Java;
  • field: fields generated for attributes;
  • get: getter of the property;
  • set: setter of the attribute;
  • receiver: the receiver parameter of an extension function or extension property;
  • param: constructor parameter;
  • setparam: The parameter of the attribute setter;
  • delegate: The field that stores the delegate instance for the delegate property.

Any annotations that use filethe target should be placed at the top level of the file, before the package directive. @JvmNameis a more common annotation applied to files,

@file:JvmName("foo")

package com.suming.kotlindemo.blog

Note: Unlike Java, Kotlin allows you to annotate arbitrary expressions, not just class and function declarations and types.

(2) For example: @Suppressannotations, which can be used to suppress compilation warnings for annotated elements. Here's an example of annotating a local variable declaration, suppressing warnings for unchecked conversions:

    fun suppressMethod(list: List<*>) {
    
    
    	//代码黄色警告Unchecked_cast:List<*> to List<String>
		val strings = list as List<String>

		//警告消失,被抑制
        @Suppress("Unchecked_cast")//names 表示要抑制的编译器诊断程序的名称
        val strings2 = list as List<String>
    }

Reminder: When this warning appears in Android studio, press Alt+Enterthe key combination to select SuppressSuppress, and this annotation will be inserted for you. As shown below:
insert image description here

Control Java API with annotations

Kotlin provides various annotations to control how declarations written in Kotlin are compiled into bytecode and exposed to Java callers. Some of these annotations replace the corresponding keywords in the Java language, such as annotations @Volatileand @Strictfpdirectly serve as stand-ins for the Java keywords Volatile and Strictfp. Other annotations are used to change the visibility of Kotlin declarations to Java callers:

  • @JvmName: will change the Java method or field name generated by Kotlin;
  • @JvmStatic: can be used on object declarations or methods of companion objects, exposing them as Java static methods;
  • @JvmOverloads: Specifies that the Kotlin compiler generate multiple overloads (functions) for functions with default parameter values;
  • @JvmField: Can be applied to a property to expose the property as a public Java field without accessors.

2.4 Meta-annotations: Controlling how an annotation is processed

Like Java, Kotlin annotated classes can themselves be annotated. Annotations that can be applied to annotated classes are called meta-annotations . The standard library defines some meta-annotations that control how the compiler handles annotations. Many dependency injection libraries use meta-annotations to mark other annotations, indicating that these annotations can be used to identify different injectable objects of the same type.

The most common is the meta-annotation defined in the standard library @Target. Let's use it to specify valid targets for some annotations. Let's see how it applies to annotations:

    @Target(AnnotationTarget.PROPERTY)
    annotation class Person

@TargetA meta-annotation specifies the type of element to which the annotation can be applied. If it is not used, all declarations can have this annotation applied, but @Personannotations are specified to only deal with attribute annotations ( AnnotationTarget.PROPERTY). The value of the AnnotationTarget enumeration lists all possible targets to which annotations can be applied, including: classes, files, functions, properties, property accessors, all expressions, etc. You can also declare multiple targets if required @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION).

To declare your own meta-annotations, use AnnotationTarget.CLASSas target.

    //元注解
    @Target(AnnotationTarget.CLASS)
    annotation class AnnBinding

    @AnnBinding
    annotation class MyBanding//注解 MyBanding 应用元注解 AnnBinding

Note: Annotations whose target is PROPERTY cannot be used in Java code. You need to add a second target to it before it can be AnnotationTarget.FIELDused in Java, so that annotations can be applied to both Kotlin properties and Java fields.

Additional properties of annotations can annotationbe specified with meta-annotations on the class:

  • @Target:   Specifies the element type that may be annotated (class, function, attribute, expression, etc.);
  • @Retention:  Specify whether the annotations are stored in the compiled class file, and whether they are visible through reflection at runtime (by default, both are true), Java defaults .classto retain annotations in the file but does not let them at runtime was visited. Most annotations do need to exist at runtime, Kotlin's default behavior is different: annotations have a RUNTIME retention period;
  • @Repeatable:  Allows the same annotation to be used multiple times on a single element;
  • @MustBeDocumented:  Specifies that the annotation is part of the public API and should be included in the class or method signature shown in the generated API documentation.

3. Annotation parameters

3.1 Use classes as annotation parameters

Knowing how to define annotations that hold static data as their parameters, you can also refer to classes as declared metadata . This is done by declaring an annotation that has a class reference as a formal parameter . For example, @DeserializeInterfacein annotations it allows you to control the deserialization of those interface type properties. An instance of an interface cannot be created directly, so it is necessary to specify which class is created as the implementation when deserializing.

    annotation class DeserializeInterface(val cla: KClass<out Any>)

When it reads a nested company object of a Student class instance, it creates and deserializes an instance of CompanyImpl and stores it in the company property. The use CompanyImpl::classof as @DeserializeInterfacean argument to the annotation illustrates this. (Use 类名::classthe keyword to refer to a class)

    interface Company {
    
    
        val name: String
    }

    data class CompanyImpl(override val name: String) : Company

    data class Student(
            val name: String,
            @DeserializeInterface(CompanyImpl::class) val company: Company
    )

KClass is similar to Java's java.lang.Class, which is used to store references to Kotlin classes. The type parameter of KClass indicates which Kotlin classes this reference can point to, and the above is a subtype of CompanyImpl::class(KClass<CompanyImpl>)the annotation parameter type ( ).KClass<out Any>

Note: KClass<out Any>The out modifier needs to be used, otherwise it cannot be passed CompanyImpl::classas an actual parameter, the only allowed actual parameter will be Any::class, the out keyword allows to refer to those classes that inherit Any, not just Any itself.

3.2 Use generic classes as annotation parameters

Sometimes properties of non-primitive data types are serialized as nested objects, but you can change this behavior and provide your own serialization logic for certain values.

The following @CustomSerializerannotation receives as an argument a reference to a custom serializer class that should implement the ValueSerializer interface:

    interface ValueSerializer<T> {
    
    
        fun toJsonValue(value: T): Any?
        fun fromJsonValue(jsonValue: Any): T?
    }

	//注解
    annotation class CustomSerializer(
            val cla: KClass<out ValueSerializer<*>>
    )

@CustomSerializerHow is the annotation declared? The ValueSerializer interface is generic and defines a type parameter. When you refer to this type, you need to provide a type parameter value, but you do not know the information about the attribute type to which this annotation is applied. An asterisk cast can be used as a type argument ( *).

Let's say you need to support serializing dates, and for that you create a DataSerializer class, which implements ValueSerializer<Date>the interface

    data class Rabbit(
            val name: String,
            @CustomSerializer(DataSerializer::class) val data: ContactsContract.Data
    )

You need to ensure that annotations can only refer to classes that implement the ValueSerializer interface. For example, the above @CustomSerializer(Data::class)writing method is not allowed because Date does not implement the ValueSerializer interface.
insert image description here
The type of the SerializerClass annotation parameter, the class reference pointing to the ValueSerializer implementation class will be a valid annotation argument

Is it troublesome? The advantage is that the same pattern can be applied every time a class needs to be used as an annotation argument, which can be written like this. KClass<out className>If className has its own type argument, it can be replaced by *.

Four. Summary

insert image description here

Source address: https://github.com/FollowExcellence/KotlinDemo-master

Pay attention, don't get lost


Well, everyone, the above is the whole content of this article. The people who can see here are all talents .

I am suming, thank you for your support and recognition, your likes are the biggest motivation for my creation, see you in the next article!

If there are any mistakes in this blog, please criticize and advise, thank you very much!

If you want to become an excellent Android developer, here is the knowledge framework you must master , and move towards your dream step by step! Keep Moving! .

Guess you like

Origin blog.csdn.net/m0_37796683/article/details/108646530