定义
// 类名中声明泛型 T,在类中即可直接使用此泛型
class MyClass<T> {
fun method(param: T): T {
return param
}
}
// 声明泛型时,可以指定其具体类型的上界
// 比如此例中使用 NumberClass 时,具体类型必须为 Number 类或其子类
class NumberClass<T : Number> {
fun method(param: T): T {
return param
}
}
使用泛型类
// 使用泛型类时,需要指定具体类型
val myClass = MyClass<Int>()
println(myClass.method(666))
定义和使用都是类似的
class MyClass {
// 定义泛型
fun <T> method(param: T): T {
return param
}
// 定义带上界的泛型
fun <T : Number> numberMethod(param: T): T {
return param
}
}
// 使用
MyClass().method(666)
实际上,默认的泛型定义也是带有上界的,默认的上界是 Any?
,所以默认的泛型是可以指定为可空类型的,如果需要让泛型不可空,只需手动将上界指定为Any
即可。
应用泛型方法,我们可以自定义一个 apply 函数
fun <T> T.myApply(block: T.() -> Unit): T {
block()
return this
}
其中, T.() -> Unit
中的 T.
表示在此函数中提供 T 类型的上下文,也就是说在函数中可以直接调用属于 T 类型的方法。
我们自定义的这个 myApply 方法和 Kotlin 标准函数中的 apply 方法的使用方式和效果是一模一样的。
fun main() {
val a = StringBuilder().myApply {
append("666")
}
// 输出 666
println(a.toString())
}
在 JDK1.5 以前,Java 还没有泛型功能,List 之类的数据结构中可以存储任意类型的数据,取数据时必须向下转型,很容易出现类型转换异常。
JDK1.5 中引入了泛型,使得 Java 代码更加安全了。但泛型功能是通过类型擦除实现的,也就是说,泛型对于类型的约束仅在编译期有效,运行时仍然会按照 JDK1.5 之前的机制来运行。
类型擦除使得我们无法使用 a is T
或者 T::class.java
这样的语法,因为 T 的实际类型已经在运行时被擦除了。
但由于 Kotlin 的内联函数会在编译时将代码替换到调用它的地方,所以内联函数中的泛型在编译后就变成实际类型了,这就使得 Kotlin 的内联函数中的泛型可以被实化,泛型实化后就成了实际类型,我们也就可以使用 a is T
或者 T::class.java
这样的语法了。
// 在内联函数中使用 reified 关键字将泛型实化
inline fun <reified T> getType() = T::class.java
fun main() {
// 输出 class java.lang.Integer
println(getType<Int>())
// 输出 class java.lang.String
println(getType<String>())
}
使用泛型实化简化 startActivity
inline fun <reified T> Context.startActivity() = startActivity(Intent(this, T::class.java))
inline fun <reified T> Context.startActivity(block: Intent.() -> Unit) = startActivity(Intent(this, T::class.java).apply(block))
// 使用
startActivity<MainActivity>()
startActivity<MainActivity> {
putExtra("key", "value")
}
当 Student 是 Person 的子类时,我们不能得出List
是 List
的子类这样的结论,因为可能出现类型转换异常,除非这个泛型类在其泛型类型的数据上是只读的。
协变:假如定义了一个
MyClass
的泛型类,其中 A 是 B 的子类型,同时MyClass
又是MyClass
的子类型,那么我们就可以称 MyClass 在 T 这个泛型上是协变的。
// out 表示此泛型只能出现在输出位置上,也就是返回值位置上
// 或者是定义成不可变类型 val、或私有变量等等,只要保证不允许外部写,只允许外部读即可
class Data<out T>(val t:T) {
fun test(): T {
return t
}
}
逆变:假如定义了一个
MyClass
的泛型类,其中 A 是 B 的子类型,同时MyClass
又是MyClass
的子类型,那么我们就可以称 MyClass 在 T 这个泛型上是逆变的。
// in 表示此泛型只能出现在输入位置上,也就是参数位置上
class Data<in T> {
fun test(data: T) {
}
}
如果想要协变(或逆变)的功能,类中又必须使用此泛型作为参数(或返回值),可以使用 @UnsafeVariance
注解使得编译通过,这时,我们需要自己对类型转换异常负责。
注:协变与逆变建议参看郭神的《第一行代码》(第三版)第十章,解释得非常清楚,笔者这里只是做个总结。
先定义一个 Person 接口,再定义一个 Student 类实现此接口:
interface Person {
fun eat()
fun sleep()
}
class Student : Person {
override fun eat() {
println("Student is eating.")
}
override fun sleep() {
println("Student is sleeping.")
}
}
类委托的一般实现
// 委托的意思就是,将此类实现的接口中的所有功能都委托给另一个对象实现
class MyClass(private val person: Person) : Person {
override fun eat() = person.eat()
override fun sleep() = person.sleep()
}
fun main() {
val student = Student()
val myClass = MyClass(student)
// 输出 Student is eating.
myClass.eat()
// 输出 Student is sleeping.
myClass.sleep()
}
Kotlin 中,使用 by 关键字就能轻松实现类委托
// Kotlin 中,使用 by 关键字可以轻松实现委托,这种写法实现的功能和上面是一样的
class MyClass(person: Person) : Person by person
某些方法不需要委托,我们也可以重写:
class MyClass(person: Person) : Person by person {
override fun eat() {
println("Eat method in MyClass.")
}
}
fun main() {
val student = Student()
val myClass = MyClass(student)
// 输出 Eat method in MyClass.
myClass.eat()
// 输出 Student is sleeping.
myClass.sleep()
}
使用委托的好处是我们可以轻易地派生出一个相似的类,并按需修改其中的部分方法。效果上和继承很像,但委托更加的灵活,不会造成继承之后子类与父类的强关联。并且由于委托的是接口的实现,所以我们可以轻易地替换具体的实现类。
class MyClass {
// 类中的属性委托
private val fieldInClass by OtherClass()
fun test() {
// 方法中的属性委托
val fieldInMethod by OtherClass()
println(fieldInClass)
println(fieldInMethod)
}
}
class OtherClass {
// 使用 operator 修饰的 getValue 方法实现委托。
// 第一个参数表示此委托可以在什么类中使用,这里设置成 Any 表示任何类中都可使用,第二个参数暂时用不上
operator fun getValue(any: Any, property: KProperty<*>): Any {
return 1
}
// 用于方法中的属性委托
operator fun getValue(nothing: Nothing?, property: KProperty<*>): Any {
return 2
}
}
fun main() {
val myClass = MyClass()
// 输出 1 2
myClass.test()
}
Kotlin 内置了 lazy 函数,使得我们可以轻松地实现懒加载,它的内部就是使用属性委托实现的
val field by lazy { 1 }
使用属性委托,我们可以定义出自己的 lazy 函数
class MyClass {
val field by MyLazy {
println("field init")
1
}
}
class MyLazy<T>(private val block: () -> T) {
var value: T? = null
operator fun getValue(myClass: MyClass, property: KProperty<*>): T {
value ?: let {
value = block.invoke()
}
return value!!
}
}
fun main() {
// 先输出 main,再输出 field init,说明懒加载成功了
val myClass = MyClass()
println("main")
myClass.field
}
这里只是仿照 lazy 实现了简易版的懒加载,实际上 Kotlin 内置的 lazy 函数还处理了线程安全问题,项目中还是应该使用内置的 lazy 函数。
// 只有一个参数的扩展函数添加 infix 关键字后,使用时就可以用中缀语法糖
infix fun String.add(s: String) = this + s
fun main() {
// 中缀语法糖
val string = "123" add "456"
}
Kotlin 中的 to
函数就是一个中缀函数
val map = mapOf(1 to "one", 2 to "two")
to
函数的源码如下
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
A to B
实际上就是调用的A.to(B)
方法。