新手上路,Kotlin学习笔记(九)---注解和反射

        入行没几年的小码农,近期学习Kotlin,做一份笔记记录,此文依据《Kotlin实战》这本书的流程记录,部分示例内容均摘自《Kotlin实战》,记下自己的理解,本篇将记录注解和反射的知识点。



        Kotlin学习笔记系列

        新手上路,Kotlin学习笔记(一)-- Kotlin入门介绍

        新手上路,Kotlin学习笔记(二)---方法(函数)部分

        新手上路,Kotlin学习笔记(三)---类、对象、接口

        新手上路,Kotlin学习笔记(四)---Lambda表达式在Kotlin中的使用

        新手上路,Kotlin学习笔记(五)---Kotlin中的类型系统

        新手上路,Kotlin学习笔记(六)---运算符重载和其它约定

        新手上路,Kotlin学习笔记(七)---Lambda作为形参和返回值的使用

        新手上路,Kotlin学习笔记(八)---泛型的使用

        新手上路,Kotlin学习笔记(九)---注解和反射



一、注解

        1、注解的使用方式

        和java中一样,注解使用的时候使用@符号作为前缀,但是在注解实参的使用上,和Java有一些不同之处

        (1)把一个类指定为注解实参的时候,需要加上::class ,如 @MyAnnotation(MyClass::class)

        (2)将另一个注解指定为注解实参的时候,需要去掉注解前的@符号,如下面ReplaceWith是另一个注解

新手上路,Kotlin学习笔记(九)---注解和反射_第1张图片

        在Kotlin中如果使用Deprecated注解,还可以在其实参中使用ReplaceWith注解,这样使用后,在调用过期的方法时,将会有使用新方法的快速修正功能。

        (3)把一个数组指定为一个实参的时候,使用arrayOf函数,如@RequestMapping(path= arrayOf("/foo", "/bar"))。如果注解类是在Java中声明的,命名为value的形参按需自动地被转换成可变长度的形参,所以不用arrayOf函数就可以提供多个实参。

        注解的实参需要在编译期就是已知的,所以如果要把一个属性当做注解使用,必须用const编辑,并且必须是基本数据类型或者String类型的值。

        2、注解的对象

        在Kotlin中,我们前面了解到,一个属性可能对应多个Java声明,如一个属性就对应一个Java字段、get方法、set方法和其参数。所以我们在指定注解对象的时候,就需要使用点目标来确定要对哪个对象注解。

        使用点目标声明要被注解的对象,将要注解的内容放在@符号和注解名之间,并用冒号 : 分隔,如@get:Rule就是对get方法使用Rule注解

         如果使用Java中的声明的注解来注解一个属性,会默认的应用到相应的字段上,Kotlin中也可以指定对应属性的注解,其对应关系如下:

        property            Java的注解不能应用这种使用点目标

        field                   为属性生成的字段

        get                    属性的getter

        set                    属性的setter

        receiver            扩展函数或者扩展属性的接收者参数

        param               构造方法的参数

        setparam          属性setter的参数

        delegate           为委托属性存储委托实例的字段

       file                    包含在文件中声名的顶层函数和属性的类(任何应用到file目标的注解都必须放在文件的顶层,放在package指令之前)

        tips:Kotlin允许对任意的表达式声明注解,而不仅仅是类和函数的声明及类型。

        3、声明注解

        现在我们看如何声明一个注解,代码如下

@Target(allowedTargets = AnnotationTarget.FIELD)
annotation class JsonName(val name: String)//如果没有参数,可以省略括号()
        很简单,只需要加上annotation关键字即可,如果有参数,就在后面括号中进行声明。元注解在该注解上方使用即可。


二、反射

        在Java中我们就知道,注解的使用一定跟随着反射,没有反射的注解没有什么实际意义。接下来我们看反射是如何在Kotlin中使用的。

        1、Kotlin中的反射API

        在kotlin的反射中,我们会接触到两种API,java原生的API和Kotlin特有的反射API,它可以解决一些在Java中不存在的概念,如可空类型。

        Kotlin反射API的主要入口是KClass类,它代表一个类,可以获取到该类和其超类中包含的所有声明。MyClass::class这样的写法可以获取到一个KClass实例,如果需要在运行过程中获取一个对象的类,可以先使用javaClass属性获取Java的Class对象,然后访问.kotlin扩展属性获取KClass对象。如 person.javaClass.kotlin

        接下来我们看一下部分源码中的KClass

