这是小编最有一篇发表Kotlin的文章了(小编其实还有一丢丢不懂,愿kotlin的生态越来越强大,不要白学就好)
在Java的时候我们经常使用注解,同时我们为了让注解产生作用,我们经常用药Java的反射机制,比如JUnit就自定义注解,然后使用反射机制看哪些元素上有什么注解,然后用反射调用方法。
注解是将元数据附加到代码的⽅法。要声明注解,请将 annotation 修饰符放在类的前⾯:
annotation class Fancy
注解的附加属性可以通过用元注解标注注解类来指定:
1. @Target 指定可以用该注解标注的元素的可能的类型(类、函数、属性、表达式等);
2. @Retention 指定该注解是否存储在编译后的 class 文件中,以及它在运行时能否通过反射可见(默认都是 true);
3. @Repeatable 允许在单个元素上多次使用相同的该注解;
4. @MustBeDocumented 指定该注解是公有 API 的⼀部分,并且应该包含在生成的 API 文档中显示的类或方法的签名中。
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.EXPRESSION, AnnotationTarget.FIELD,AnnotationTarget.VALUE_PARAMETER)
@Repeatable
@Retention(AnnotationRetention.SOURCE)
annotation class My
上面的代码就定义了一个自定义的注解
用法:
@My class Foo {
@My fun baz(@My foo: Int): Int {
return (@My 1)
}
}
如果需要对类的主构造函数进行标注,则需要在构造函数声明中添加 constructor 关键字 ,并将注解添加到其前面:
注意使用注解修饰主构造函数时constructor不能省略。
class Foo @Inject constructor(dependency: MyDependency) {
// ……
}
你也可以标注属性访问器
class Foo {
var x: MyDependency? = null
@Inject set
}
注解可以有接受参数的构造函数
annotation class Special(val why: String)
@Special("example") class Foo
允许的参数类型有:
1. 对应于 Java 原生类型的类型(Int、Long等);
2. 字符串;
3. 类( Foo::class );
4. 枚举;
5. 其他注解;
6. 上面已列类型的数组
注解参数不能有可空类型,因为 JVM 不支持将 null 作为注解属性的值存储。
如果注解用作另⼀个注解的参数,则其名称不以 @ 字符为前缀:
要注意的是,和java一样,kotlin的注解也不会影响程序,要另外定义一个注解处理程序才能使注解起作用
annotation class ReplaceWith(val expression: String)
annotation class Deprecated(
val message: String,
val replaceWith: ReplaceWith = ReplaceWith(""))/*这里的参数是注解类型的,有一个默认值*/
fun test(){
@Deprecated("This function is deprecated, use === instead", ReplaceWith("this === other"))
var a :String =""
}
如果需要将⼀个类指定为注解的参数,请使用 Kotlin 类(KClass)。Kotlin 编译器会自动将其转换为 Java 类,以便 Java 代码能够正常看到该注解和参数。
//制定的时候要制定为Kotlin类,编译期会翻译,不能指定为Java类
annotation class Ann(val arg1: KClass<*>, val arg2: KClass)
@Ann(String::class, Int::class) class MyClass //String::class是获取String类的反射对象
注解也可以用于 lambda 表达式。它们会被应用于生成 lambda 表达式体的 invoke() 方法上。这对于像 Quasar 这样的框架很有用,该框架使用注解进行并发控制。
annotation class Suspendable
val f = @Suspendable { Fiber.sleep(10) }
当对属性或主构造函数参数进行标注时,从相应的 Kotlin 元素生成的 Java 元素会有多个,因此在生成的 Java 字节码中该注解有多个可能位置 。如果要指定精确地指定应该如何生成该注解,请使用以下语法
只有主构造函数上才有这样的语法
annotation class Ann
class Example(@field:Ann val foo:String, // 标注 Java 字段
@get:Ann val bar:Int, // 标注 Java getter
@param:Ann val quux:String) // 标注 Java 构造函数参数
可以使用相同的语法来标注整个⽂件。要做到这⼀点,把带有目标 file 的注解放在文件的顶层、package 指令之前或者在所有导⼊之前(如果文件在默认包中的话)
//下面用于Java和Kotlin交互的时候用的,它制定这个文件会被翻译成一个Foo类
@file:JvmName("Foo")
package org.jetbrains.demo
//Kotlin代码
@file :JvmName("Test") //这里会把这个文件翻译成Test类
package C_others
fun sayHello(){ //顶层函数Java中翻译成静态方法
println("hello")
}
//Java代码,怎么调用Kt顶层函数
public class LL {
public static void main(String[] args) {
Test t = new Test();
t.sayHello();
Test.sayHello(); //顶层函数翻译成静态方法
}
}
如果你对同⼀目标有多个注解,那么可以这样来避免目标重复⸺在目标后面添加方括号并将所有注解放在方括号内
class Example {
@set:[Inject VisibleForTesting] //这里对setter使用了多个注解
var collaborator: Collaborator
}
⽀持的使用处目标的完整列表为:
1. file —–kt文件
2. property(具有此目标的注解对 Java 不可见)—–属性
3. field
3. get(属性 getter)
4. set(属性 setter)
5. Lambda 表达式
6. receiver(扩展函数或属性的接收者参数)
7. param(构造函数参数)
8. setparam(属性 setter 参数)
9. delegate(为委托属性存储其委托实例的字段)
要标注扩展函数的接收者参数,请使用以下语法:
annotation class Fancy
fun @receiver:Fancy String.myExtension() { } //扩展函数的接收者是String,使用Fancy标注String接收者参数
如果不指定使用处目标,则根据正在使用的注解的 @Target
注解来选择目标 。如果有多个适用的目标,则使用以下列表中的第⼀个适用目标:
1. parm
2. property
3. field
Java 注解与 Kotlin 100% 兼容:
import org.junit.Test
import org.junit.Assert.*
import org.junit.Rule
import org.junit.rules.*
class Tests {
// 将 @Rule 注解应⽤于属性 getter
@get:Rule val tempFolder = TemporaryFolder()
@Test fun simple() { //使用Junit的Test标注simple方法
val f = tempFolder.newFile()
assertEquals(42, getTheAnswer())
}
}
因为 Java 编写的注解没有定义参数顺序,所以不能使用常规函数调用语法来传递参数。相反,你需要使用命名参数语法。
// Java
public @interface Ann {
int intValue();
String stringValue();
}
// Kotlin
@Ann(intValue = 1, stringValue = "abc") class C
就像在 Java 中⼀样,⼀个特殊的情况是 value 参数;它的值无需显式名称指定。
// Java
public @interface AnnWithValue {
String value();
}
// Kotlin
@AnnWithValue("abc") class C
如果 Java 中的 value 参数具有数组类型,它会成为 Kotlin 中的⼀个 vararg 参数:
// Java
public @interface AnnWithArrayValue {
String[] value();
}
// Kotlin
@AnnWithArrayValue("abc", "foo", "bar") class C
对于具有数组类型的其他参数,你需要显式使⽤ arrayOf :
// Java
public @interface AnnWithArrayMethod {
String[] names();
}
// Kotlin
@AnnWithArrayMethod(names = arrayOf("abc", "foo", "bar")) class C
注解实例的值会作为属性暴露给 Kotlin 代码。
// Java
public @interface Ann {
int value();
}
// Kotlin
fun foo(ann: Ann) { //使用注解类型的参数
val i = ann.value
}
反射是这样的⼀组语言和库功能,它允许在运⾏时自省你的程序的结构。Kotlin 让语言中的函数和属性做为⼀等公民、并对其自省(即在运⾏时获悉⼀个名称或者⼀个属性或函数的类型)与简单地使用函数式或响应式风格紧密相关。
注意:在 Java 平台上,使用反射功能所需的运行时组件作为单独的 JAR ⽂件(kotlin-reflect.jar
)分发。这样做是为了减少不使用反射功能的应⽤程序所需的运行时库的大小。如果你需要使⽤反射,请确保该 .jar文件添加到项目的 classpath 中!!!
也就是说,kotlin JVM要使用反射要有依赖
最基本的反射功能是获取 Kotlin 类的运行时引用。要获取对静态已知的 Kotlin 类的引用,可以使用类字面值语法:
class MyClass
val c = MyClass::class //该引⽤c是 KClass 类型的值
class MyClass{
fun foo(){
println("foo方法被调用了")
}
}
val c = MyClass::class //该引⽤c是 KClass 类型的值
fun main(args: Array) {
val c = MyClass::class
val methods = c.functions
val m1 = methods.first()
}
请注意,Kotlin 类引用与 Java 类引用不同。要获得 Java 类引用,请在 KClass 实例上使用 .java 属性。
通过使用对象作为接收者,可以用相同的 ::class 语法获取指定对象的类的引用:
val widget: Widget = ……
assert(widget is GoodWidget) { "Bad widget: ${widget::class.qualifiedName}" }
你可以获取对象的精确类的引用,例如 GoodWidget
或 BadWidget
,尽管接收者表达式的类型是 Widget
。
先在这告以段落,小编之后会补上 的。