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

news2024/9/20 18:37:21

IDE(编辑器)报错

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

interface IPresenter<V> {  
	fun getView(): V  
}

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

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

这时,我想提供一个Presenter与View的基类,在基类中实现某些通用功能,并且增加这样的约束:Presenter中拿到的V必须是View的子类,View中拿到的P必须是Presenter的子类,那我们知道,可以通过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

interface IPresenter<V extends IView> {  
	V getView();  
}  
  
interface IView<P extends IPresenter> {  
	P getPresenter();  
}

接着我们看IDE的提示:

请添加图片描述

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

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

interface IPresenter<V extends IView<?>> {  
	V getView();  
}  
  
interface IView<P extends IPresenter<?>> {  
	P getPresenter();  
}

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

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

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

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 找到了一个解决方案,就是增加一个泛型,

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的另一种写法:

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放在后边

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指定为当前类即可。

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、登录成功之后的用户数据获取。

各自独立的部分:
密码登录:检查密码位数,用密码登录。
验证码登录:发送验证码,检查验证码,用验证码登录。

  
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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1631544.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

云原生Kubernetes: K8S 1.29版本 部署Sonarqube

一、实验 1.环境 &#xff08;1&#xff09;主机 表1 主机 主机架构版本IP备注masterK8S master节点1.29.0192.168.204.8 node1K8S node节点1.29.0192.168.204.9node2K8S node节点1.29.0192.168.204.10已部署Kuboard &#xff08;2&#xff09;master节点查看集群 1&…

【已解决】Python Selenium chromedriver Pycharm闪退的问题

概要 根据不同的业务场景需求&#xff0c;有时我们难免会使用程序来打开浏览器进行访问。本文在pycharm中使用selenium打开chromedriver出现闪退问题&#xff0c;根据不断尝试&#xff0c;最终找到的问题根本是版本问题。 代码如下 # (1) 导入selenium from selenium import …

RDD算子使用----transformation转换操作

RDD算子使用 transformation转换操作 map算子 rdd.map(p: A > B):RDD&#xff0c;对rdd集合中的每一个元素&#xff0c;都作用一次该func函数&#xff0c;之后返回值为生成元素构成的一个新的RDD。 val sc new SparkContext(conf)//map 原集合*7val list 1 to 7//构建…

纯血鸿蒙APP实战开发——全局状态保留能力弹窗

全局状态保留能力弹窗 介绍 全局状态保留能力弹窗一种很常见的能力&#xff0c;能够保持状态&#xff0c;且支持全局控制显隐状态以及自定义布局。使用效果参考评论组件 效果图预览 使用说明 使用案例参考短视频案例 首先程序入口页对全局弹窗初始化&#xff0c;使用Globa…

Redis基本數據結構 ― List

Redis基本數據結構 ― List 介紹常用命令範例1. 將元素推入List中2. 取得List內容3. 彈出元素 介紹 Redis中的List結構是一個雙向鏈表。 LPUSH LPOP StackLPUSH RPOP QueueLPUSH BRPOP Queue(消息隊列) 常用命令 命令功能LPUSH將元素推入列表左端RPUSH將元素推入列表右…

ZooKeeper 环境搭建详细教程之三(真集群)

ZooKeeper 搭建详细步骤之三(真集群) ZooKeeper 搭建详细步骤之二(伪集群模式) ZooKeeper 搭建详细步骤之一(单机模式) ZooKeeper 及相关概念简介 真集群搭建 搭建 ZooKeeper 真集群涉及多个步骤,包括准备环境、配置文件设置、启动服务以及验证集群状态。 以下是一个简…

如何基于Zookeeper实现注册中心模型?

在分布式系统中&#xff0c;通常会存在几十个甚至上百个服务&#xff0c;开发人员可能甚至都无法明确系统中到底有哪些服务正在运行。另一方面&#xff0c;我们很难同时确保所有服务都不出现问题&#xff0c;也很难保证当前的服务部署方式不做调整和优化。由于自动扩容、服务重…

天冕科技亮相第十七届深圳国际金融博览会!

第十七届深圳国际金融博览会在深圳会展中心正式开幕&#xff0c;天冕科技跟随南山区组团集体亮相&#xff0c;充分展现金融活力。此次金博会&#xff0c;南山区政府共遴选了包括天冕科技在内的三家优秀金融科技企业组团参展&#xff0c;以特色与创新的案例展示了辖区金融业发展…

Power BI:如何将文件夹批量Excel(多sheet页)文件导入?

