入门
-
型变(Variant)有三种基本形态
- 协变(Covariant): string->object (子类到父类的转换)
- 逆变(Contravariant):object->string (父类到子类的转换)
- 不变(Invariant):基于上面两种情况,不可变
-
对于简单的泛型类、泛型函数与java差不多。Kotlin泛型的是型变支持,kotlin提供了【声明处型变】和【使用处型变】,java只支持【使用处型变】
-
代码
fun main() { //无法推断,编译错误 //var parent0 = Parent() var parent0 = Parent<String>() println(parent0.info) //null val parent1 = Parent<String>("苹果") println(parent1.info)//苹果 //显示指定类型 var parent2: Parent<Int> = Parent<Int>(3) println(parent2.info)//3 //可省略"菱形语法",自动推断处Apple<Double> var parent3 = Parent(3.5) println(parent3.info)//3.5 val child = Child() println(child.info)//child } /** * 定义泛型类 */ open class Parent<T> { open var info: T? constructor() { info = null } constructor(info: T) { this.info = info } } open class Child : Parent<String> { constructor() { info = "child" } //重写构造器 constructor(info: String) { this.info = info } }
型变
-
java的泛型是不支持型变的,java采用通配符来解决这个问题。即在java中
List<String>
并不是List<Object>
的子类,因此不能将List<String>
赋值给List<Object>
-
如果不支持型变,以下的
numSet
是Set<Number>
,则addAll传入的参数也必须是Set<Number>
,而Set<Integer>
是无法传入的。但是其实是安全的,java中采用通配符的方式来解决这个问题1. 如果addAll定义为这样 public interface Collection<E> extends Iterable<E> { boolean addAll(Collection<E> c) } Set<Number> numSet = new HashSet<>(); Set<Integer> intSet = new HashSet<>(); numSet.addAll(intSet) 2. java中采用通配符(通配符上限)的方式来支持型变 public interface Collection<E> extends Iterable<E> { boolean addAll(Collection<? extends E> c) }
-
泛型存在如下规律
- 通配符上限(泛型协变)意味着从中取出(out)对象是安全的,但传入对象(in)则是不可靠
- 通配符下限(泛型逆变)意味着向其中传入(in)对象是安全的,但取出对象(out)则不可靠
- kotlin利用以上两条规律,抛弃了泛型通配符语法,而是利用in、out来让泛型支持型变
-
协变
fun main() { var user = User<String>("jannal") println(user.info) //因为T支持协变,所以 User<String>可以当作 User<Any>用 var user1: User<Any> = user println(user1.info) } class User<out T> { val info: T constructor(info: T) { this.info = info } fun info(): T { println(this.info) return info } }
-
逆变: T只能出现在方法的形参声明中,不能出现在方法的返回值声明中。一旦声明了逆变,可以安全的将
Person<Any>
和Person<CharSequence>
赋值给Person<String>
,只要尖括号中的类型是String的父类即可。fun main() { var person = Person<Any>() person.info(111) var person2: Person<String> = person person2.info("jannal") } class Person<in T> { fun info(t: T) { println(t) } }
-
如果一个类中有的方法使用泛型声明返回类型,有的方法使用泛型声明形参类型,那么该类就不能使用声明处型变。