funmain(){ // 泛型类的调用方式 val myClass = MyClass<Int>() val result = myClass.method(123) println("$result")
// 泛型方法的调用方式 val myClass2 = MyClass2() // 类型推导机制,所以可省略泛型的指定。 // val result = myClass.method<Int>(123) val result2 = myClass2.method(123) println("$result2") }
/** * by 关键字连接了左边的 p 属性和右边的 Delegate() 实例。 * 这种写法就代表,将 p 属性的具体实现委托给了 Delegate 类去完成。 * 当调用 p 属性时,会自动调用 Delegate 类的 getValue(), * 当给 p 属性赋值时,会自动调用 Delegate 类的 setValue()。 * * 假设这里使用 val 关键字,那么只需要实现 getValue() 即可, * 因为 p 属性是无法在初始化后被重新赋值的。 */ var p by Delegate() }
/** * 定义 Later 类,并指定为泛型。 * 构造函数中接收一个函数类型参数,其中不接收任何参数,返回值的类型就是 Later 指定的泛型。 * * 由于懒加载技术是不会对属性进行赋值的,因此这里就不用实现 setValue() 了。 */ classLater<T>(val block:() -> T){
/** * 使用 value 变量对值进行缓存, * 如果为空就调用构造函数中传入的函数类型参数去获取值,否则就直接返回。 */ var value: Any? = null
/** * @param any Any?类型表示希望 Later 的委托功能在所有类中都可以使用。 */ operatorfungetValue(any: Any?,prop:KProperty<*>): T{ if (value == null){ value = block() } return value as T } }
funmain(){ val map = mapOf("Apple" to 1,"Banana" to 2,"Pear" to 3) for (i in map){ println("i is $i, key is ${i.key}, value is ${i.value}") } // 模仿 to() 函数的源码来编写自己的键值对构建函数。 val map1 = mapOf("Apple" with 1,"Banana" with 2,"Pear" with 3) for (i in map1){ println("i is $i, key is ${i.key}, value is ${i.value}") } }
/** * 模仿 to() 函数的源码来编写自己的键值对构建函数 * --------------------------------------------------------------------------- * to() 函数源码如下: * public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that) * --------------------------------------------------------------------------- * 首先,使用定义泛型函数的方式将 to() 定义到了 A 类型下,并且接收一个 B 类型的参数。 * 因此 A 和 B 可以是两种不同类型的泛型,也就使得我们可以构建出字符串 to 整型这样的键值对。 * --------------------------------------------------------------------------- * to() 函数的具体实现: * 创建并返回了一个 Pair 对象,实际上 A to B 这样的语法结构得到的是一个包含 A、B 数据的 Pair 对象, * 而 mapOf() 实际上接收的正是一个 Pair 类型的可变参数列表。 */ infixfun<A,B> A.with(that:B): Pair<A,B> = Pair(this,that)
泛型的高级特性
Kotlin 在泛型方面提供的特有功能
对泛型进行实化
在 JDK 1.5 之前,Java 是没有泛型的,那时诸如 List 之类的数据结构可以存储任意类型的数据,取出数据的时候也需要手动向下转型,这不仅麻烦,而且很危险。比如说在同一个 List 中存储了字符串和整型这两种数据,但是在取出数据时却无法区分具体的数据类型,如果手动将它们强制转成同一种类型,那么就会抛出类型转换异常。
于是在 JDK 1.5 中,Java 引入了泛型功能。这不仅让诸如 List 之类的数据结构变得简单好用,也让代码变得更加安全。但实际上,Java 的泛型功能是通过类型擦除机制来实现的。
funmain(){ val result1 = getGenericType<String>() val result2= getGenericType<Int>() println(result1) println(result2) // 打印结果: // class java.lang.String // class java.lang.Integer }
/** * out 关键字说明 T 只能出现在 out 位置上,同时也意味着 SimpleData 是在泛型 T 上是协变的。 * 由于泛型 T 不能出现在 in 位置上,所以也就不能使用 set() 为 data 参数赋值了,所以这里采用构造函数的方式来赋值。 * 虽然构造函数中的泛型 T 在 in 位置上,但由于使用了 val 关键字,所以 T 仍然是只读的, * 即使使用 var 关键字,只要给它加上 private 修饰符,保证这个泛型 T 对于外部而言是不可修改的,那么就都是合法的写法。 */ classSimpleData<out T>(valdata:T?){
funget():T?{ returndata } }
上述中,如果某个方法接收一个 List<Persion> 类型参数,这时传入一个 List<Student> 的实例,在 Java 中是不允许的,但在 Kotlin 中是合法的,因为已经默认给许多内置的 API 加上了协变声明,其中就包括了各种集合的类与接口。比如 List 本身就是只读的,如果想添加数据,需要使用 MutableList 才行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/** * List 简化版源码: * out 关键字说明 List 在泛型 E 上是协变的。 * 原则上声明了协变后,泛型 E 只能出现在 out 位置,但 contaion() 中,仍然出现在 in 位置上, * 这是因为 contains() 的目的非常明确,它只是为了判断当前集合中是否包含参数中传入的这个元素,而不会修改当前集合中的内容,因此这种操作实际上又是安全的。为了让编译器能够理解这种操作是安全的,使用了 @UnsafeVariance 注解,这样编译器就会允许泛型 E 出现在 in 位置上了。 */ publicinterfaceList<out E> : Collection<E> { overrideval size: Int overridefunisEmpty(): Boolean overridefuncontains(element: @UnsafeVarianceE): Boolean overridefuniterator(): Iterator<E> publicoperatorfunget(index: Int): E }
泛型的逆变
逆变的定义:假如定义了一个MyClass<T>的泛型类,其中 A 是 B 的子类型,同时MyClass<B>又是MyClass<A>的子类型,那么就可以称 MyClass 在 T 这个泛型上是逆变的。
// 如果让泛型 T 出现在 out 位置的隐患 val trans1 = object :Transformer1<Person>{ // 构建了一个 Teacher 对象,并直接返回。 overridefuntransform(name: String,age: Int): Person { // transform() 的返回值要求是一个 Person 对象,而 Teacher 是 Person 的子类,这种写法是合法的。 return Teacher(name,age) } } handleTransformer1(trans1) }
funhandleTransformer(trans:Transformer<Student>){ val student = Student("Tom",19) val result = trans.transform(student) println(result) // 打印结果: // Tom 19 }
funhandleTransformer1(trans:Transformer1<Student>){ // 期望得到的是一个 Student 对象,但实际上得到的是一个 Teacher 对象,因此造成类型转换异常。 val result = trans.transform("Tom",19) println(result) // 打印结果 // Exception in thread "main" java.lang.ClassCastException: // com.example.myapplication.test.Teacher cannot be cast to com.example.myapplication.test.Student }
/** * 用来执行一些转换操作 * in 关键字表示 T 只能出现在 in 位置,Transformer 在泛型 T 上是逆变的 */ interfaceTransformer<in T>{ funtransform(t:T):String }
/** * 如果让泛型 T 出现在 out 位置的隐患 */ interfaceTransformer1<in T>{ funtransform(name:String,age: Int):@UnsafeVariance T }