/**
 * Represents a class and provides introspection capabilities.
 * Instances of this class are obtainable by the `::class` syntax.
 * See the [Kotlin language documentation](http://kotlinlang.org/docs/reference/reflection.html#class-references)
 * for more information.
 *
 * @param T the type of the class.
 */
public interface KClass : KDeclarationContainer, KAnnotatedElement, KClassifier {
    /**
     * The simple name of the class as it was declared in the source code,
     * or `null` if the class has no name (if, for example, it is an anonymous object literal).
     */
    public val simpleName: String?

    /**
     * The fully qualified dot-separated name of the class,
     * or `null` if the class is local or it is an anonymous object literal.
     */
    public val qualifiedName: String?

    /**
     * All functions and properties accessible in this class, including those declared in this class
     * and all of its superclasses. Does not include constructors.
     */
    override val members: Collection>

    /**
     * All constructors declared in this class.
     */
    public val constructors: Collection>
}

        一个类中所有的成员和函数都放在members对象中,他是一个KCallable接口组成的集合,那么我们看下KCallable接口的代码是什么样的。

/**
 * Represents a callable entity, such as a function or a property.
 *
 * @param R return type of the callable.
 */
public interface KCallable : KAnnotatedElement {
    /**
     * The name of this callable as it was declared in the source code.
     * If the callable has no name, a special invented name is created.
     * Nameless callables include:
     * - constructors have the name "",
     * - property accessors: the getter for a property named "foo" will have the name "",
     *   the setter, similarly, will have the name "".
     */
    public val name: String

    /**
     * Parameters required to make a call to this callable.
     * If this callable requires a `this` instance or an extension receiver parameter,
     * they come first in the list in that order.
     */
    public val parameters: List

    /**
     * The type of values returned by this callable.
     */
    public val returnType: KType

    /**
     * The list of type parameters of this callable.
     */
    @SinceKotlin("1.1")
    public val typeParameters: List

    /**
     * Calls this callable with the specified list of arguments and returns the result.
     * Throws an exception if the number of specified arguments is not equal to the size of [parameters],
     * or if their types do not match the types of the parameters.
     */
    public fun call(vararg args: Any?): R

    /**
     * Calls this callable with the specified mapping of parameters to arguments and returns the result.
     * If a parameter is not found in the mapping and is not optional (as per [KParameter.isOptional]),
     * or its type does not match the type of the provided value, an exception is thrown.
     */
    public fun callBy(args: Map): R

    /**
     * Visibility of this callable, or `null` if its visibility cannot be represented in Kotlin.
     */
    @SinceKotlin("1.1")
    public val visibility: KVisibility?

    /**
     * `true` if this callable is `final`.
     */
    @SinceKotlin("1.1")
    public val isFinal: Boolean

    /**
     * `true` if this callable is `open`.
     */
    @SinceKotlin("1.1")
    public val isOpen: Boolean

    /**
     * `true` if this callable is `abstract`.
     */
    @SinceKotlin("1.1")
    public val isAbstract: Boolean
}

        这里面的call就是我们调用这个函数或者这个属性get方法的地方。

        接下来我们看KClass中的constructors,它是一个KFunction的实例组成的集合,这又是什么呢?

/**
 * Represents a function with introspection capabilities.
 */
public interface KFunction : KCallable, Function {
    /**
     * `true` if this function is `inline`.
     * See the [Kotlin language documentation](https://kotlinlang.org/docs/reference/inline-functions.html)
     * for more information.
     */
    @SinceKotlin("1.1")
    public val isInline: Boolean

    /**
     * `true` if this function is `external`.
     * See the [Kotlin language documentation](https://kotlinlang.org/docs/reference/java-interop.html#using-jni-with-kotlin)
     * for more information.
     */
    @SinceKotlin("1.1")
    public val isExternal: Boolean

    /**
     * `true` if this function is `operator`.
     * See the [Kotlin language documentation](https://kotlinlang.org/docs/reference/operator-overloading.html)
     * for more information.
     */
    @SinceKotlin("1.1")
    public val isOperator: Boolean

    /**
     * `true` if this function is `infix`.
     * See the [Kotlin language documentation](https://kotlinlang.org/docs/reference/functions.html#infix-notation)
     * for more information.
     */
    @SinceKotlin("1.1")
    public val isInfix: Boolean

