1、泛型的实化
Java中泛型是在JDK1.5引入的,是一个伪泛型,它是通过泛型擦除机制来实现的。泛型只存在编译时期,运行时泛型就会被擦除,所以我们无法在运行时获取泛型的类型信息。
Kotlin最终也会编译生成和Java相同规格的class文件,所以Kotlin中的泛型也会被擦除,所以我们无法使用a is T 和 T::class.java
。但是Kotlin提供了一个内联函数,在编译时就会将内联函数的代码替换到实际调用的地方,所以对于内联函数来说是不存在泛型的擦除的。
所以我们是可以将内联函数中的泛型进行实化的,泛型实化的前提有2个:
- 1、必须泛型函数且是内联函数
- 2、声明泛型的地方加上关键字
reified
修饰(reified
只能用于内联泛型函数)
具体代码如下:
inline fun getType() {}
通过泛型实化可以获取泛型类型的功能,创建Reified.kt,并在其中定义一个顶层函数
inline fun getType() = T::class.java
调用getType()
fun kotlinTest() {
val stringType:Class = getType()
val intType:Class = getType()
println("stringType:${stringType.name} , intType:${intType.name}")
}
打印结果如下:
stringType:java.lang.String , intType:java.lang.Integer
可以看到我们在运行时获取到了泛型的类型。
泛型实化的使用场景
先看下打开Activity的代码
fun openActivity(){
val intent=Intent(this,MainActivity::class.java)
startActivity(intent)
}
下面看下如何通过泛型的实化来实现这个功能
inline fun openActivity(context: Context){
val intent=Intent(context,T::class.java)
context.startActivity(intent)
}
但是问题又来了,怎么使用Intent进行数据的传输呢?其实很简单,我们可以使用高阶函数来实现,具体传输的数据由调用者来完成。
inline fun openActivity(context: Context, block: Intent.() -> Unit) {
val intent = Intent(context, T::class.java)
intent.block()
context.startActivity(intent)
}
调用就很简单了
openActivity(this) {
putExtra("name", "LiLei")
putExtra("age", 12)
}
这样我们就完成了打开页面通用的方法,可以把openActivity()函数定义成顶层函数,这样我们就可以在任意位置调用了,不过泛型的实化是无法在Java中调用的。下面看下在Java中如何调用openActivity()
ReifiedKt.openActivity(context, new Function1() {
@Override
public Unit invoke(Intent intent) {
intent.putExtra("name", "LiLei");
intent.putExtra("age", 12);
return null;
}
});
报错信息如下:
openActivity(android.content.Context, kotlin.jvm.functions.Function1 super android.content.Intent,kotlin.Unit>)' has private access in 'com.example.abu.serviceproject.ReifiedKt
可以看到在Java中无权访问该方法的。
2、泛型的协变
一个泛型类或泛型接口中的方法,方法的参数列表是接收数据的,称为in位置,方法的返回值是返回数据的,称为out位置。
在讲解泛型的协变之前,先看个例子:创建一个Person类、Student类、Teacher类,让Student和Teacher继承Person类。
open class Person(var name: String, var age: Int) {}
class Student(name: String, age: Int) : Person(name, age)
class Teacher(name: String, age: Int) : Person(name, age)
创建一个方法,接收的参数是Person对象
fun handleData(person: Person){}
该方法接收Person对象,那么能不能向其中传入Student对象或Teacher对象?
Student和Teacher都是Person的子类,所以是肯定可以的。下面我们修改下handleData()接收的参数为List
对象
fun handleData(person: List) {}
那么handleData()能接收List
在Java中是不允许的,List
创建泛型类SimpleData
class SimpleData() {
private var t: T? = null
fun set(t: T) {
this.t = t
}
fun get(): T? = t
}
创建handleData()函数接收SimpleData
fun test() {
val stu = Student("张", 12)
val stuData = SimpleData()
stuData.set(stu)
handleSimpleData(stuData) //编译失败
val result: Student? = stuData.get()
}
fun handleSimpleData(data: SimpleData) {
val teacher = Teacher("李", 40)
data.set(teacher)
}
由于SimpleDatahandleSimpleData(stuData)
肯定是编译失败的,这里假设编译通过,那么在handleSimpleData()
中创建Teacher对象并通过data.set(teacher)
替换Student
对象,然后通过stuData.get()
获取的是Student
对象,实际上返回的是Teacher
对象,这就造成了类型转换异常。所以这种写法是不合法的。
如果不允许修改SimpleData
中的数据,是不是就能解决类型转换异常的问题了。
Kotlin中提供了out
关键字来保证泛型只能定义在返回值的位置,不能定义在接收参数的位置,使用out修改SimpleData类,代码如下:
class SimpleData(val t: T) {
fun get(): T = t
}
不是说T不能出现在接收参数的位置吗?为什么能出现在构造函数中,这是因为val
修饰的t是不允许修改,这也是符合安全规范的。同理,我们也可以使用private var t:T
来代替val t:T
。
此时handleSimpleData(data: SimpleData
中就能接收SimpleData
了
fun test() {
val stu = Student("张", 12)
val stuData = SimpleData(stu)
handleSimpleData(stuData) //编译成功
val student: Student = stuData.get()
Log.e(tag,"name is ${student.name} , age is ${student.age}。")
}
fun handleSimpleData(data: SimpleData) {
val person = data.get()
person.name = "李"
person.age = 40
}
handleSimpleData(data: SimpleData
中就能接收SimpleData
说明SimpleData
3、泛型的逆变
定义一个 MyClass
下面举个例子来看下:
interface Transform {
fun transform(t: T): String
}
fun main() {
val tranform = object : Transform {
override fun transform(t: Person): String {
return "name is ${t.name}, age is ${t.age}"
}
}
transformData(tranform)//编译失败
}
fun transformData(transform: Transform) {
val student = Student("张", 12)
transform.transform(student)
}
上面代码是无法正常的编译的,我们可以使用逆变来解决上面问题。修改Transform接口
interface Transform {
fun transform(t: T): String
}
只要加上关键字in
就可以实现泛型的逆变了,Transform
也就变成了Transform
的子类了。