Kotlin泛型之 循环引用泛型(A的泛型是B的子类,B的泛型是A的子类)

IDE(编辑器)报错

循环引用泛型是我起的名字,不知道官方的名字是什么。这个问题是我在定义Android 的MVP时提出来的。具体是什么样的呢?我们看一下我的基础的MVP定义:

kotlin 复制代码
interface IPresenter<V> {  
	fun getView(): V  
}

interface IView<P> {  
	fun getPresenter(): P  
}

这里我定义了一个View和Presenter的接口,但是实际上这两个东西现在没什么关联。

这时,我想提供一个Presenter与View的基类,在基类中实现某些通用功能,并且增加这样的约束:Presenter中拿到的V必须是View的子类,View中拿到的P必须是Presenter的子类,那我们知道,可以通过kotlin,指定上界的方式。

我们修改上边的代码:改成下边这样:

kotlin 复制代码
interface IPresenter<V: IView> {  
	fun getView(): V  
}

interface IView<P: IPresenter> {  
	fun getPresenter(): P  
}

同时我们也很容易发现,编译器报错了:

IDE提示这个错误:One type argument expected for interface IPresenter<V>,意思是:IPresenter必须指定一个泛型类型才可以。在Kotlin中,这样的写法是直接保存的,在Java中,这种写法只是警告,所以Java中使用这种写法,"没问题"(没有IDE报错问题)。

Java 中如何实现这个定义

我们来看一下Java

java 复制代码
interface IPresenter<V extends IView> {  
	V getView();  
}  
  
interface IView<P extends IPresenter> {  
	P getPresenter();  
}

接着我们看IDE的提示:

出现了警告,但是不报错了,我们看一下提示是什么:
Raw use of parameterized class 'IPresenter' 意思是其实和Kotlin提示的差不多,希望你指定一个泛型类型,而不是直接使用这个类。

在Java中,为了解决这个提示,可以使用?

java 复制代码
interface IPresenter<V extends IView<?>> {  
	V getView();  
}  
  
interface IView<P extends IPresenter<?>> {  
	P getPresenter();  
}

当我们在后边的泛型中加入来规定一下泛型,编译器就不会提示错误和警告。

在Kotlin中如何实现类似的接口定义?

我们尝试在Kotlin的代码中实现增加上界的方式: 我们知道kotlin 的通配符 *对应着java中的?,那我们模仿一下Java的方式不就行了吗?

kotlin 复制代码
interface IView<P: IPresenter<*>> {  
	fun getPresenter(): P  
}  
  
interface IPresenter<V: IView<*>> {  
	fun getView(): V  
}

结果发现又报错了:

This type parameter violates the Finite Bound Restriction

然后我又尝试了很多方式都不行。

解决方案

1、利用Kotlin与Java混合开发

既然Java中是可以定义的,那么可以通过定义一个Java类型的接口不就行了。确实可以,但是当这个定义不是接口,而是抽象类时,你需要再Java代码中写一堆方法实现、属性定义,这时你就无法体验到Kotlin代码的优势了。

定义用Java,实现用Kotlin。

2、增加Self泛型

在StackOverFlow 找到了一个解决方案,就是增加一个泛型,

kotlin 复制代码
interface IView<Self: IView<Self, P>, P: IPresenter<P, Self>> {  
	fun getPresenter(): P  
}  
  
interface IPresenter<Self: IPresenter<Self,V>, V: IView<V, Self>> {  
	fun getView(): V  
}

我们通过增加一个泛型,并传递自己的方式,实现了循环引用泛型。

但是这样的代码,看起来定义变长了

当然我们为了看起来更清晰,也可以改成kotlin的另一种写法:

kotlin 复制代码
interface IView<Self, P> where Self: IView<Self, P>, P: IPresenter<P, Self>{  
	fun getPresenter(): P  
}  
  
interface IPresenter<Self, V> where Self: IPresenter<Self,V>, V: IView<V, Self>{  
	fun getView(): V  
}

虽然变长了,但是定义看起来清晰了,缺点就是,增加多了一个泛型。

也可以把Self放在后边

kotlin 复制代码
interface IView<P, Self> where P: IPresenter<Self, P>, Self: IView<P, Self> {  
	fun getPresenter(): P  
}  
  
interface IPresenter<V, Self> where Self: IPresenter<V, Self>, V: IView<Self, V>{  
	fun getView(): V  
}

在使用时,我们只需要把Self指定为当前类即可。

kotlin 复制代码
class LoginView() : IView<LoginPresenter, LoginView> {  
	override fun getPresenter(): LoginPresenter {  
		TODO("Not yet implemented")  
	}  
}  
  
