kotlin第七天:集合数组、注解、反射

集合与数组

可空性和集合

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 中创建数组,有下面这些方法供你选择 :

  1. arrayOf 函数创建一个数组,它包含的元素是指定为该函数的实参
  2. arrayOfNulls 创建一个给定大小的数组,包含的是 null 元素。当然,它
    只能用来创建包含元素类型可空的数组。
  3. 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[]等。 因此这 些数组中的值存储时并没有装箱,而是使用了可能的最高效的方式。

要创建一个基本数据类型的数组,你有如下选择:

  1. 该类型的构造方法接收 size 参数并返回 一个使用对应基本数据类型默认值 (通常是 0)初始化好的数组。
  2. 工厂函数( IntArray 的 intArrayOf,以及其他数组类型的函数〉接收变 长参数的值并创建存储这些值的数组。
  3. 另一种构造方法,接收一个大小和一个用来初始化每个元素的 lambda。
  4. 假如你有一个持有基本数据类型装箱后 的值的数组或者集合 , 可以用对 应的转换函数把它们转换成基本数据类型的数组,比如 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), 还会提供一个自动的快速修正 。

  1. 注解只能拥有如下类型的参数 : 基本数据类型、字符串、枚举、类引用、其他 的注解类,以及前面这些类型的数组。
  2. 要把一个类指定为注解实参,在类名后加上 : :class : @MyAnnotation (MyClass::class) 。
  3. 要把另一个注解指定为一个实参 , 去掉注解名称前面的@ 。 例如,前面例子中的ReplaceWith是一个注解,但是你把它指定为Deprecated注解的实参时没有用@。
  4. 要把一个数组指定为一个实参 ,使用 arrayOf 函数:@ RequestMapping (path = arrayOf ("/foo", "/bar”))。如果注解类是在 Java 中 声明 的,命名为 value 的形参按需自动地被转换成可变长度的形参,所以不用arrayOf 函数就可以提供多个实参。
  5. 注解实参需要在编译期就是己知的,所以你不能引用任意的属性作为实参。要 把属性当作注解实参使用,你需要用 const 修饰符标记它,来告知编译器这个属 性是编译期常量。
  6. 注解参数不能有可空类型,因为 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。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值