Kotlin高级特性

文章说明

本文内容基于Kotlin官方文档总结翻译而成,旨在为自己学习Kotlin使用。文中的代码示例、技术概念和最佳实践均参考自Kotlin官方资源和相关技术文档,如需获取最新或更详细的信息,建议查阅官方文档。


1. 扩展函数与属性

概述

Kotlin提供了扩展类或接口的能力,而无需继承或使用设计模式。通过特殊声明称为”扩展”,可以为现有类添加新功能。

扩展函数

扩展函数允许为现有类添加新的方法,即使无法访问其源代码。声明扩展函数时,需要在函数名前添加接收者类型。

fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this'对应于列表
this[index1] = this[index2]
this[index2] = tmp
}

扩展函数中的this关键字对应于接收者对象(在点之前传递的对象)。现在可以在任何MutableList<Int>上调用此函数:

val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // 'swap()'内部的'this'将保存'list'的值

为了增加灵活性,可以将扩展函数改为泛型:

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1]
this[index1] = this[index2]
this[index2] = tmp
}

扩展的静态解析特性

扩展并不实际修改它们扩展的类。通过定义扩展,不会向类插入新成员,而只是让新函数可以通过点符号在变量上调用。

扩展函数是静态解析的,这意味着调用哪个扩展函数在编译时已经确定,基于接收者的声明类型。

open class Shape
class Rectangle: Shape()

fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"

fun printClassName(s: Shape) {
println(s.getName()) // 总是打印"Shape"
}

// 即使传入Rectangle实例,也会调用Shape的扩展函数
printClassName(Rectangle())

当类有成员函数与扩展函数发生冲突时,成员函数总是优先:

class Example {
fun printFunctionType() { println("Class method") }
}

fun Example.printFunctionType() { println("Extension function") }

Example().printFunctionType() // 打印"Class method"

可空接收者

扩展可以用可空接收者类型定义,这些扩展可以对值为null的对象变量调用:

fun Any?.toString(): String {
if (this == null) return "null"
// 在null检查之后,'this'会自动转换为非空类型
return toString()
}

扩展属性

Kotlin支持扩展属性,类似于扩展函数:

val <T> List<T>.lastIndex: Int
get() = size - 1

由于扩展不实际插入成员到类中,扩展属性不能有幕后字段,只能通过显式提供getter/setter来定义行为。

// 错误示例:扩展属性不允许初始化器
// val House.number = 1

伴生对象扩展

如果类定义了伴生对象,也可以为伴生对象定义扩展:

class MyClass {
companion object { } // 将被称为"Companion"
}

fun MyClass.Companion.printCompanion() { println("companion") }

// 调用方式
MyClass.printCompanion()

扩展的作用域

通常在顶层(直接在包下)定义扩展。要在声明包外使用扩展,需要在调用位置导入它:

// 在org.example.declarations包中定义
package org.example.declarations
fun List<String>.getLongestString() { /*...*/ }

// 在另一个包中使用
package org.example.usage
import org.example.declarations.getLongestString

fun main() {
val list = listOf("red", "green", "blue")
list.getLongestString()
}

2. 泛型

基本概念

泛型允许定义接受不同类型参数的类和函数,增强代码重用性和类型安全性。

// 泛型类
class Box<T>(t: T) {
var value = t
}

// 创建实例
val box1: Box<Int> = Box<Int>(1)
// 类型推断
val box2 = Box(1) // 编译器推断为Box<Int>

型变

型变处理泛型类型之间的子类型关系。Kotlin使用声明点型变,而非Java的使用点型变(通配符)。

协变(out)

使用out修饰符标记类型参数使其成为协变的。协变类型参数只能用于输出位置(返回值),不能作为输入参数。

// Source<T>是T的生产者
interface Source<out T> {
fun nextT(): T
}

fun demo(strs: Source<String>) {
// 允许将Source<String>赋值给Source<Any>
val objects: Source<Any> = strs
}

当类C的类型参数T被声明为out时,C<Base>可以安全地成为C<Derived>的超类型。

逆变(in)

使用in修饰符标记类型参数使其成为逆变的。逆变类型参数只能用于输入位置(参数),不能用作返回值。

