Kotlin and Java reflection
- 1) Kotlin's
KClass
and Java'sClass
can be regarded as types with the same meaning, and can be passed.java
and.kotlin
methods convert betweenKClass
andClass
. - 2) Both Kotlin’s
KCallable
and Java’sAccessiableObject
can be understood as callable elements. The constructor in Java is an independent type, but in Kotlin it is uniformly treated asKFunction
. - 3) Kotlin’s
KProperty
and Java’sField
are not quite the same. Kotlin'sKProperty
usually refers to the correspondingGetter
andSetter
(only mutable attributesSetter
) overall As aKProperty
(usually Kotlin does not have the concept of a field), and Java'sField
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
KClass
Special 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 |
object Instance (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 Property
Funciton
KCallable
Class
Class
KClass
provides us with a members
method, and its return value is Collection<KCallable<*>>
.
KCallable
API 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 |
classifier
The 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. class
callable
List<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 (
KClass
some Java styleClass
); - Other notes;
- Array of the above type. Note that the basic type array needs to be specified as the corresponding
XXXArray
, such asIntArray
, notArray<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
@Documented
This annotation must appear in the documentation (usually the API documentation).@Inherited
If the superclass is annotated with this type, its subtypes will also be automatically annotated with this annotation without having to specify it.@Repeatable
This annotation can appear multiple times at the same location.@Retention
Indicates 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
.class
The 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.
@Target
Indicates 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
@Target
As 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.
@Target
Prototype:
@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
).
@Retention
Prototype:
@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,@CacheKey
Where 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 |
CacheKey Annotations apply to files |
@property:CacheKey |
CacheKey Annotations act on properties |
@field:CacheKey |
CacheKey Annotations act on fields |
@get:CacheKey |
CacheKey Annotations apply to Getters |
@set:CacheKey |
CacheKey Annotations apply to Setter |
@receiver:CacheKey |
CacheKey Annotations act on extension functions or properties |
@param:CacheKey |
CacheKey Annotations act on constructor parameters |
@setparam:CacheKey |
CacheKey Annotation functions as Setter parameters |
@delegate:CacheKey |
CacheKey Annotations 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()
}
}
withLock
The 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)
}