文章说明 本文内容基于Kotlin官方文档总结翻译而成,旨在为自己学习Kotlin使用。文中的代码示例、技术概念和最佳实践均参考自Kotlin官方资源和相关技术文档,如需获取最新或更详细的信息,建议查阅官方文档。
1. 扩展函数与属性 概述 Kotlin提供了扩展类或接口的能力,而无需继承或使用设计模式。通过特殊声明称为”扩展”,可以为现有类添加新功能。
扩展函数 扩展函数允许为现有类添加新的方法,即使无法访问其源代码。声明扩展函数时,需要在函数名前添加接收者类型。
fun MutableList<Int> .swap (index1: Int , index2: Int ) { val tmp = this [index1] this [index1] = this [index2] this [index2] = tmp }
扩展函数中的this
关键字对应于接收者对象(在点之前传递的对象)。现在可以在任何MutableList<Int>
上调用此函数:
val list = mutableListOf(1 , 2 , 3 )list.swap(0 , 2 )
为了增加灵活性,可以将扩展函数改为泛型:
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()) } printClassName(Rectangle())
当类有成员函数与扩展函数发生冲突时,成员函数总是优先:
class Example { fun printFunctionType () { println("Class method" ) } } fun Example.printFunctionType () { println("Extension function" ) }Example().printFunctionType()
可空接收者 扩展可以用可空接收者类型定义,这些扩展可以对值为null的对象变量调用:
fun Any?.toString () : String { if (this == null ) return "null" return toString() }
扩展属性 Kotlin支持扩展属性,类似于扩展函数:
val <T> List<T>.lastIndex: Int get () = size - 1
由于扩展不实际插入成员到类中,扩展属性不能有幕后字段,只能通过显式提供getter/setter来定义行为。
伴生对象扩展 如果类定义了伴生对象,也可以为伴生对象定义扩展:
class MyClass { companion object { } } fun MyClass.Companion.printCompanion () { println("companion" ) }MyClass.printCompanion()
扩展的作用域 通常在顶层(直接在包下)定义扩展。要在声明包外使用扩展,需要在调用位置导入它:
package org.example.declarationsfun List<String> .getLongestString () { }package org.example.usageimport org.example.declarations.getLongestStringfun 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 )
型变 型变处理泛型类型之间的子类型关系。Kotlin使用声明点型变,而非Java的使用点型变(通配符)。
协变(out) 使用out
修饰符标记类型参数使其成为协变的。协变类型参数只能用于输出位置(返回值),不能作为输入参数。
interface Source <out T > { fun nextT () : T } fun demo (strs: Source <String >) { val objects: Source<Any> = strs }
当类C
的类型参数T
被声明为out
时,C<Base>
可以安全地成为C<Derived>
的超类型。
逆变(in) 使用in
修饰符标记类型参数使其成为逆变的。逆变类型参数只能用于输入位置(参数),不能用作返回值。
interface Comparable <in T > { operator fun compareTo (other: T ) : Int } fun demo (x: Comparable <Number >) { val y: Comparable<Double > = x }
类型投影 当无法使用声明点型变时,可以使用类型投影在使用点指定型变。
fun copy (from: Array <out Any >, to: Array <Any >) { } fun fill (dest: Array <in String >, value: String ) { }
星号投影 当不关心具体类型参数时,可以使用星号投影(*
):
fun printFirst (list: List <*>) { val first = list.firstOrNull() println(first) }
泛型约束 可以通过上界限制类型参数:
fun <T : Comparable<T> > sort (list: List <T >) { } 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" )
类型擦除 泛型类型的实例在运行时不保留类型实参信息。类型信息被”擦除”,意味着Foo<Bar>
和Foo<Baz?>
的实例被擦除为Foo<*>
。
if (something is List<Int >) { } if (something is List<*>) { something.forEach { println(it) } }
3. 委托 类委托 委托模式是实现继承的替代方案,Kotlin通过关键字by
提供原生支持,无需样板代码。
interface Base { fun print () } class BaseImpl (val x: Int ) : Base { override fun print () { print(x) } } class Derived (b: Base) : Base by bfun main () { val base = BaseImpl(10 ) Derived(base).print() }
可以重写通过委托实现的接口成员:
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 val javaClass = String::class .java
函数引用 可以使用::
操作符获取函数引用:
fun isOdd (x: Int ) = x % 2 != 0 val numbers = listOf(1 , 2 , 3 )println(numbers.filter(::isOdd)) val predicate = String::isNotEmpty
属性引用 同样使用::
获取属性引用:
val x = 1 println(::x.get ()) println(::x.name) val strs = listOf("a" , "bc" , "def" )println(strs.map(String::length))
构造函数引用 可以引用构造函数:
class Foo (val x: Int )val ctor = ::Foo val foo = ctor(10 )
绑定的函数和属性引用 从特定对象获取成员引用:
val str = "Hello" val lengthProp = str::length println(lengthProp.get ()) val getProp = "Hello" ::get println(getProp(4 ))
使用KClass KClass提供了对类元数据的访问:
val kClass = String::class println (kClass.simpleName) println(kClass.qualifiedName) 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::nameprintln(nameProperty.get (person)) nameProperty.set (person, "Bob" ) println(person.name)
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) { println(obj.length) }
Kotlin编译器跟踪is
检查和显式转换,并在安全的情况下自动插入转换,称为”智能转换”。
当处理可空类型时:
val x: String? = "Hello" if (x != null ) { 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 .annotationsval special = annotations.filterIsInstance<Special>().firstOrNull()println(special?.why)
参考链接