"Kotlin Core Programming" Notes: Reflection, Annotations and Locking

Kotlin and Java reflection

Insert image description here
Insert image description here

  • 1) Kotlin's KClass and Java's Class can be regarded as types with the same meaning, and can be passed .java and .kotlin methods convert between KClass and Class.
  • 2) Both Kotlin’s KCallable and Java’s AccessiableObject can be understood as callable elements. The constructor in Java is an independent type, but in Kotlin it is uniformly treated as KFunction.
  • 3) Kotlin’s KProperty and Java’s Field are not quite the same. Kotlin's KProperty usually refers to the corresponding Getter and Setter (only mutable attributes Setter) overall As a KProperty (usually Kotlin does not have the concept of a field), and Java's Field usually only refers to the field itself.

In some cases (usually when encountering some Kotlin-unique features) the Kotlin compiler stores additional information in the produced bytecode, which is currently passedkotlin.Metadata Solution realized. The Kotlin compiler will annotate these classes with Metadata.

KClass in Kotlin

KClassSpecial attributes or functions (unique in Kotlin, Java has no corresponding features):

Property or function name meaning
isCompanion Whether it is a companion object
isData Whether data class
isSealed Whether to seal the class
objectInstance objectInstance (if object)
companionObjectInstance companion object instance
declaredMemberExtensionFunctions Extension function (declared)
declaredMemberExtensionProperties Extended attributes (declared)
memberExtensionFunctions Extension functions of this class and super class
memberExtensionProperties Extended attributes of this class and superclass
starProjectedType Generic wildcard type

Kotlin 的 KCallable

Kotlin treats the properties (), functions () and even constructors in Class as < /span>? . So how do we get a member of , because they are callable, they are all members of PropertyFuncitonKCallableClassClass

KClass provides us with a members method, and its return value is Collection<KCallable<*>>.

KCallableAPI provided:

API description meaning
isAbstract: Boolean This KCallable is abstract
isFinal: Boolean This KCallable Right or wrong final Target
isOpen: Boolean This KCallable Right or wrong open Target
name: String The name of this KCallable
parameters: List<KParameter> Call this KCallable Required parameters
returnType: KType The return type of this KCallable
typeParameters: List<KTypeParameter> Type parameters of this KCallable
visibility: KVisibility? Visibility of this KCallable
call(vararg args: Any?): R Call this given argumentsKCallable

KMutableProperty is a subclass of KProperty, so how do we identify whether an attribute is KMutableProperty or KProperty? Refer to the following code:

fun KMutablePropertyShow() {
    
    
    val p = Person("张三", 8, "HangZhou")
    val props = p::class.memberProperties
    for (prop in props) {
    
    
        when (prop) {
    
    
            is KMutableProperty<*> -> prop.setter.call(p, "Hefei")
            else -> prop.call(p)
        }
    }
    println(p.address)
}

Get parameter information

Kotlin divides parameters into three categories, namely function parameters (KParameter), function return values ​​(KType) and type parameters ( KTypeParameter).

KParameter

UseKCallabel.parameters to get aList<KParameter>, which represents the parameters of the function (including extension functions).

API description meaning
index: Int Returns the index of the parameter in the parameter list
isOptional: Boolean Is this parameterOptional
isVararg: Boolean Is this parametervararg
kind: Kind of this parameterKind
name: String? The name of the parameter
type: KType The type of the parameter
fun KParameterShow() {
    
    
    // val p = Person("张三", 8, "HangZhou")
    for (c in Person::class.members) {
    
    
        print("${
      
      c.name} -> ")
        for (p in c.parameters) {
    
    
            print("${
      
      p.type}" + " -- ")
        }
        println()
    }
}

operation result:

address -> Person 
name -> Person
detailAddress -> Person,kotlin.String 
isChild -> Person
equals -> kotlin.Any,kotlin.Any? 
hashCode -> kotlin.Any
toString -> kotlin.Any

Through the above running results, we found that for attributes and parameterless functions, they have a hidden parameter that is an instance of the class, and for functions that declare parameters, the instance of the class is used as the first parameter, and The declared parameters are used as subsequent parameters. For parameters inherited from Any, Kotlin defaults to Any as the first parameter.

KType

EveryKCallabe can usereturnType to get the return value type, and its result type is oneKType, Represents types in Kotlin.