// Comparable<T>是T的消费者
interface Comparable<in T> {
operator fun compareTo(other: T): Int
}

fun demo(x: Comparable<Number>) {
// 允许将Comparable<Number>赋值给Comparable<Double>
val y: Comparable<Double> = x
}

类型投影

当无法使用声明点型变时,可以使用类型投影在使用点指定型变。

// 使用点型变:out投影
fun copy(from: Array<out Any>, to: Array<Any>) {
// 允许从Array<String>复制到Array<Any>
// 但不能向from写入数据
}

// 使用点型变:in投影
fun fill(dest: Array<in String>, value: String) {
// 允许向Array<Any>写入String
}

星号投影

当不关心具体类型参数时,可以使用星号投影(*):

// 对于协变类型Foo<out T>,Foo<*>等同于Foo<out Any?>
// 对于逆变类型Foo<in T>,Foo<*>等同于Foo<in Nothing>
// 对于不变类型Foo<T>,Foo<*>等同于Foo<out Any?>用于读取,Foo<in Nothing>用于写入

fun printFirst(list: List<*>) {
val first = list.firstOrNull() // 返回Any?类型
println(first)
}

泛型约束

可以通过上界限制类型参数:

// T必须是Comparable<T>的子类型
fun <T : Comparable<T>> sort(list: List<T>) {
// ...
}

// 多个约束使用where子句
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
where T : CharSequence,
T : Comparable<T> {
return list.filter { it > threshold }.map { it.toString() }
}

泛型函数

函数也可以有类型参数:

fun <T> singletonList(item: T): List<T> {
return listOf(item)
}

// 调用泛型函数
val l1 = singletonList<Int>(1)
// 类型推断
val l2 = singletonList("a") // 推断为List<String>

类型擦除

泛型类型的实例在运行时不保留类型实参信息。类型信息被”擦除”,意味着Foo<Bar>Foo<Baz?>的实例被擦除为Foo<*>

// 不能在运行时检查泛型类型
if (something is List<Int>) { } // 错误,无法检查是否为List<Int>

// 可以检查星号投影类型
if (something is List<*>) {
something.forEach { println(it) } // 元素类型为Any?
}

3. 委托

类委托

委托模式是实现继承的替代方案,Kotlin通过关键字by提供原生支持,无需样板代码。

interface Base {
fun print()
}

class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}

// 通过by将接口实现委托给参数b
class Derived(b: Base) : Base by b

fun main() {
val base = BaseImpl(10)
Derived(base).print() // 打印10
}

可以重写通过委托实现的接口成员:

class Derived(b: Base) : Base by b {
override fun print() { print("abc") }
}

重写的成员不会从委托对象调用,委托对象只能访问其自身对接口成员的实现。

委托属性

委托属性允许将属性的getter和setter委托给另一个对象,适用于多种常见属性类型,如延迟加载、可观察属性等。

基本语法:

class Example {
var p: String by Delegate()
}

委托必须提供getValue()函数,对于可变属性还需提供setValue()函数:

class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}

operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}

标准委托

Kotlin标准库提供了几种常用的委托实现:

延迟属性(lazy)
val lazyValue: String by lazy {
println("computed!")
"Hello"
}

属性值仅在首次访问时计算,后续访问返回缓存结果。

可观察属性(observable)
var name: String by Delegates.observable("<no name>") {
prop, old, new -> println("$old -> $new")
}

每次属性赋值时会调用处理程序。

存储在Map中的属性
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}

// 使用
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))

委托给另一个属性

一个属性可以将getter和setter委托给另一个属性:

var newName: Int = 0
@Deprecated("Use 'newName' instead", ReplaceWith("newName"))
var oldName: Int by this::newName

提供委托(provideDelegate)

通过定义provideDelegate运算符,可以扩展创建属性委托对象的逻辑,实现属性初始化时的验证等功能。

class ResourceLoader<T>(id: ResourceID<T>) {
operator fun provideDelegate(
thisRef: MyUI,
prop: KProperty<*>
): ReadOnlyProperty<MyUI, T> {
checkProperty(thisRef, prop.name)
return ResourceDelegate()
}
}

4. 反射

Kotlin反射概述

