大连做网站优化,一级a做爰片 网站就能看,网站设计怎么算间距,wordpress手机主题视频Kotlin 和 Java 反射 1#xff09;Kotlin 的 KClass 和 Java 的 Class 可以看作同一个含义的类型#xff0c;并且可以通过.java和.kotlin方法在KClass和Class之间互相转化。2#xff09;Kotlin 的 KCallable 和 Java 的 AccessiableObject 都可以理解为可调用元素。Java 中构…Kotlin 和 Java 反射 1Kotlin 的 KClass 和 Java 的 Class 可以看作同一个含义的类型并且可以通过.java和.kotlin方法在KClass和Class之间互相转化。2Kotlin 的 KCallable 和 Java 的 AccessiableObject 都可以理解为可调用元素。Java 中构造方法为一个独立的类型而 Kotlin 则统一作为 KFunction 处理。3Kotlin 的 KProperty 和 Java 的 Field 不太相同。Kotlin 的KProperty通常指相应的Getter和Setter 只有可变属性Setter整体作为一个KProperty通常情况 Kotlin 并不存在字段的概念而 Java 的 Field 通常仅仅指字段本身。
在某些情况下通常是碰到一些Kotlin独有的特性时Kotlin编译器会在生产的字节码中存储额外信息这些信息目前是通过 kotlin.Metadata 解实现的。Kotlin 编译器会用 Metadata 标注这些类。
Kotlin 的 KClass
KClass的特别属性或者函数在Kotlin中独有Java没有与之对应的特性
属性或函数名称含义isCompanion是否伴生对象isData是否数据类isSealed是否密封类objectInstanceobject实例如果是objectcompanionObjectInstance伴生对象实例declaredMemberExtensionFunctions扩展函数声明的declaredMemberExtensionProperties扩展属性声明的memberExtensionFunctions本类及超类扩展函数memberExtensionProperties本类及超类扩展属性starProjectedType泛型通配类型
Kotlin 的 KCallable
Kotlin 把 Class 中的属性Property、函数Funciton甚至构造函数都看作 KCallable因为它们是可调用的它们都是Class的成员。那我们如何获取一个Class的成员呢
KClass给我们提供了一个members方法它的返回值就是一个CollectionKCallable*。
KCallable提供的 API
API 描述含义isAbstract: Boolean此 KCallable 是否为抽象的isFinal: Boolean此 KCallable 是否为 final 的isOpen: Boolean此 KCallable 是否为 open 的name: String此 KCallable 的名称parameters: ListKParameter调用此 KCallable 需要的参数returnType: KType此 KCallable 的返回类型typeParameters: ListKTypeParameter此 KCallable 的类型参数visibility: KVisibility?此 KCallable 的可见性call(vararg args: Any?): R给定参数调用此 KCallable
KMutableProperty是KProperty的一个子类那我们如何识别一个属性是KMutableProperty还是KProperty呢参考如下代码
fun KMutablePropertyShow() {val p Person(张三, 8, HangZhou)val props p::class.memberPropertiesfor (prop in props) {when (prop) {is KMutableProperty* - prop.setter.call(p, Hefei)else - prop.call(p)}}println(p.address)
}获取参数信息
Kotlin 把参数分为3个类别分别是函数的参数KParameter、函数的返回值KType及类型参数KTypeParameter。
KParameter
使用KCallabel.parameters即可获取一个ListKParameter它代表的是函数包括扩展函数的参数。
API 描述含义index: Int返回该参数在参数列表里面的索引isOptional: Boolean该参数是否为 OptionalisVararg: Boolean该参数是否为 varargkind: Kind该参数的 Kindname: String?该参数的名称type: KType该参数的类型
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()}
}运行结果
address - Person
name - Person
detailAddress - Person,kotlin.String
isChild - Person
equals - kotlin.Any,kotlin.Any?
hashCode - kotlin.Any
toString - kotlin.Any通过上面的运行结果我们发现对于属性和无参数的函数它们都有一个隐藏的参数为类的实例而对于声明参数的函数类的实例作为第 1 个参数而声明的参数作为后续的参数。对于那些从Any继承过来的参数Kotlin 默认它们的第 1 个参数为Any。
KType
每一个KCallabe都可以使用returnType来获取返回值类型它的结果类型是一个KType代表着Kotlin中的类型。
API 描述含义arguments: ListKTypeProjection该类型的类型参数classifier: KClassifier?得到结果为 List忽略类型参数的类型isMarkedNullable: Boolean该类型是否标记为可空类型
classifier API其实就是获取该参数在类层面对应的类型 如 Int - class kotlin.Int ListString - class kotlin.collections.List。
KTypeParameter
在KClass和KCallable中我们可以通过typeParameters来获取class和callable的类型参数它返回的结果集是ListKTypeParameter不存在类型参数时就返回一个空的List。
fun A get(a: A) : A { return a
}然后我们可以使用下面的代码来获取get方法和ListString的类型参数
fun KTypeParameterShow() {for (c in Person::class.members) {if (c.name.equals(get)) {println(c.typeParameters)}}val list listOfString(How)println(list::class.typeParameters)
}运行结果
[A]
[E]Kotlin 的注解
前面我们提及过注解 kotlin.Metadata这是实现 Kotlin 大部分独特特性反射的关键Kotlin 将这些信息直接以注解形式存储在字节码文件中以便运行时反射可以获取这些数据。
由于 Kotlin 兼容 Java所以所有 Java 可以添加注解的地方Kotlin 也都可以。并且 Kotlin 也简化了注解创建语法创建注解就像创建 class 一样简单只需额外在 class 前增加 annotation 关键字即可。
annotation class FooAnnotation(val bar: String) 上面的代码就直接创建了FooAnnotation注解和创建其他 Kotlin 的类一样正如前文所说只要在前面加上annotation这个类就变成了注解和等价的 Java 代码相比较确实简化了很多。
同时和 Java 一样注解的参数只能是常量并且仅支持下列类型
Java 对应的基本类型字符串Class 对象KClass或者Java的Class其他注解上述类型数组。注意基本类型数组需要指定为对应的XXXArray例如IntArray⽽不是ArrayInt。
元注解
类似Target这样标注在注解上的注解我们称之为元注解。我们知道 Java 中的java.lang.annotation 包中定义了下列 5 个元注解
Documented文档通常是API文档中必须出现该注解。Inherited如果超类标注了该类型那么其子类型也将自动标注该注解而无须指定。Repeatable这个注解在同一位置可以出现多次。Retention表示注解用途有3种取值。 Source。仅在源代码中存在编译后class文件中不包含该注解信息。CLASS。class文件中存在该注解但不能被反射读取。RUNTIME。注解信息同样保存在class文件中并且可以在运行时通过反射获取。 Target表明注解可应用于何处。
和 Java 一样在 Kotlin 中也有对应的元注解类。Kotlin 中的元注解类定义在 kotlin.annotation 包下主要有
KotlinJava含义RetentionRetention注解的保留期TargetTarget注解可用于哪些目标对象MustBeDocumentedDocumented注解将被文档工具提取到API文档中RepeatableRepeatable注解可以多次应用于相同的声明或类型
注意到相比 Java 中5种元注解少了 InheritedKotlin 目前不支持 Inherited理论上实现继承没有很大难度但当前版本还不支持。
通过上面对比我们发现Kotlin 和 Java 注解整体上是保持一致的熟悉 Java 注解的读者应该很容易将这部分知识迁移到 Kotlin。
Target
Target顾名思义就是目标对象也就是我们定义的注解能够应用于哪些目标对象可以同时指定多个作用的目标对象。
Target的原型:
Target(AnnotationTarget.ANNOTATION_CLASS)
MustBeDocumented
public annotation class Target(vararg val allowedTargets : AnnotationTarget) 从Target的原型中我们可以看出它接受一个vararg可变数量的参数所以可以同时指定多个作用的目标对象并且参数类型限定为 AnnotationTarget。
Retention
Retention 我们可以理解为保留期和 Java 一样 Kotlin 有三种时期源代码时期SOURCE、编译时期BINARY、运行时期RUNTIME。
Retention的原型
Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Retention(val value : AnnotationRetention AnnotationRetention.RUNTIME) Retention 接收一个AnnotationRetention类型的参数该参数有个默认值默认是保留在运行时期。AnnotationRetention 是一个枚举类其定义如下
public enum class AnnotationRetention {// Annotation isnt 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
}基本上对应了 Java 的三种类型只不过 Kotlin 中默认值是 RUNTIME。
AnnotationTarget
前面提到在Target元注解中可以同时指定一个或多个目标对象那么到底有哪些目标对象呢接下来让我们看一下
Kotlin (Annotation Target)Java (Target)说明CLASSTYPE作用于类ANNOTATION_CLASSANNOTATION_TYPE作用于注解本身即元注解TYPE_PARAMETERTYPE_PARAMETER作用于类型参数PROPERTYN/A作用于属性FIELDFIELD作用于字段属性通常包含字段 Getter 以及 SetterLOCAL_VARIABLEFIELD作用于局部变量VALUE_PARAMETERN/A作用于 val 参数CONSTRUCTORCONSTRUCTOR作用于构造函数FUNCTIONMETHOD作用于函数Java只有MethodPROPERTY_GETTERN/A作用于 GetterPROPERTY_SETTERN/A作用于 SetterTYPETYPE_USE作用于类型EXPRESSIONN/A作用于表达式FILEPACKAGE作用于文件开头/包声明两者有细微区别TYPEALIASN/A作用于类型别名
Kotlin支持几乎所有Java支持的标注的位置并且增加了一些kotlin独有的位置。
一个简单Kotlin注解使用的例子
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的代码常常会表达多重含义。例如上述例子中的name除了生成了一个不可变的字段之外实际上还包含了Getter同时又是其构造函数的一个参数。
这就带来一个问题CacheKey注解究竟是作用于何处
精确控制注解的位置
为了解决这个问题Kotlin 引入精确的注解控制语法假如我们有注解 annotation class CacheKey
用法含义file:CacheKeyCacheKey 注解作用于文件property:CacheKeyCacheKey 注解作用于属性field:CacheKeyCacheKey 注解作用于字段get:CacheKeyCacheKey 注解作用于 Getterset:CacheKeyCacheKey 注解作用于 Setterreceiver:CacheKeyCacheKey 注解作用于扩展函数或属性param:CacheKeyCacheKey 注解作用于构造函数参数setparam:CacheKeyCacheKey 注解作用 Setter 的参数delegate:CacheKeyCacheKey 注解作用于存储代理实例的字段
例如
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
)上述CacheKey分别作用在属性、字段和Getter上。
反射获取注解信息
这有一个前提就是这个注解的Retentaion标注为Runtime或者没有显示指定注默认为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})
}通过反射获取注解信息是在运行时发生的和Java一样存在一定的性能开销当然这种开销大部分时候可以忽略不计。此外前面提到的注解标注位置也会影响注解信息的获取。例如fileCacheKey这样标注的注解则无法通调用KProperty.annotions获取到该注解信息。
注解的使用场景
提供信息给编译器编译器可以利用注解来处理一些比如一些警告信息错误等编译阶段时处理利用注解信息来生成一些代码在 Kotlin 生成代码非常常见一些内置的注解为了与 Java API 的互操作性往往借助注解在编译阶段生成一些额外的代码运行时处理某些注解可以在程序运行时通过反射机制获取注解信息来处理一些程序逻辑
下面是一个通过注解来标注Http请求方法的代码示例
Target(AnnotationTarget.CLASS)
Retention(AnnotationRetention.RUNTIME)
annotation class HttpMethod(val method : Method) interface Api {val name: Stringval version: Stringget() 1.0
}HttpMethod(Method.POST)
class ApiGetArticles : Api {override val name: Stringget() /api.articles
}fun fire(api: Api) {val annotations api.javaClass.annotationsval method annotations.find { it is HttpMethod } as? HttpMethodprintln(通过注解得知该接口需需要通过${method?.method}方式请求)
}我们知道著名的网络请求库 Retrofit 就是通过这种方式来标注接口请求的方法、路径、参数等信息的。
加锁
虽然 Kotlin 是基于 Java 改良过来的语言但是它没有 synchronized 关键字取而代之它使用了Synchronized注解和synchronized()函数来实现等同的效果。比如
class Shop {val goods hashMapOfLong,Int()init {goods.put(1,10)goods.put(2,15)}Synchronizedfun 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)}}
}注意这里的synchronized(this)是 kotlin 中的方法而非 java 中的 synchronized 关键字。
Kotlin 除了支持 Java 中 synchronized 这种并发原语外也同样支持其他一些并发工具比如 volatile 关键字java.util.concurrent.*下面的并发工具。当然Kotlin 也做了一些改造比如 volatile 关键字在 Kotlin 中也变成了注解
Volatile private var running false除了可以用 synchronized 这种方式来对代码进行同步加锁以外在 Java 中还可以用 Lock 的方式来对代码进行加锁。所以我们试着改造一下上面的 buyGoods 方法
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()}
}但是这种写法似乎有如下不好之处
若是在同一个类内有多个同步方法将会竞争同一把锁在加锁之后编码人员很容易忘记解锁操作重复的模板代码。
那么我们现在试着对它进行改进提高这个方式的抽象程度
fun T withLock (lock: Lock, action: () - T): T {lock.lock()try{return action()} catch (ex: Exception) {println([Exception] is ${ex})} finally {lock.unlock()}
}withLock方法支持传入一个lock对象和一个Lamada表达式所以我们现在可以不用关心对buyGoods进行加锁了只需要在调用的时候传入一个lock对象即可。
fun buyGoods(id: Long) {val stock goods.getValue(id)goods.put(id, stock - 1)
}var lock: Lock ReentrantLock()
withLock(lock) {buyGoods(1)
}Kotlin 类库中也默认添加了该方式的支持
var lock: Lock ReentrantLock()
lock.withLock { buyGoods(1)
}