    /**
     * `true` if this is a suspending function.
     */
    @SinceKotlin("1.1")
    public val isSuspend: Boolean
}
        可以看到他实现了KCallable接口,实际上我们在前面学习Lambda的时候,使用的成员引用的形式获取一个方法的引用,如person::showAddr,就是获取到的一个KFunction对象,KFunction同时也实现了Function接口,该接口将会对应FunctionN的接口(这里是自动处理的,源码中暂时未找到相关说明),FunctionN接口会对应指定的参数和返回值,刚好供一个方法使用。如Function2的声明如下,表示两个参数P1,P2和一个返回值R,刚好对应两个参数一个返回值的方法。
// IntelliJ API Decompiler stub source generated from a class file
// Implementation of methods is not available

package kotlin.jvm.functions

public interface Function2 : kotlin.Function {
    public abstract operator fun invoke(p1: P1, p2: P2): R
}
        接着我们就能使用invoke来调用指定的方法,这样做的好处在于不会使用到不存在的方法,使用call的时候如果参数数量不匹配是会抛出异常的。使用示例如下:
    fun sum(a: Int, b: Int) = a + b
    fun reflect() 
    {
        val sumMethod = this::sum
        sumMethod.invoke(1, 2)//使用call的话,如果参数不匹配,将会在运行时抛出异常,而invoke会强制要求参数匹配
    }

        最后我们来看另一个类KProperty,这个类是我们获取属性的时候,实际取到的类型。

/**
 * Represents a property, such as a named `val` or `var` declaration.
 * Instances of this class are obtainable by the `::` operator.
 * See the [Kotlin language documentation](http://kotlinlang.org/docs/reference/reflection.html)
 * for more information.
 *
 * @param R the type of the property.
 */
public interface KProperty : KCallable {
    /**
     * `true` if this property is `lateinit`.
     * See the [Kotlin language documentation](https://kotlinlang.org/docs/reference/properties.html#late-initialized-properties)
     * for more information.
     */
    @SinceKotlin("1.1")
    public val isLateinit: Boolean

    /**
     * `true` if this property is `const`.
     * See the [Kotlin language documentation](https://kotlinlang.org/docs/reference/properties.html#compile-time-constants)
     * for more information.
     */
    @SinceKotlin("1.1")
    public val isConst: Boolean

    /** The getter of this property, used to obtain the value of the property. */
    public val getter: Getter

    /**
     * Represents a property accessor, which is a `get` or `set` method declared alongside the property.
     * See the [Kotlin language documentation](http://kotlinlang.org/docs/reference/properties.html#getters-and-setters)
     * for more information.
     *
     * @param R the type of the property, which it is an accessor of.
     */
    public interface Accessor {
        /** The property which this accessor is originated from. */
        public val property: KProperty
    }

    /**
     * Getter of the property is a `get` method declared alongside the property.
     */
    public interface Getter : Accessor, KFunction
}

/**
 * Represents a property declared as a `var`.
 */
public interface KMutableProperty : KProperty {
    /** The setter of this mutable property, used to change the value of the property. */
    public val setter: Setter

    /**
     * Setter of the property is a `set` method declared alongside the property.
     */
    public interface Setter : KProperty.Accessor, KFunction
}

        它也实现了KCallable接口,不过其实现KProperty0和KProperty1,KProperty2提供了一个get的方法,该方法用于获取该属性的值。KProperty0的get方法没有参数,用于直接获取顶层属性的值。KProperty1的get方法有一个参数,用于获取该属性在对应对象上的值。同理KMutableProperty就是对应的set功能。简单使用示例如下:

    var count = 0//此处是一个顶层属性
    fun reflect()
    {
        val sumMethod = this::sum
        sumMethod.invoke(1, 2)
        val person: Person = Person(age = 18, name = "Bob", addr = "address")
        val kProperty0 = ::count
        kProperty0.get()//此处获count的值
        kProperty0.set(100)//此处设置count的值
        
        val kProperty1 = Person :: addr
        kProperty1.get(person) //此处获取person对象addr的值
        kProperty1.set(person,"new address") //此处设置person对象新的addr的值
    }
        Kotlin的学习笔记到此为止就算结束了,感谢大家的阅读,有错误及不妥之处欢迎留言指正,让我们一起共同学习进步。


        欢迎大家关注我的其他文章,后面会有Kotlin相关的实际使用等文章,也会有Android相关的其它内容。


你可能感兴趣的:(Kotlin)