kotlin学习笔记之注解与反射

news2024/11/12 21:26:47

一、声明并应用注解

         一个注解允许你把额外的元数据关联到一个声明上。然后元数据就可以被相关的源代码工具访问,通过编译好的类文件或是在运行时,取决于这个注解是如何配置的

        1、应用注解

        在kotlin中使用注解的方法和java一样。要应用一个注解,以@字符作为(注解)名字的前缀,并放在要注解的声明最前面。可以注解不同的代码元素,比如函数和类。

        例如,如果你正在使用JUunit框架,可以用@Test标记一个测试方法:

        我们再来看一个更有趣的例子,@Deprecated注解。它在kotlin中的含义和java一样,但是kotlin用replaceWith参数增强了它,让你可以提供一个替代着的(匹配)模式,以支持平滑过渡到API的新版本。下面这个例子向你展示了如何给该注解提供实参(一条不推荐使用的消息和一个替代者的模式):         实参在括号中传递,就和常规函数的调用一样。用了这种声明之后,如果有人使用了remove函数,IDEA不仅会提示应该使用哪个函数来代替它(这个例子中是removeAt),还会提供一个自动的快速修正。

        注解只能拥有如下类型的参数:基本数据类型、字符串、枚举、类引用、其他的注解类,以及前面这些类型的数组。指定注解实参的语法和java有些微小的差别:

         注解实参需要在编译期是已知的,所以你不能引用任意的属性作为实参。要把数据当做注解实参使用,你需要使用const修饰符来标记它,来告知编译器这个属性时编译期常量。下面是一个JUnit @Test注解的例子,使用timeout参数指定测试超时时长,单位为毫秒:

        正如3.3.1节讨论过的,用const标注的属性可以声明在一个文件的顶层或者一个object之中,而且必须初始化为基本数据类型或者String类型的值。如果你尝试使用普通属性作为注解实参,将会得到一个错误“Only 'const val' can be used in constant expression” 。

        2、注解目标

        许多情况下,kotlin源代码中的单个声明会对应成多个java声明,而且它们每个都能携带注解。例如,一个kotlin属性就对应了一个java字段、一个getter,以及一个潜在的setter和它的参数。而一个主构造方法中声明的属性还多拥有一个对应的元素:构造方法的参数。因此,说明这些元素中哪些需要注解十分必要。

        使用点目标声明被用来说明要被注解的元素。使用点目标被放在@符号符号和注解名称之间,并用冒号和注解名称隔开。下图中单词get导致注解@Rule被应用到了属性的getter上。

         下面我们来看一个使用这个注解的例子。在JUnit中可以指定一个每个测试方法被执行之前都会执行的规则。例如,标准的TemporaryFolder规则用来创建文件和文件夹,并在测试结束后删除它们。

        要指定一个规则,在java中需要声明一个用@Rule注解的public字段或者方法。如果在你的kotlin测试类中只是用@Rule注解了属性folder,你会得到一个JUnit异常:“The (???) ‘folder’ must be public.”((???) 'folder'必须是公有的)。这是因为@Rule被应用到了字段上,而字段默认是私有的。要把它应用到(公有的)getter上,要显式地写出来:@get:Rule,就像下面这样:

        如果你使用Java中声明的注解来注解一个属性,它会被默认地应用到相应的字段上。kotlin也可以让你声明被直接对应到属性上的注解。

        kotlin支持的使用点目标的完整列表如下: 

        任何应用到file目标的注解都必须放在文件的顶层,放在package指令之前@JvmName是常见的应用到文件的注解之一,它改变了对应类的名称。3.2.3节中已经展示了一个例子:@file:JvmName("StringFunctions")。

         注意,和java不一样的是,kotlin允许你对任意的表达式应用注解,而不仅仅是类和函数的声明及类型。最常见的例子就是@Suppress注解,可以用它来抑制被注解的表达式的上下文中的特定的编译器警告。下面就是一个注解局部变量声明的例子,抑制了未受检转换的警告:

        注意,在IDEA中,在出现这个编辑器警告的地方,按下Alt + Enter组合键并从意向选项菜单中选择Suppress(抑制),IDEA就会帮你插入这个注解。 

