接《Android开发者快速上手Kotlin(三) 之 高阶函数和SAM转换》文章继续。
8 泛型
8.1 泛型的声明
Kotlin中的泛型基本上跟Java是一个思路的,只是在使用上有一点点区别。如:
fun <T> func1(a: T, b: T): T { // 单个泛型参数的方法的声明
return a
}
fun <T, R> func2(a: T, b: R): R { // 多个泛型参数的方法的声明
return b
}
class A<T>(var t:T) { // 泛型类的声明
fun a() :T {
return t
}
}
// 调用
val a:Int = func1(1,2)
val b:Long = func2(1,2L)
val aObj = A(10)
val c:Int = aObj.a()
8.2 泛型的约束
泛型可以对传入的T进行相应的约束,如果是多条件约束,可以使用关键字where。如:
fun <T : Comparable<T>> maxOf1(a: T, b: T): T { // 单约束
return if (a > b) a else b
}
fun <T> maxOf2(a: T, b: T): T where T : Comparable<T>, T : Number { // 多约束
return if (a > b) a else b
}
8.3 泛型的型变
泛型的型变就是描述泛型参数本身有继承关系的时候泛型类型的继承关系。泛型变分:协变、逆变和不变(没有继承关系)三种。
8.3.1 协变
协变:就是继承关系保持一致。在泛型类作为泛型参数类实例的生产者时,一般需要提供的泛型类要跟泛型参数类型保持继承关系的一致。
协变点:描述在类里有一函数,它的返回值类型为泛型参数。
以下示例中,泛型Class A实现的就是协变情况,T前面加out关键字,getValue方法返回参数就是协变点。如:
class A<out T>(val number: T) {
fun getValue(): T {
return number
}
}
// 调用
var number: Number = 1
var int: Int = 1
val intObj: A<Int> = A<Int>(1)
val numberObj: A<Number> = intObj // 因为T前有out,所以是允许的,number是int的父类,A<Number>当然也应该是A<Int>的父类
int = intObj.getValue()
number = intObj.getValue() // 想要一个number,返回一个int是合理。意思如:我要买一辆车,然后买了辆宝马
// int = numberObj.getValue() // 这句会报错,因为是number父型的,不一定是int类型
number = numberObj.getValue()
总结:
- 子类兼容父类。
- 子类参数的泛型生产者兼容父类参数的泛型生产者。
- 存在协变点的类的泛型参数必须声明为协变或不变。
- 当泛型类作为泛型参数类实例的生产者时用协变。
8.3.2 逆变
逆变:就是继承关系相反。在泛型类作为泛型参数类实例的消费者时,一般需要提供的泛型类要跟泛型参数类型的继承关系相反。
逆变点:描述在类里有一函数,它的参数类型为泛型参数。
以下示例中,泛型Class A实现的就是逆变情况,T前面加in关键字,compare方法接收参数就是逆变点。如:
class A<in T : Number>() {
fun compare(t:T) {
// TODO...
}
}
// 调用
val number:Number = 1
val int:Int = 1
val numberObj: A<Number> = A<Number>()
val intObj:A<Int> = numberObj // T前有in,所以是允许的,适用于父类的出现可以代替子类做事
numberObj.compare(number)
numberObj.compare(int) // 能比较number的,一定能比较int。意思如:我会开车当然也会开宝马
// intObj.compare(number) // 这句会报错,因为能比较int的,不一定能比较number
intObj.compare(int)
总结:
- 子类兼容父类。
- 父类参数的泛型消费者兼容子类参数的泛型消费者。
- 存在逆变点的类的泛型参数必须声明为逆变或不变。
- 当泛型类作为泛型参数实现的消费者时用逆变。
8.4 星投影
星投影是Kotlin中的一个新概念,可用在变量类型声明的位置用于描述一个未知的类型。通地 * 来表示,它所替换的类型在协变点返回泛型参数上限类型;在逆变点接收泛型参数下限类型。使用如:
class B<out K : Number, out V : Any>(val bKey: K, val bValue: V) {
fun getKey(): K {
return bKey
}
fun getValue(): V {
return bValue
}
}
class C<in K : Number, in V : Any>() {
fun setKey(cKey: K) {
}
fun setValue(cValue: V) {
}
}
// 调用
val b: B<*, *> = B<Long, String>(1L, "壹")
val bKey = b.getKey() // 返回的是K类型的上限,也就是Number
val bValue = b.getValue() // 返回的是V类型的上限,也就是Any
val c: C<*, *> = C<Long, String>()
// c.setKey(1L) // 不允许传入值,因为参数下限类型是Nothing
// c.setValue("壹") // 不允许传入值,因为参数下限类型是Nothing
注意:
在给星投影声明创建的对象进行类型判断时,不允许进行对实例创建时类型的判断。因为泛型类型只存在于编译期,在运行时泛型类型是会被擦除的,如:
if (b is B) { // 允许进行判断
}
if (b is B<*,*>) { // 允许进行判断
}
if (b is B<Number, Any>) { // 允许进行判断,这是运行时行为
b as B<Int, String>) // 允许进行强转,因为这是编译期行为
}
// if (b is B<Long, String>) { // 不允许进行判断,因为泛型类型只存在于编译期,在运行时泛型类型是会被擦除
// }
适用范围:
- 适用于作为类型描述的场景,如:val b<*,*>、if (b is B<*,*>)、HashMap<String, List<*>>()。
- 不能直接或间接应用在属性或函数上,如下面是不允许的:B<Long,*>()、maxOf<*>(1,3)。
8.5 内联特化
Kotlin中的泛型原理跟Java是一样的都是类型擦除。即在编译时擦除类型,运行时无实际类型生成。这种类型擦除的泛型也有人叫作“伪泛型”。类型擦除会导致泛型类型无法当作真实类型来使用,就如以下注释掉的代码是编译不通过:
fun<T> a(t:T) {
// val t = T() // 无法知道该类是否在无参构造函数
// val ts = Array<T>(3) {TODO()} // 数组其实并不是真正的泛型,它是需要编译器确定的类型,只是写法跟泛型像
// val tClass = T::class.java // 因为编译期会将类型擦除,所以不能当为一个真实类型获得它的类
val list = ArrayList<T>() // ArrayList本身在编译期是会被擦除,所以传啥都可以
}
在Kotlin中可以使用内联特化来解决语言上伪泛型所带来的一些限制。因为函数是一个内联函数,所以代码会在调用处替换过去,一旦替换后类型就是肯定的能确定的。如:
inline fun<reified T> b(t:T) {
// val t = T() // 仍然不可以,因为还是不知道你的类型是否存在无参构造函数
val ts = Array<T>(3) {TODO()} // 内联特化,允许
val tClass = T::class.java // 内联特化,允许
val list = ArrayList<T>()
}
9 反射
Kotlin中使用反射跟Java中使用反射的概念和思想是一样的。都是允许程序在运行时访问类、接口、方法、属性等语法的一类特性。在Kotlin中反射的信息主要是通过类中的Metadata注解获取的。Metadata注解内会带着本类、父类、成员等信息。使用上Kotlin依然可以使用Java的那套Type、Class、Field、Method反射对象;但其实Kotlin自身语法也支持了一套新的,它们对应KType、KClass、KProperty、KFunction。两套在Kotlin中是可以混着使用。如:
class A {
private var property = 123
private fun func(index: Int): String {
return if (index == 1) "Hello"
else property.toString()
}
}
// Java版的反射
val jClass: Class<A> = A::class.java
val constructor = jClass.getConstructor()
val obj = constructor.newInstance()
val field = jClass.getDeclaredField("property")
field.isAccessible = true
field.setInt(obj, 999)
val method = jClass.getDeclaredMethod("func", Int::class.java)
method.isAccessible = true
val result = method.invoke(obj, 2)
println(result) // 输出结果:999
// Kotlin版的反射
// 若代码需要运行在Android工程中,还需在Gradle中依赖org.jetbrains.kotlin:kotlin-reflect:$kotlin_version
var kClass: KClass<A> = A::class
val kConstructor:KFunction<A>? = kClass.primaryConstructor
val kObj = kConstructor?.call()
val properties= kClass.declaredMemberProperties.filter { it.name == "property" } // 直接通过filter过滤返回的List只有一个
properties.forEach {
it.isAccessible = true
val a = it as KMutableProperty1<A?, Int>
a.set(kObj, 888)
}
val functions:Collection<KFunction<*>> = kClass.declaredMemberFunctions // 不过滤,在forEach中才去判断也是可以的
functions.forEach {
if ("func".equals(it.name)) {
it.isAccessible = true
val result = it.javaMethod?.invoke(kObj, 2)
println(result) // 输出结果:888
}
}
10 注解
注解是对类、函数、函数参数、属性等做附加信息说明。Kotlin中使用注解跟Java中使用注解的概念和思想是一样的。Kotlin中要声明注解,要使用 annotation 关键字放在类的前面:
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class API(val say: String)
// 使用
class A(@API("Hello") var name: String)
上述代码声明了一个叫“API”的注解类,它的构造方法中定义了一个叫“say”的属性,它的标注对象是属性、作用时机是运行时,并将它在Class A的构造方法中使用。
通过反射使用注解:
如果定义的注解的作用时机是运行时,那么就可以使用反射来对它进行逻辑性处理。如:
val aObj = A("子云心")
val kClass = A::class
val properties = kClass.declaredMemberProperties.filter { it.annotations.isNotEmpty() }
properties.forEach {
var say = it.annotations.filterIsInstance<API>().first().say
val name = it as KMutableProperty1<A?, String>
name.set(aObj, say + name.get(aObj))
}
println(aObj.name) // 输出结果:Hello子云心
11 正则
在Kotlin中完全可以照着Java中的正则表达式的写法使用Pattern类完成正则的匹配,但其实Kotlin自身语法也存在一个Regex的类,使用它可以写出很Kotlin风格的代码。两套在Kotlin中是可以混着使用。如:
// Java版的正则
val source = "Hello, This my phone number:0756-1234567"
val pattern = ".*(\\d{4}-\\d{7}).*"
val matcher = Pattern.compile(pattern).matcher(source)
while (matcher.find()) {
println(matcher.group())
println(matcher.group(1))
}
// Kotlin版的正则
val source = "Hello, This my phone number:0756-1234567"
val pattern = """.*(\d{4}-\d{7}).*"""
Regex(pattern).findAll(source).toList().flatMap(MatchResult::groupValues).forEach(::println)
未完,请关注后面文章更新…