API description meaning
arguments: List<KTypeProjection> type parameters of this type
classifier: KClassifier? gets the type that results in List (ignoring type parameters)
isMarkedNullable: Boolean Whether the type is marked as nullable

classifierThe API actually obtains the corresponding type of the parameter at the class level, such as Int -> class kotlin.Int, List<String> -> class kotlin.collections.List.

KTypeParameter

In KClass and KCallable we can get and typeParameters The type parameter of , the result set it returns is , if there is no type parameter, an empty is returned. classcallableList<KTypeParameter>List

fun <A> get(a: A) : A {
    
     
	return a
}

Then we can use the following code to get the type parameters of the get method and List<String>:

fun KTypeParameterShow() {
    
    
    for (c in Person::class.members) {
    
    
        if (c.name.equals("get")) {
    
    
            println(c.typeParameters)
        }
    }
    
    val list = listOf<String>("How")
    println(list::class.typeParameters)
}

operation result:

[A]
[E]

Annotations for Kotlin

We mentioned annotations earlierkotlin.Metadata, which are the key to realizing most of Kotlin’s unique feature reflections. Kotlin stores this information directly in the form of annotations inBytecode file so that runtime reflection can obtain these data.

Since Kotlin is compatible with Java, Kotlin can also add annotations wherever Java can. And Kotlin has also simplified the annotation creation syntax. Creating annotations is as simple as creating class, just add before class Just keywords. annotation

annotation class FooAnnotation(val bar: String) 

The above code directly creates theFooAnnotation annotation, just like creating other Kotlin classes. As mentioned before, just add annotation in front of it. , this class becomes an annotation, which is indeed much simpler than the equivalent Java code.

At the same time, like Java, the parameters of annotations can only be constants and only support the following types:

  • Basic types corresponding to Java;
  • string;
  • Class example (KClasssome Java styleClass);
  • Other notes;
  • Array of the above type. Note that the basic type array needs to be specified as the corresponding XXXArray, such as IntArray, not Array<Int>.

meta-annotation

Annotations marked on annotations like@Target are called meta-annotations. We know that the package in Java defines the following 5 meta-annotations: java.lang.annotation

  • @DocumentedThis annotation must appear in the documentation (usually the API documentation).
  • @InheritedIf the superclass is annotated with this type, its subtypes will also be automatically annotated with this annotation without having to specify it.
  • @RepeatableThis annotation can appear multiple times at the same location.
  • @RetentionIndicates the purpose of annotation, and has three values.
    • Source. It only exists in the source code, and the compiled file does not contain this annotation information. class
    • CLASS. classThe annotation exists in the file, but cannot be read by reflection.
    • RUNTIME. Annotation information is also stored inclass files and can be obtained through reflection at runtime.
  • @TargetIndicates where the annotation can be applied.

Like Java, there is also a corresponding meta-annotation class in Kotlin. The meta-annotation classes in Kotlin are defined under the kotlin.annotation package, mainly including:

Kotlin Java meaning
@Retention @Retention Annotation retention period
@Target @Target For whom annotations are available
@MustBeDocumented @Documented Annotations will be extracted into API documentation by documentation tools
@Repeatable @Repeatable Annotations can be applied multiple times to the same declaration or type

Notice that there are 5 fewer meta-annotations than in Java@Inherited, and Kotlin currently does not support itInherited. In theory, it is not very difficult to implement inheritance. , but the current version does not support it.

Through the above comparison, we found that Kotlin and Java annotations are generally consistent. Readers who are familiar with Java annotations should easily transfer this knowledge to Kotlin.

@Target

@TargetAs the name implies, it is the target object, that is, which target objects the annotations we define can be applied to. Multiple target objects can be specified at the same time.

@TargetPrototype:

@Target(AnnotationTarget.ANNOTATION_CLASS)
@MustBeDocumented
public annotation class Target(vararg val allowedTargets : AnnotationTarget) 

We can see from the prototype of @Target that it accepts a vararg variable number of parameters, so multiple targets can be specified at the same time. object, and the parameter type is qualified to AnnotationTarget.

@Retention

@Retention can be understood as a retention period. Like Java, Kotlin has three periods: source code period (SOURCE), compilation period (BINARY), and runtime. Period (RUNTIME).

@RetentionPrototype:

@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Retention(val value : AnnotationRetention = AnnotationRetention.RUNTIME) 