3、使用注解定制JSON序列化

         注解的经典用法之一就是定制化对象的序列化序列化就是一个过程,把对象转换成可以存储或者在网络上传输的二进制或者文本的表示法。它的逆变过程,反序列化,把这种表示法转换回一个对象。而最常见的一种用来序列化的格式就是JSON。已经有很多广泛使用的库可以把java对象序列化成JSON,包括Jackson(https://github.com/FasterXML/jackson)和GSON(https://github.com/google/gson)。就和任何其他java库一样,它们和kotlin完全兼容。

        在本章中,我们将会讨论一个满足此用途的名为JKid的纯Kotlin库。它足够小巧,你可以轻松地读完它的全部源码,我们也鼓励你在阅读本章的同时阅读它的源码。

        让我们从最简单的例子开始,测试一下这个库:序列化和反序列化一个Person类的实例。把实例传给serialize函数,然后它就会返回一个包含该实例JSON表示法的字符串: 

        一个对象的JSON表示法由键值对组成:具体实例的属性名称和它们的值之间的键值对,比如:“age”:29。

        要从JSON表示法中取回一个对象,要调用deserialize函数:

        当你从JSON数据中创建实例的时候,必须显式地指定一个类作为类型参数,因为JSON没有存储对象的类型。这种情况下,你要传递Person类。

        下图展示了一个对象和它的JSON表示法之间的等价关系。注意序列化之后的类能包含的不仅是图中展示的这些基本数据类型或者字符串类型的值,还可以是集合,以及其他值对象类的实例。

         你可以使用注解来定制对象序列化和反序列化的方式。当把一个对象序列化成JSON的时候,默认情况下这个库尝试序列化所有属性,并使用属性名称作为键。注解允许你改变默认的行为,这一节我们会讨论两个注解:@JsonExclude和@JsonName。本章稍后你就会看到它们的实现。

        参考下面这个例子:

         你注解了属性firstName,来改变在JSON中用来表示它的键。而属性age也被注解了,在序列化和反序列化的时候会排除它。注意,你必须指定属性age的默认值。否则,在反序列化时你无法创建一个Person的新实例。下图展示了Person类实例的表示法发生了怎样的变化。

 4、声明注解

        在这一节,你会以JKid库中的注解为例学习怎样声明它们。注解@JsonExclude有着最简单的形式,因为它没有任何参数:

annotation class JsonExclude

        语法看起来和常规类的声明很像,只是在class关键字之前加上了annotation修饰符。因为注解类只是用来定义关联到声明和表达式的元数据的结构,它们不能包含任何的代码。因此,编译器禁止为一个注解类指定类主体

        对拥有参数的注解来说,在类的主构造方法中声明这些参数:

annotation class JsonName(val name: String)

        你用的是常规的主构造方法的声明语法。对一个注解类的所有参数来说,val关键字是强制的

        作为对比,下面是如何在java中声明同样的注解:

/* java */
public @interface JsonName {
    String value();
}

        注意,java注解拥有一个叫做value的方法,而kotlin注解拥有一个name属性。java中的value方法很特殊:当你应用一个注解时,你需要提供value以外所有指定特性显式名称。而另一方面,在kotlin中应用注解就是常规的构造方法调用。可以使用命名实参语法让实参的名称变成显式的,或者可以省略掉这些实参的名称:@JsonName(name = "first_name")和@JsonName("first_name")含义一样,因为name是JsonName构造方法的第一个形参(它的名称可以省略)。然而,如果你需要把java声明的注解应用到kotlin元素上,必须对除了value以外的所有实参使用命名实参语法,而value也会被kotlin特殊对待

5、元注解:控制如何处理一个注解

        和java一样,一个kotlin注解类自己也可以被注解。可以应用到注解类上的注解被称为元注解。标准库中定义了一些元注解,它们会控制编译器如何处理注解。其他一些框架也会用到元注解——例如,许多依赖注入库使用了元注解来标记其他注解,表示这些注解用来识别有同样类型的不同的可注入对象。

        标准库定义的元注解中最常见的就是@Target。JKid中@JsonExclude和@JsonName的声明使用它为这些注解指定有效的目标。下面展示了它是如何应用(在注解上)的:        

        @Target元注解说明了注解可以被应用的元素类型。如果不使用它,所有的声明都可以应用这个注解。这并不是JKid想要的,因为它只需要处理属性的注解。

        AnnotationTarget枚举的值列出了可以应用注解的全部可能的目标。包括:类、文件、函数、属性访问器、所有的表达式等等。如果需要,你还可以声明多个目标:@Target(AnnotationTarget.CLASS,  AnnotationTarget.METHOD) 

        要声明你自己的元注解,使用ANNOTATION_CLASS作为目标就好了

        注意,在java代码中无法使用目标为PROPERTY的注解:要让这样的注解可以在java中使用,可以给它添加第二个目标AnnotationTarget.FIELD。这样,注解既可以应用到kotlin中的属性上,也可以应用到java中的字段上。

6、使用类做注解参数

         你已经见过了如何定义保存了作为其实参的静态数据的注解,但有时候你有不同的需求:能够引用类作为声明的元数据。可以通过声明一个拥有类引用作为形参的注解类来做到这一点。在JKid库中,这出现在@DeserializeInterface注解中,它允许你控制那些接口类型属性的反序列化。不能直接创建一个接口的实例,因此需要指定反序列化时那个类作为实现被创建。

        下面这个简单例子展示了这个注解如何使用:

        当JKid读到一个Person类实例嵌套的company对象时,它创能并反序列化了一个CompanyImpl的实例,把它存储在company属性中,使用CompanyImpl::class作为@DeserializeInterface注解的实参来说明这一点。通常,使用类名称后面跟上::class关键字来引用一个类。 

        现在我们看看这个注解是如何声明的。它的单个实参是一个类引用,就像@DeserializeInterface(CompanyImpl::class):

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

        KClass是java的java.lang.Class类型在kotlin中的对应类型。例如,CompanyImpl::class的类型是KClass<CompanyImpl>,它是这个注解形参类型的子类型,如下图所示:

        如果你只写出KClass<Any>而没有out修饰符,就不能传递CompanyImpl::class作为实参:唯一允许的实参是Any:class。out关键字说明允许引用那些继承Any的类,而不仅仅是引用Any自己

7、使用泛型类做注解参数

        默认情况下,JKid把非基本数据类型的属性当成嵌套的对象序列化。但是你可以改变这种行为并为某些值提供你自己的序列化逻辑。

        @CustomSerializer注解接收一个自定义序列化器类的引用作为实参。这个序列化器类应该实现ValueSerializer接口:

        

        假设你需要支持序列化日期,而且已经为此创建了你自己的DateSerializer类,它实现了ValueSerializer<Date>接口(这个类是JKid源码中的一个例子)。下面展示如何在Person类上应用它:

        现在我们来看看CustomSerializer注解是如何声明的。 ValueSerializer类是泛型的而且定义了一个类型形参,所以在你引用该类型的是需要提供一个类型实参值。因为你不知道任何关于那些应用了这个注解的属性类型的信息,可以星号投射作为(类型)实参:

        下图审视了serializerClass参数的类型并解释了其中不同的部分。你需要保证注解只能引用实现了ValueSerializer接口的类。例如@CustomSerializer(Date::class)的写法是不允许的,因为Date没有实现ValueSerializer接口。

        是不是很麻烦?好消息是每一次需要使用类作为注解实参的时候都可以应用同样的模式。可以这样写KClass<out YourClassName> 。如果YourClassName有它自己的类型实参,就用*代替它们。

二、反射:在运行时对Kotlin对象进行自省

        反射是,简单来说,一种在运行时动态地访问对象属性和方法的方式,而不需要事先确定这些属性时什么。一般来说,当你访问一个对象的方法或属性时,程序的源代码会引用一个具体的声明,编译器将静态地解析这个引用并确保这个声明是存在的。但有些时候,您需要编写能够使用任意类型的对象的代码,或者只能在运行时才能确定要访问的方法或属性的名称。JSON序列化库就是这种代码绝好的例子:它要能够把任何对象都序列化成JSON,所以它不能引用具体的类和属性。这时该反射大显身手了。

        当在kotlin中使用反射时,你会和两种不同的反射API打交道。第一种是标准的java反射,定义在java.lang.reflect中。因为kotlin类会被编译成普通的java字节码,java反射API可以完美地支持它们。实际上,这意味着使用了反射API的java库完全兼容kotlin代码。

        第二种是kotlin的反射API,定义在kotlin.reflect中。它让你能够访问那些在java世界里不存在的概念,诸如属性和可空类型。但这一次它没有为java反射API提供一个面面俱到的替身,而且不久你就会看到,有些情况下你仍然会回去使用java反射。这里有个重要的提示,kotlin反射api没有仅限于kotlin类:你能够使用同样的api访问任何jvm语言写成的类。

1、 kotlin反射API:KClass、KCallable、KFunction和KProperty

        kotlin反射API的主要入口就是KClass,它代表了一个类。KClass对应的是java.lang.class,可以用它列举和访问类中包含的所有声明,然后是它的超类中的声明,等等。MyClass::class的写法会带给你一个KClass的实例。要在运行时取得一个对象的类,首先使用javaClass属性获得它的java类,这直接等价于java的java.lang.Object.getClass()。然后访问该类的.kotlin扩展属性,从java切换到kotlin的反射API。        

         这个简单的例子打印出了类的名称和它的属性的名称,并且使用.memberProperties来收集这个类,以及它的所有超类中定义的全部非扩展的属性。

        如果浏览一下KClass的声明,你会发现它包含了大量方便的方法,用于访问类的内容:

        KClass的许多有用的特性,包括前面例子中用到的memberProperties,都声明成了扩展。

        你可能已经发现了由类的所有成员组成的列表是一个KCallable实例的集合。KCallable是函数和属性的超接口。它声明了call方法,允许你调用对应的函数或对应属性的getter:

        你把(被引用)函数的实参放在varargs列表中提供给它。下面的代码展示了如何通过反射使用call来调用一个函数:

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/131444.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何通过3个月自学成为网络安全工程师!

前言&#xff1a; 趁着今天下班&#xff0c;我花了几个小时整理了下&#xff0c;非常不易&#xff0c;希望大家可以点赞收藏支持一波&#xff0c;谢谢。 我的经历&#xff1a; 我 19 年毕业&#xff0c;大学专业是物联网工程&#xff0c;我相信很多人在象牙塔里都很迷茫&…

Pycharm配置关于pyside6的外部工具

文章目录一、前言二、Pycharm配置1、designer.exe&#xff08;1&#xff09;打开Pycharm的设置&#xff08;2&#xff09;相关参数&#xff08;可复制粘贴&#xff09;2、Pyside6-uic.exe&#xff08;1&#xff09;设置&#xff08;2&#xff09;相关参数&#xff08;可复制粘贴…

Java--抽象类和接口的区别

今天是22年最后一天了, 写篇博客记录一下吧, 这一年发生了很多事情, 也学到了很多知识, 后面要继续加油啊, 大家也要加油啊, 米娜桑. 目录 概述 区别 1. 定义关键字不同 2. 继承或实现的关键字不同 3. 子类扩展的数量不同 4. 属性访问控制符不同 5. 方法控制符不同 6.…

python互联网程序设计GUI程序设计和网络程序设计(人机互动聊天软件)

1&#xff0e;项目意义 1、了解网络的结构&#xff1b; 2、了解网络传输协议&#xff1b; 3、掌握基本的网络编程方法。 2&#xff0e;总体设计 使用 TCP 协议实现人机聊天互动&#xff0c;程序具有服务端和客户端&#xff1a; &#xff08;1&#xff09;必备功能&#xff1…

Java财务在线咨询网站系统财务咨询网

简介 财务咨询网站&#xff0c;可以咨询公司代办&#xff0c;代理记账等一系列的财务问题的资讯服务网站 演示视频 https://www.bilibili.com/video/BV1T54y1H7Ar/?share_sourcecopy_web&vd_sourceed0f04fbb713154db5cc611225d92156 角色 管理员客服注册用户游客 技术…

Spring之DI入门案例

目录 一&#xff1a;DI入门案例实现思路分析 1.要想实现依赖注入&#xff0c;必须要基于 IOC 管理 Bean 2.Service 中使用 new 形式创建的 Dao 对象是否保留 ? 3.Service 中需要的 Dao 对象如何进入到 Service 中 ? 4.Service 与 Dao 间的关系如何描述 ? 二&#xff1…

(Qt) cmake编译Qt项目

文章目录前言环境cmake基础预备的项目代码文件资源路径demo.promain.cppres.qrcmywidget.cppmywidget.hmywidget.ui运行效果CMake文件资源路径CMakeLists.txt生成与构建END前言 通常我们在编写qt的时候都是在Qt creator中。而如何在VS Code中编写qt就是本文需要解决的问题 环…

顺序表 —— 初始化、销毁、打印、增加、删除、查找、修改

1.何为线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串…线性表在逻辑上是线性结构&#xff0c;也就说是连续的一条直…

zotero导出pdf

今天老师给我改论文的时候布置了一个任务&#xff0c;让我把所有论文的pdf按格式打包发给她。可是之前我用zotero的时候都是在线保存的&#xff0c;有些是没有pdf的&#xff0c;怎么办&#xff1f;而且就算有pdf&#xff0c;他们的命名格式也五花八门&#xff0c;难道一个个手改…

kafka 消息日志原理 指定偏移量消费 指定时间戳消费

Kafka 日志详解 Apache Kafka日志存储在物理磁盘上各种数据的集合&#xff0c;日志按照topic分区进行文件组织&#xff0c;每一个分区日志由一个或者多个文件组成。生产者发送的消息被顺序追加到日志文件的末尾。 如上图所述&#xff0c;Kafka主题被划分为3个分区。在Kafka中&…

vscode使用跳板机(密钥)进入内网并连接内网中其它机器(密码)

经过简单测试 1、不能像xshell一样选择服务器的密钥登陆&#xff0c;只能通过将本机的公钥传到服务器上 2、不能使用本地socket5做代理登录 3、不能使用系统代理登录 一、使用密钥连接到跳板机 1、内网穿透 2、将本机公钥上传到服务器上 1&#xff09;建立密钥对 无论是win…

redis集群 mac安装

1.安装redis mac环境用brew install安装 brew install redis 安装好后默认配置启动单点服务 redis-server 注&#xff1a;brew默认程序安装在/usr/local/Cellar目录下 /usr/local/Cellar/redis 默认配置文件在 /usr/local/etc/redis.conf 2.创建配置文件 准备创建6个节…

谣言检测数据集

1 PHEME-R 这是一个在PHEME FP7项目的新闻学用例中收集和注释的数据集。这些谣言与9个不同的突发新闻相关。它是为分析社交媒体谣言而创建的&#xff0c;包含由谣言推文发起的推特对话&#xff1b;对话包括对这些谣言推文的回应推文。这些推文已经被注解为支持度、确定性和证…

VS2012安装教程

我要学只有我们两个人懂得C语言。 安装包&#xff1a;https://pan.baidu.com/s/1YR7Xk9Zlv7zQWCsERdVgIQ [提取码]&#xff1a;stvi 1、右键以管理员身份运行 “vs_ultimate.exe” 2、编辑软件安装位置&#xff0c;然后点击同意许可&#xff0c;之后点下一步即可&#xff01; 3…

mongoDB聚合查询

管道 管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。 聚合管道操作 可参考菜鸟文档&#xff1a;菜鸟文档 命令 功能描述 $project指定输出…

shell第四天作业——流程控制之循环

题目 一、for创建20个用户&#xff0c;用户前缀由用户输入&#xff0c;用户初始密码由用户输入。 二、for循环ping测试指定网段的主机&#xff0c;网段由用户输入。 三、使用for/while实现批量主机root密码的修改 一、for创建20个用户&#xff0c;用户前缀由用户输入&#x…

2022年已然要结束了,一起来分享下你的故事吧!2023年的接力棒已经递到手里,千言万语不如一句Fighting!

【系列专栏】&#xff1a;博主结合工作实践输出的&#xff0c;解决实际问题的专栏&#xff0c;朋友们看过来&#xff01; 《QT开发实战》 《嵌入式通用开发实战》 《从0到1学习嵌入式Linux开发》 《Android开发实战》 《实用硬件方案设计》 长期持续带来更多案例与技术文章分享…

c++语法欠缺地方

sizeof是用来计算变量占多大内存的&#xff0c;单位是字节&#xff08;byte&#xff09;&#xff1b;sizeof 后面跟类型时&#xff0c;必须加上括号&#xff0c;例如sizeof(double);后面跟变量可以不用加括号&#xff0c;例如&#xff1a;sizeof d%d是以十进制形式输出有符号整…

CDP集群卸载过程

CDP集群卸载过程 1. 登录到Cloudera Manager&#xff0c;并停止整个集群服务。 2. 停用并移除所有Parcel 3. “停用”CDH7的Parcel。 4. 从主机删除”CDH6的Parcel 5. 删除集群 6. 登录server机器&#xff0c;停止CM Server服务 systemctl stop cloudera-scm-server 7. 移除…

如何实现高性能点赞(三)

数据库设计 数据库表中至少要包含三个字段&#xff1a;被点赞用户id&#xff0c;点赞用户id&#xff0c;点赞状态。再加上主键id&#xff0c;创建时间&#xff0c;修改时间就行了。 建表语句 对应的对象 UserLike 数据库操作 操作数据库同样封装在接口中 LikedService L…