反射是在运行时检查和操作类、对象、函数和属性的能力。Kotlin的反射API允许在程序运行时获取类型信息并动态调用方法。

类引用

可以使用::class获取Kotlin类的引用:

val c = String::class  // 获取KClass<String>

// 对于Java类互操作
val javaClass = String::class.java // 获取Class<String>

函数引用

可以使用::操作符获取函数引用:

fun isOdd(x: Int) = x % 2 != 0
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // [1, 3]

// 引用类成员函数
val predicate = String::isNotEmpty

属性引用

同样使用::获取属性引用:

val x = 1
println(::x.get()) // 1
println(::x.name) // "x"

// 引用类属性
val strs = listOf("a", "bc", "def")
println(strs.map(String::length)) // [1, 2, 3]

构造函数引用

可以引用构造函数:

class Foo(val x: Int)
val ctor = ::Foo // 引用构造函数

// 使用构造函数引用创建实例
val foo = ctor(10)

绑定的函数和属性引用

从特定对象获取成员引用:

val str = "Hello"
val lengthProp = str::length // 绑定的属性引用
println(lengthProp.get()) // 5

val getProp = "Hello"::get // 绑定的函数引用
println(getProp(4)) // 'o'

使用KClass

KClass提供了对类元数据的访问:

val kClass = String::class
println(kClass.simpleName) // "String"
println(kClass.qualifiedName) // "kotlin.String"
println(kClass.members.joinToString { it.name }) // 列出所有成员

使用反射创建实例

val kClass = String::class
val constructor = kClass.constructors.first { it.parameters.isEmpty() }
val instance = constructor.call() // 创建空字符串

访问和修改属性

class Person(var name: String, var age: Int)

val person = Person("Alice", 30)
val nameProperty = Person::name
println(nameProperty.get(person)) // "Alice"
nameProperty.set(person, "Bob")
println(person.name) // "Bob"

5. 类型系统深入

类型别名(typealias)

类型别名为现有类型提供替代名称,特别适用于泛型类型和函数类型:

// 简化长泛型类型
typealias NodeSet = Set<Network.Node>

// 函数类型别名
typealias Predicate<T> = (T) -> Boolean

// 嵌套类的别名
class A {
inner class Inner
}
typealias AInner = A.Inner

类型别名不会引入新类型,它们等同于相应的底层类型。

内联类(value class)

内联类(又称值类)是对单一值的包装,在运行时通常不创建包装器实例,从而提高性能:

@JvmInline
value class Password(private val s: String)

// 使用
val securePassword = Password("Don't try this in production")

内联类必须有且仅有一个属性在主构造函数中初始化。在大多数情况下,内联类的实例会被编译器优化掉,直接使用底层值。

内联类可以定义方法和实现接口:

@JvmInline
value class Name(val s: String) {
val length: Int
get() = s.length

fun greet() {
println("Hello, $s")
}
}

Nothing类型

Nothing类型表示永不返回的函数的返回类型。它是所有其他类型的子类型,没有实例:

fun fail(message: String): Nothing {
throw IllegalArgumentException(message)
}

// 使用示例
val list = listOf(1, 2, 3)
val name = list.firstOrNull() ?: fail("List is empty")

Nothing常用于表示永远不会返回的函数,如抛出异常或无限循环的函数。

实例检查与智能转换

is!is操作符用于检查对象是否符合给定类型:

if (obj is String) {
// 智能转换:obj自动转换为String类型
println(obj.length)
}

Kotlin编译器跟踪is检查和显式转换,并在安全的情况下自动插入转换,称为”智能转换”。

当处理可空类型时:

val x: String? = "Hello"
if (x != null) {
// x被智能转换为非空String
println(x.length)
}

注解类

注解允许将元数据附加到代码中。自定义注解通过annotation class声明:

annotation class Fancy

// 使用注解
@Fancy
class MyClass { }

注解可以有构造函数参数:

annotation class Special(val why: String)

@Special("example")
class MyClass { }

注解可以用于多种目标,如类、函数、属性等,可以通过@Target指定:

@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class MyAnnotation

注解可以通过反射在运行时访问:

val annotations = MyClass::class.annotations
val special = annotations.filterIsInstance<Special>().firstOrNull()
println(special?.why) // "example"

参考链接