Retention receives a parameter of type AnnotationRetention, which has a default value. The default value is retained during runtime. AnnotationRetention is an enumeration class, which is defined as follows:

public enum class AnnotationRetention {
    
    
	// Annotation isn't stored in binary output 
	SOURCE,
	// Annotation is stored in binary output, but invisible for reflection 
	BINARY,
	// Annotation is stored in binary output and visible for reflection (default retention) 
	RUNTIME
}

basically corresponds to the three types of Java, except that the default value in Kotlin is RUNTIME.

AnnotationTarget

As mentioned earlier, one or more target objects can be specified in the @Target meta-annotation at the same time. So what are the target objects? Let’s take a look next:

Kotlin (Annotation Target) Java (Target) illustrate
CLASS TYPE Acts on class
ANNOTATION_CLASS ANNOTATION_TYPE Acts on the annotation itself (i.e. meta-annotation)
TYPE_PARAMETER TYPE_PARAMETER Acts on type parameters
PROPERTY N/A Acts on properties
FIELD FIELD acts on fields (properties usually include fields Getter and Setter)
LOCAL_VARIABLE FIELD Acts on local variables
VALUE_PARAMETER N/A acts on val parameter
CONSTRUCTOR CONSTRUCTOR Acts on constructor
FUNCTION METHOD acts on functions (Java onlyMethod)
PROPERTY_GETTER N/A Acting onGetter
PROPERTY_SETTER N/A Acting onSetter
TYPE TYPE_USE Acts on type
EXPRESSION N/A Acts on expressions
FILE PACKAGE Acts on the beginning of the file/package declaration (there is a slight difference between the two)
TYPEALIAS N/A Acts on type aliases

Kotlin supports almost all annotation positions supported by Java, and adds some kotlin-unique positions.

An example of using simple Kotlin annotations:

annotation class Cache(val namespace: String, val expires: Int)
annotation class CacheKey(val keyName: String, val buckets: IntArray)

@Cache(namespace = "hero", expires = 3600)
data class Hero(
    @CacheKey(keyName = "heroName", buckets = intArrayOf(1,2,3))
    val name: String,
    val attack: Int,
    val defense: Int,
    val initHp: Int
)

Kotlin code often expresses multiple meanings. For example, in addition to generating an immutable field, name in the above example actually contains Getter and is a parameter of its constructor. .

This brings up a question,@CacheKeyWhere does the annotation work?

Precise control over annotation placement

In order to solve this problem, Kotlin introduces precise annotation control syntax. If we have annotationsannotation class CacheKey

usage meaning
@file:CacheKey CacheKeyAnnotations apply to files
@property:CacheKey CacheKeyAnnotations act on properties
@field:CacheKey CacheKeyAnnotations act on fields
@get:CacheKey CacheKeyAnnotations apply to Getters
@set:CacheKey CacheKeyAnnotations apply to Setter
@receiver:CacheKey CacheKeyAnnotations act on extension functions or properties
@param:CacheKey CacheKeyAnnotations act on constructor parameters
@setparam:CacheKey CacheKeyAnnotation functions as Setter parameters
@delegate:CacheKey CacheKeyAnnotations act on fields that store proxy instances

For example:

@Cache(namespace = "hero", expires = 3600)
data class Hero(
    @property:CacheKey(keyName = "heroName", buckets = [1, 2])
    val name: String,

    @field:CacheKey(keyName = "atk", buckets = [1, 2, 3])
    val attack: Int,
        @get:CacheKey(keyName = "def", buckets = [1, 2, 3])

    val defense: Int,
    val initHp: Int
)

The aboveCacheKey applies to attributes, fields and Getter respectively.

Reflection to obtain annotation information

The prerequisite for this is that the annotationRetentaion is marked as Runtime or is not explicitly specified (the annotation defaults to Runtime ).

annotation class Cache(val namespace: String, val expires: Int)
annotation class CacheKey(val keyName: String, val buckets: IntArray)

@Cache(namespace = "hero", expires = 3600)
data class Hero(
    @CacheKey(keyName = "heroName", buckets = [1, 2, 3])
    val name: String,
    val attack: Int,
    val defense: Int,
    val initHp: Int
)

fun main() {
    
    
    val cacheAnnotation = Hero::class.annotations.find{
    
     it is Cache } as Cache?
    println("namespace ${
      
      cacheAnnotation?.namespace}")
    println("expires ${
      
      cacheAnnotation?.expires}")
}

