关于Kotlin泛型遇到的问题

在使用kotlin的过程中,遇到了一些泛型上的问题,索性统一研究下;

关于通配

Java里有?、extends、super;
Kotlin里有*、out、in;
虽然表示方法不同,但其实可以认为是分别对应且等价的;

需要明确的是其特性,比如:

通配符类型参数 ? extends E 表示此方法接受 E 或者 E 的 一些子类型对象的集合,而不只是 E 自身。 这意味着我们可以安全地从其中(该集合中的元素是 E 的子类的实例)读取 E,但不能写入, 因为我们不知道什么对象符合那个未知的 E 的子类型。 反过来,该限制可以让Collection表示为Collection<? extends Object>的子类型。 简而言之,带 extends 限定(上界)的通配符类型使得类型是协变的(covariant)。

这段内容来自Kotlin中文网-泛型,主要是说明两个特性:

? extends E表示数据不能安全写入,但可以安全读出;(协变)
? super E表示数据可以安全写入,但无法安全读出;(逆变)

这里的“安全”当然是指类型安全,因为类型擦除的原因,泛型的安全检查全部在编译时进行。

概念什么的太模糊,动手就知道了:

	static void testOutType() {
    
    
		List<Integer> list1 = new ArrayList<>();
		List<? extends Number> list2 = new ArrayList<>();
		list1.add(1);
		list1.add(2);
//		list2.add(1);//不允许
		list2 = list1;
		//取出来的数据可以确定是Number的子类
		Number n1 = list2.get(0);
		Number n2 = list2.get(1);
	}

声明为? extends Number的list2是无法添加Integer的,编译直接无法通过;
虽然Integer是Number的子类,但编译器认为不能确定添加(写入)的数据是否安全,有可能是Integer也有可能是其他Number子类,所以并不能安全写入;
但读取的时候是可以确定是Number的子类,所以认为是安全的;

	static void testInType() {
    
    
		List<Number> list1 = new ArrayList<>();
		List<? super Integer> list2 = new ArrayList<>();
		list2 = list1;
		list2.add(1);//允许
		list2.add(2);//允许
		//取出来的数据只能视为Object
		Integer a = (Integer) list2.get(0);
		Object b = list2.get(1);
	}

而声明为? super Integer的list2是可以添加Integer的,相比之下宽松一些;
因为确定了添加的数据肯定是Integer的父类,所以编译器认为是安全的;
相反读取的时候,只能将读取的数据视为Object,并显示转换,这样明显是不安全的,所以认为是读取不安全的;

	static void testType(){
    
    
		List<Integer> list1 = new ArrayList<>();
		List<?> list2 = new ArrayList<>();
		list1.add(1);
//		list2.add(2);//不允许
		list2 = list1;
		Object a = list1.get(0);
	}

声明为?的list2则兼顾了上面两种特性,一是不允许添加数据,因为不确定类型安全,二是读取数据只能为Object;
?默认实现为? extends Object,所以会具有这样的特性;

那么,对应kotlin中:

? extends E	等价于out E;
? super E等价于in E;
?等价于*;

问题

现在有这么3个接口,

interface Gesture {
    
    
    fun onMove()
    fun setGestureListener(listener: GestureListener<out Gesture>)
}

interface Fling : Gesture {
    
    
    fun onFling()
}

interface GestureListener<F> {
    
    
    fun done(gesture: F)
}

并且有这么一个实现类,用Java写出来是这样的:

public class FlingImpl implements Fling {
    
    
	private GestureListener listener;//可以规避

	@Override
	public void onMove() {
    
    
		listener.done(this);//因为没有声明F,规避了extends无法写入的问题
	}

	@Override
	public void setGestureListener(GestureListener<? extends Gesture> listener) {
    
    
		this.listener = listener;
	}

	@Override
	public void onFling() {
    
    
		System.out.println("invoke onFling");
	}
}

这里的GestureListener允许不声明类型参数,但kotlin中这样是不行的,kotlin不允许使用没有指定类型实参的泛型类型
比如像下面这样写是会报错的:

class FlingImpl : Fling {
    
    
    private var listener: GestureListener<out Gesture>? = null

    override fun onMove() {
    
    
        listener?.done(this)//编译出错
    }

    override fun setGestureListener(listener: GestureListener<out Gesture>) {
    
    
        this.listener = listener
    }

    override fun onFling() {
    
    
        println("invoke onFling")
    }
}

这里的GestureListener就规避不了了,因为必须要指定类型参数;
而指定了类型参数,因为out Gesture的特性,这个listener是无法写入的,所以listener?.done方法是无法通过编译的,会报这么一个错误:

Kotlin: Out-projected type 'GestureListener<*>?' prohibits the use of 'public abstract fun done(gesture: F): Unit defined in com.xter.callback.kotlin.GestureListener'

解决

最简单也是最实用的解决办法,还得是强制转换,因为调用的类或接口有可能并不是能够改动的;
那么解决办法就是把out改为in,使用as进行强制转换:

class FlingImpl : Fling {
    
    
    private var listener: GestureListener<in Fling>? = null

    override fun onMove() {
    
    
        listener?.done(this)
    }

    override fun setGestureListener(listener: GestureListener<out Gesture>) {
    
    
        this.listener = listener as? GestureListener<in Fling>?
    }

    override fun onFling() {
    
    
        println("invoke onFling")
    }
}

这样就能正常运行了。
同样的思路在Java里也是适用的,只是Java相对宽松一些,所以在某些场景可以不使用转换就完成。

猜你喜欢

转载自blog.csdn.net/ifmylove2011/article/details/105989476
今日推荐