class LoginPresenter() : IPresenter<LoginView, LoginPresenter> {  
	override fun getView(): LoginView {  
		TODO("Not yet implemented")  
	}  
}

完整案例MVP

通常我们不会在接口的位置直接定义循环引用,更多的时候是在实现某一个方案时,发现有通用的功能,想把通用功能统一抽离在父类里边。

案例:登录功能,我现在有一个登录的功能,为了简单,此处只增加两种方式:手机号码&密码登录。手机号码&验证码登录。

首先看这个功能的通用逻辑部分:

1、手机号码验证。

2、登录成功之后的用户数据获取。

各自独立的部分:

密码登录:检查密码位数,用密码登录。

验证码登录:发送验证码,检查验证码,用验证码登录。

kotlin 复制代码
  
interface IView<P> {  
	fun getPresenter(): P?  
}  
  
interface IPresenter<V> {  
	fun getView(): V?  
}  
  
abstract class BaseView<P : BasePresenter<*>>() : IView<P> {  
  
	private var myPresenter: P? = null  
	  
	override fun getPresenter(): P? {  
		if (myPresenter == null) {  
			myPresenter = createPresenter()  
			myPresenter?.setView(this)  
		}  
		return myPresenter  
	}  
	  
	fun showLoadingView() {  
		// 具体的展示加载中动画的实现  
	}  
	  
	fun hideLoadingView() {  
		// 具体的去除加载中动画的实现  
	}  
	  
	fun destroyView() {  
		hideLoadingView()  
		// 其他页面销毁时操作  
	}  
	  
	abstract fun createPresenter(): P?  
}  
  
  
abstract class BasePresenter<V> : IPresenter<V> {  
	protected var myView: V? = null  
	  
	override fun getView(): V? {  
		return myView  
	}  
	  
	fun setView(view: Any?) {  
		this.myView = view as V?  
	}  
	  
  
}  
  
  
abstract class BaseLoginView<Presenter, Self> :  
	BaseView<Presenter>() where Self : BaseLoginView<Presenter, Self>, Presenter : BaseLoginPresenter<Self, Presenter> {  
  
}  
  
abstract class BaseLoginPresenter<View, Self> :  
	BasePresenter<View>() where View : BaseLoginView<Self, View>, Self : BaseLoginPresenter<View, Self> {  
  
	fun checkPhone(phone: String): Boolean {  
		// 检查手机号具体实现  
		return true  
	}  
  
	fun onObtainTokenSuccess(token: String) {  
	// 登录成功具体操作  
	}  
}  
  
class LoginCodeView : BaseLoginView<LoginCodePresenter, LoginCodeView>() {  
	override fun createPresenter(): LoginCodePresenter {  
		return LoginCodePresenter()  
	}  
  
}  
  
class LoginCodePresenter : BaseLoginPresenter<LoginCodeView, LoginCodePresenter>() {  
	fun checkCode(): Boolean {  
		// 检查Code 的具体代码  
		return true  
	}  
}  
  
  
class LoginPasswordView : BaseLoginView<LoginPasswordPresenter, LoginPasswordView>() {  
	override fun createPresenter(): LoginPasswordPresenter {  
	return LoginPasswordPresenter()  
	}  
	  
}  
  
class LoginPasswordPresenter : BaseLoginPresenter<LoginPasswordView, LoginPasswordPresenter>() {  
	fun checkCode(): Boolean {  
		// 检查Code 的具体代码  
		return true  
	}  
}

参考链接

https://stackoverflow.com/questions/46682455/how-to-solve-violation-of-finite-bound-restriction-in-kotlin

相关推荐
xvch19 分钟前
Kotlin 2.1.0 入门教程(七)
android·kotlin
zhangphil3 小时前
Android BitmapShader简洁实现马赛克,Kotlin(一)
android·kotlin
五味香10 小时前
Java学习,查找List最大最小值
android·java·开发语言·python·学习·golang·kotlin
五味香1 天前
Java学习,List截取
android·java·开发语言·python·学习·golang·kotlin
xvch2 天前
Kotlin 2.1.0 入门教程(三)
android·kotlin
小李飞飞砖2 天前
kotlin的协程的基础概念
开发语言·前端·kotlin
深色風信子2 天前
Kotlin Bytedeco OpenCV 图像图像49 仿射变换 图像裁剪
opencv·kotlin·javacpp·bytedeco·仿射变换 图像裁剪
五味香3 天前
Java学习,List移动元素
android·java·开发语言·python·学习·kotlin·list
studyForMokey3 天前
【Android学习】Kotlin随笔
android·学习·kotlin
zhangphil4 天前
Android BitmapShader实现狙击瞄具十字交叉线准星,Kotlin
android·kotlin