Obtaining annotation information through reflection occurs at runtime, and there is a certain performance overhead like Java. Of course, this overhead can be ignored most of the time. In addition, the annotation annotation position mentioned earlier will also affect the acquisition of annotation information. For example, if an annotation is marked as @file:CacheKey, the annotation information cannot be obtained by calling KProperty.annotions.

Annotation usage scenarios

  • Provide information to the compiler: The compiler can use annotations to process some, such as some warning messages, errors, etc.
  • Processing during the compilation phase: Use annotation information to generate some code. Generating code is very common in Kotlin. Some built-in annotations often use annotations to generate some additional code during the compilation phase for interoperability with Java APIs.
  • Runtime processing: Some annotations can obtain annotation information through the reflection mechanism when the program is running to process some program logic.

The following is a code example that uses annotations to annotate HTTP request methods:

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class HttpMethod(val method : Method) 

interface Api {
    
    
    val name: String
    val version: String
        get() = "1.0"
}

@HttpMethod(Method.POST)
class ApiGetArticles : Api {
    
    
    override val name: String
        get() = "/api.articles"
}

fun fire(api: Api) {
    
    
    val annotations = api.javaClass.annotations
    val method = annotations.find {
    
     it is HttpMethod } as? HttpMethod
    println("通过注解得知该接口需需要通过:${
      
      method?.method}方式请求")
}

We know that the famous network request library Retrofit uses this method to mark the methods, paths, parameters and other information of interface requests.

Lock

Although Kotlin is an improved language based on Java, it does not have the synchronized keyword. Instead, it uses @Synchronized annotations and synchronized() function to achieve the same effect. For example:

class Shop {
    
    
    val goods = hashMapOf<Long,Int>()
    init {
    
    
        goods.put(1,10)
        goods.put(2,15)
    }
    @Synchronized
    fun buyGoods(id: Long) {
    
    
        val stock = goods.getValue(id)
        goods.put(id, stock - 1)
    }
    fun buyGoods2(id: Long) {
    
    
        synchronized(this) {
    
    
            val stock = goods.getValue(id)
            goods.put(id, stock - 1)
        }
    }
}

Note that synchronized(this) here is a method in kotlin, not the synchronized keyword in java.

In addition to supporting the concurrency primitive synchronized in Java, Kotlin also supports other concurrency tools, such as the volatile keyword, keyword has also become annotation in Kotlin: java.util.concurrent.*The following concurrency tools. Of course, Kotlin has also made some changes. For example, the volatile

@Volatile private var running = false

In addition to using synchronized to lock the code synchronously, in Java you can also use Lock to lock the code synchronously. Lock. So let’s try to transform the above buyGoods method:

var lock: Lock = ReentrantLock()

fun buyGoods(id: Long) {
    
    
    lock.lock()
    try {
    
    
        val stock = goods.getValue(id)
        goods.put(id, stock - 1)
    } catch (ex: Exception) {
    
    
        println("[Exception] is ${
      
      ex}")
    } finally {
    
    
        lock.unlock()
    }
}

But this way of writing seems to have the following disadvantages:

  • If there are multiple synchronized methods in the same class, they will compete for the same lock;
  • After locking, it is easy for coders to forget to unlock;
  • Duplicate template code.

So, let’s now try to improve it and increase the abstraction of this method:

fun <T> withLock (lock: Lock, action: () -> T): T {
    
    
    lock.lock()
    try{
    
    
        return action()
    } catch (ex: Exception) {
    
    
        println("[Exception] is ${
      
      ex}")
    } finally {
    
    
        lock.unlock()
    }
}

withLockThe method supports passing in a lock object and an Lamada expression, so we don’t need to worry about buyGoods now After locking, you only need to pass in an lock object when calling.

fun buyGoods(id: Long) {
    
    
    val stock = goods.getValue(id)
    goods.put(id, stock - 1)
}

var lock: Lock = ReentrantLock()
withLock(lock) {
    
    
	buyGoods(1)
}

Support for this method has also been added by default in the Kotlin class library:

var lock: Lock = ReentrantLock()
lock.withLock {
    
     
	buyGoods(1)
}

Guess you like

Origin blog.csdn.net/lyabc123456/article/details/135036752