集合与数组
可空性和集合
kotlin支持值为null的集合,也就是集合的类型参数支持“?”;看个例子就明白了
注意区分集合值可空,还是集合本身可空
只读集合和可变集合
Kotlin 的集合设计和 Java不同的另 一项重要特质是,它把访问集合数据的接 口和修改集合数据 的接口分开了 。 这种区别存在于最基础的使用集合的接口之中 :
使用kotlin.collections.Collection
这个接口,可以遍历集合中的元素、获取集合大小、判断集合 中是否包含某个元素,以及执行其他从该集合中读取数据 的操作。但这个接口没有任 何添加或移除元素的方法 。
使用 kotlin.collections.MutableCollection
接口可以修改集合中的 数据 。 它继承了普通的 kotlin.collections.Collection
接口,还提供了方 法来添加和移除元素、清空集合等。
特别注意:只读集合不一定是不可变的
原因:
1、java中集合是没有区分只读和可变的,所以在与java互操是有可能出现null
2、使用的变量拥有一个只读接口类型,它可能只是同 一个集合的众多引用中的 一个 。 任何其他的引用都可能拥有一个可变接口类型
kotlin集合与java集合
每一个 Kotlin接口都是其对应 Java集合接口的一个实例,这种说法并没有错。在 Kotlin和 Java之间转移并不需要转换;不需要包装器也不需要拷贝数据。
但是每一种 Java集合接口在 Kotlin 中都有两种表示: 一种是只读的,另 一种是可变的
可以用来创建 不同类型集合的函数。
对象和基本数据类型的数组
先看一个例子:
fun main(args: Array<String>) {
for(i in args.indices){
println("Argument $i is : ${args[i]}")
}
}
从这个例子可以看出两点:
1、kotlin数组是一个带有类型参数的类
2、通过下标访问
创建数组
要在 Kotlin 中创建数组,有下面这些方法供你选择 :
- arrayOf 函数创建一个数组,它包含的元素是指定为该函数的实参
- arrayOfNulls 创建一个给定大小的数组,包含的是 null 元素。当然,它
只能用来创建包含元素类型可空的数组。 - Array 构造方法接收数组的大小和一个 lambda 表达式,调用 lambda 表达式来创建每一个数组元素 。这就是使用非空元素类型来初始化数组 , 但不用显式地传递每个元素的方式 。
fun main(args: Array<String>) {
val letter = Array<String>(26){i-> ('a'+i).toString()}
println(letter.joinToString(" "))
}
集合转数组可以使用 toTypedArray;期望vararg参数时可以使用展开运算符(*)
val strings = listOf("a", "b", "c")
println("%s,%s,%s".format(*strings.toTypedArray()))
数组类型的元素始终会变成一个对象,因此,如果你声 明了 一个 Array,它将会是 一个包含装箱整型的数组(它的 Java 类型将是 java.lang . Integer[ ])。如果你需要创建没有装箱的基本数据类型的数组,必须使用一个基本数据类型数组的特殊类。
为了表示基本数据类型的数组, Kotiin 提供了若 干独立的类,每一种基本数据类型都对应 一 个。例如, Int 类型值的数组叫作 IntArray。 Kotlin 还提供 了 ByteArray、 CharArray、 BooleanArray 等给其他类型 。 所有这些类型都被编 译成普通的 Java基本数据类型数组,比如 int[]、 byte[]、 char[]等。 因此这 些数组中的值存储时并没有装箱,而是使用了可能的最高效的方式。
要创建一个基本数据类型的数组,你有如下选择:
- 该类型的构造方法接收 size 参数并返回 一个使用对应基本数据类型默认值 (通常是 0)初始化好的数组。
- 工厂函数( IntArray 的 intArrayOf,以及其他数组类型的函数〉接收变 长参数的值并创建存储这些值的数组。
- 另一种构造方法,接收一个大小和一个用来初始化每个元素的 lambda。
- 假如你有一个持有基本数据类型装箱后 的值的数组或者集合 , 可以用对 应的转换函数把它们转换成基本数据类型的数组,比如 toInttArray。
Kotlin 标准库支持一套 和 集合相同的用于数组的 扩展函数
fun main(args: Array<String>) {
args.forEachIndexed { index, s -> println("Argument $index is : $s") }
}
注解
应用注解
注解的使用和java一样,@加注解名称,放到可以注解的类型前面
看个例子:
@Deprecated (”Use removeAt(index) instead.”, ReplaceWith (”removeAt(index)”)) fun remove (index: Int) { . . . }
Kotlin用 replaceWith参数增强了@Deprecated 注解
实参在括号中传递,就和常规函数的调用一样。用了这种声明之后,如果有人使用了remove函数IntelliJIDEA不仅会提示应该使用哪个函数来代替它(这个例子中是removeAt), 还会提供一个自动的快速修正 。
- 注解只能拥有如下类型的参数 : 基本数据类型、字符串、枚举、类引用、其他 的注解类,以及前面这些类型的数组。
- 要把一个类指定为注解实参,在类名后加上 : :class : @MyAnnotation (MyClass::class) 。
- 要把另一个注解指定为一个实参 , 去掉注解名称前面的@ 。 例如,前面例子中的ReplaceWith是一个注解,但是你把它指定为Deprecated注解的实参时没有用@。
- 要把一个数组指定为一个实参 ,使用 arrayOf 函数:@ RequestMapping (path = arrayOf ("/foo", "/bar”))。如果注解类是在 Java 中 声明 的,命名为 value 的形参按需自动地被转换成可变长度的形参,所以不用arrayOf 函数就可以提供多个实参。
- 注解实参需要在编译期就是己知的,所以你不能引用任意的属性作为实参。要 把属性当作注解实参使用,你需要用 const 修饰符标记它,来告知编译器这个属 性是编译期常量。
- 注解参数不能有可空类型,因为 JVM 不支持将 null 作为注解属性的值存储。
注解声明
注解是将元数据附加到代码的方法。要声明注解,请将 annotation 修饰符放在类的前面:
annotation class Fancy
注解的附加属性可以通过用元注解标注注解类来指定:
- @Target 指定可以用该注解标注的元素的可能的类型(类、函数、属性、表达式等);
- @Retention 指定该注解是否存储在编译后的 class 文件中,以及它在运行时能否通过反射可见 (默认都是 true);
- @Repeatable 允许在单个元素上多次使用相同的该注解;
- @MustBeDocumented 指定该注解是公有 API 的一部分,并且应该包含在生成的 API 文档中显示的类或方法的签名中。
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class Fancy
用法
@Fancy class Foo {
@Fancy fun baz(@Fancy foo: Int): Int {
return (@Fancy 1)
}
}
如果需要对类的主构造函数进行标注,则需要在构造函数声明中添加 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 {}
允许的参数类型有:
- 对应于 Java 原生类型的类型(Int、 Long等);
- 字符串;
- 类(Foo::class);
- 枚举;
- 其他注解;
- 上面已列类型的数组。
- 注解参数不能有可空类型,因为 JVM 不支持将 null 作为注解属性的值存储。
- 如果注解用作另一个注解的参数,则其名称不以 @ 字符为前缀:
annotation class ReplaceWith(val expression: String)
annotation class Deprecated(
val message: String,
val replaceWith: ReplaceWith = ReplaceWith(""))
@Deprecated("This function is deprecated, use === instead", ReplaceWith("this === other"))
如果需要将一个类指定为注解的参数,请使用 Kotlin 类 (KClass)。Kotlin 编译器会自动将其转换为 Java 类,以便 Java 代码能够正常看到该注解及参数 。
import kotlin.reflect.KClass
annotation class Ann(val arg1: KClass<*>, val arg2: KClass<out Any>)
@Ann(String::class, Int::class) class MyClass
Lambda 表达式
注解也可以用于 lambda 表达式。它们会被应用于生成 lambda 表达式体的 invoke() 方法上。这对于像 Quasar 这样的框架很有用, 该框架使用注解进行并发控制。
annotation class Suspendable
val f = @Suspendable { Fiber.sleep(10) }
注解使用处目标
当对属性或主构造函数参数进行标注时,从相应的 Kotlin 元素生成的 Java 元素会有多个,因此在生成的 Java 字节码中该注解有多个可能位置 。如果要指定精确地指定应该如何生成该注解,请使用以下语法:
class Example(@field:Ann val foo, // 标注 Java 字段
@get:Ann val bar, // 标注 Java getter
@param:Ann val quux) // 标注 Java 构造函数参数
可以使用相同的语法来标注整个文件。 要做到这一点,把带有目标 file 的注解放在文件的顶层、package 指令之前或者在所有导入之前(如果文件在默认包中的话):
@file:JvmName("Foo")
package org.jetbrains.demo
如果你对同一目标有多个注解,那么可以这样来避免目标重复——在目标后面添加方括号并将所有注解放在方括号内:
class Example {
@set:[Inject VisibleForTesting]
var collaborator: Collaborator
}
支持的使用处目标的完整列表为:
- file;
- property(具有此目标的注解对 Java 不可见);
- field;
- get(属性 getter);
- set(属性 setter);
- receiver(扩展函数或属性的接收者参数);
- param(构造函数参数);
- setparam(属性 setter 参数);
- delegate(为委托属性存储其委托实例的字段)。
要标注扩展函数的接收者参数,请使用以下语法:
fun @receiver:Fancy String.myExtension() { ... }
如果不指定使用处目标,则根据正在使用的注解的 @Target 注解来选择目标 。如果有多个适用的目标,则使用以下列表中的第一个适用目标:
- param;
- property;
- field.
Java 注解
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() {
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
对于具有数组类型的其他参数,你需要显式使用数组字面值语法(自 Kotlin 1.2 起)或者 arrayOf(……):
// Java
public @interface AnnWithArrayMethod {
String[] names();
}
// Kotlin 1.2+:
@AnnWithArrayMethod(names = ["abc", "foo", "bar"])
class C
// 旧版本 Kotlin:
@AnnWithArrayMethod(names = arrayOf("abc", "foo", "bar"))
class D
访问注解实例的属性
注解实例的值会作为属性暴露给 Kotlin 代码:
// Java
public @interface Ann {
int value();
}
// Kotlin
fun foo(ann: Ann) {
val i = ann.value
}
反射
Kotlin 反射 API: KClass、 KCallable、 KFunction 和 KProperty
KClass
Kotlin反射API的主要入口就是KClass,它代表了一个类。
KClass对应的是 java.lang.class,可以用它列举和访问类中包含的所有声明,然后是它的超 类中的声明 ,等等。
MyClass::class 的写法会带给你一个KClass的实例。要在运行时取得一个对象的类,首先使用javaClass属性获得它的Java类,这直接等价于Java中的java.lang.Object.getClass ()。然后访问该类的. kotlin 扩展属性,从Java切换到Kotlin的反射API:
class Person(val 口ame: String, val age: Int)
val person = Person(”Alice”, 29)
val kClass = person.javaClass.kotlin
println(kClass.simpleName)
// Person
kClass.memberProperties.forEach { println(it .name)}
//age
//name
KClass 还有很多有用的方法,可以在标准库参考 (http://mng.bz/em4i)中看到 KClass 的完整 方法类 列表(包括扩展) 。
interface KClass<T : Any> {
val simpleName: String?
val qualif工edName: String?
val members : Collection<KCallable< * >
val constructors: Collection<KFunction<T>>
val nestedClasses: Collection<KClass<*>>
...
}
KCallable
由类的所有成员组成的列表是一个 KCallable 实例的集合。
KCallable 是函数和属性的超接口。它声明了call方法 ,允许你调用对应的函数或者对应属性的 getter :
interface KCallable<out R> {
fun call(vararg args: Any?): R
...
}
你把(被引用)函数的实参放在 varargs 列表中提供给它。下面的代码展示了如何通过反射使用 call 来调用一个函数 :
fun foo(x: Int) = println(x)
val kFunction= ::foo
kFunction.call(42)
//42
如果你传错了参数,就会报一个运行时错误:IllegalArgumentException。
对于这种情况,kotlin提供了一种更好的方法;kotlin提供了一种KFunctionN接口,N代表传入参数的个数;每一个类型都继承了 KFunction 并加上一个额外的成员 invoke,它拥有数量刚好的参数。例如, KFunction2 声明了operator fun invoke(p1:P1, p2: P2): R, 其中P1和P2代表着函数的参数类型,而R代表着函数的返回类型。
import kotlin.reflect.KFunction2
fun sum(x:Int, y:Int)=x+y
val kFunction: KFunction2<Int, Int, Int> = ::sum
println(kFunction.invoke(1, 2) + kFunction(3, 4)) 10
kFunction(l)
//ERROR: No value passed for parameter p2
现在你无法用数量不正确的实参去调用 kFunction 的 invoke 方法 :这连编 译都不能通过。因此,如果你有这样一个具体类型的 KFunction,它的形参类型 和返回类型是确定的,那么应该优先使用这个具体类型的 invoke 方法。 call 方 法是对所有类型都有效的通用手段,但是它不提供类型安全性。
KProperty
你也可以在一个 KProperty 实例上调用 call 方法, 它会调用该属性的 getter。但是属性接 口为你提供了 一个更好的获取属性值的方式: get 方法 。
顶层属性表示为KProperty0接口的实例;
var count = 0
val kProperty= ::count
kProperty.setter.call(21)
println(kProperty.get())
//21
一 个成员属性由 KProperty1的实例表示 , 它拥有 一 个单参数的 get 方 要访问该属性的值,必须提供你需要的值所属的那个对象实例 。
class Person (val 且ame: String, val age : Int)
val person = Person(”Alice”, 29)
val memberProperty = Person::age
println(memberProperty.get (person))
//29
KProperty1 是 一 个泛型类。变 量 memberProperty 的类型是 KProperty<Person, Int>,其中第一个类型参数表示接收者的类型,而第 二个 类型参数代表了属性的类型 。 这样你只能对正确类型的接收者调用它的 get 方法 : 而 memberProperty.get("Alice”)这样的调用不会通过编译。
不支持局部变量的反射访问
反射小结
KClass、 KFunction 和KParameter,全部继承了KAnnotatedElement。KClass既可以 用来表示类也可以表示对象 。 KProperty 可以表示任何属性,而它的子类 KMutableProperty 表示 一 个用 var 声明的可变属性。可以使用声明在 KProperty 和 KMutableProperty 中的特殊接口 Getter 和 Setter,把属性 的访问器当成函数使用。例如,如果你需要取回它们的注解。两个访问器的接口 都继承了 KFunction。