故事背景&#xff1a; 业务同事想用Power BI分析近两年市场费用。 数据源全部是Excel文件&#xff0c;并且以每月一个Excel文件的方式&#xff0c;统一存放到同一文件夹下面。 重点&#xff0c;每张Excel文件会有多张sheet页&#xff0c;用区分每家分公司的费用信息。 目前…

照片误删怎么办?华为手机删除的照片如何恢复?

我们在使用华为手机时&#xff0c;可能会因为各种原因不小心删除一些照片。如果这些照片对我们来说很重要&#xff0c;那么恢复它们是非常必要且急迫的。那么华为手机删除的照片如何恢复呢&#xff1f;本文将为您介绍3种恢复华为手机中误删照片的方法。 如何恢复华为手机中被删…

Vue3 v3.4之前如何实现组件中多个值的双向绑定?

文章目录 基础代码1. watch2. computed&#xff08;推荐&#xff09; 官方给的例子是关于el-input的&#xff0c;如下。但是input不是所有组件标签都有的属性啊&#xff0c;有没有一种通用的办法呢&#xff1f; <script setup> defineProps({firstName: String,lastName…

VS2022 配置OpenCV开发环境详细教程

OpenCV OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉和机器学习软件库&#xff0c;由Intel开发并首先发布于1999年。OpenCV被广泛用于实时图像处理、视频分析、物体检测、面部识别、机器人视觉以及许多其他领域。它支持C、Pytho…

【vscode环境配置系列】vscode远程debug配置

VSCODE debug环境配置 插件安装配置文件debug 插件安装 安装C/C, C/C Runner 配置文件 在项目下建立.vscode文件夹&#xff0c;然后分别建立c_cpp_properties.json&#xff0c; launch.json&#xff0c;tasks.json&#xff0c;内容如下&#xff1a; c_cpp_properties.json:…

机器学习:基于Sklearn、XGBoost框架,使用逻辑回归、支持向量机和XGBClassifier预测帕金森病

前言 系列专栏&#xff1a;机器学习&#xff1a;高级应用与实践【项目实战100】【2024】✨︎ 在本专栏中不仅包含一些适合初学者的最新机器学习项目&#xff0c;每个项目都处理一组不同的问题&#xff0c;包括监督和无监督学习、分类、回归和聚类&#xff0c;而且涉及创建深度学…

Panoptic Domain Adaptive Mask R-CNN (PDAM) 论文总结

论文&#xff08;CVPR会议&#xff09;&#xff1a; Unsupervised Instance Segmentation in Microscopy Images via Panoptic Domain Adaptation and Task Re-weighting &#xff08;TMI期刊&#xff09;&#xff1a;PDAM: A Panoptic-Level Feature Alignment Framework for …

微软如何打造数字零售力航母系列科普03 - Mendix是谁?作为致力于企业低代码服务平台的领头羊,它解决了哪些问题?

一、Mendix 成立的背景 Mendix的成立是为了解决软件开发中最大的问题&#xff1a;业务和IT之间的脱节。这一挑战在各个行业和地区都很普遍&#xff0c;很简单&#xff1a;业务需求通常被描述为IT无法正确解释并转化为软件。业务和IT之间缺乏协作的原因是传统的代码将开发过程限…

[论文笔记]Language Modeling with Gated Convolutional Networks

引言 今天带来论文Language Modeling with Gated Convolutional Networks的笔记&#xff0c;该篇工作提出了GLU(Gated Linear Units&#xff0c;门控线性单元)。 注意该篇工作是2016年发表&#xff0c;是在Transformer论文发表之前。当时作者认为语言建模的主要方法是基于循环…

百度语音识别的springboot应用

1、pom依赖 <dependency> <groupId>com.baidu.aip</groupId> <artifactId>java-sdk</artifactId> <version>4.16.18</version> </dependency> 2、测试的demo 创建语音识别应用 百度智能云-管理中心 (baidu.com) 代码中要…

qt-C++笔记之滑动条QSlider和QProgressBar进度条

qt-C笔记之滑动条QSlider和QProgressBar进度条 —— 2024-04-28 杭州 本例来自《Qt6 C开发指南》 文章目录 qt-C笔记之滑动条QSlider和QProgressBar进度条1.运行2.阅读笔记3.文件结构4.samp4_06.pro5.main.cpp6.widget.h7.widget.cpp8.widget.ui 1.运行 2.阅读笔记 3.文件结构…

ubuntu安装Anaconda安装及conda使用

一. 安装anaconda3详细教程 1、下载镜像 清华大学开源软件镜像站下载地址&#xff1a; https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 下拉到最低端选择Linux&#xff0c;选择最新版&#xff08;32/64位&#xff09;下载。这里我下载的是版本Anaconda3-4.3.30-Linux…