文章目录

  • 泛型场景
    • 泛型递归和链式调用
    • 子类的泛型递归
    • 直接使用父类
  • Kotlin的问题与方式
    • 使用时必须指定类型的泛型的问题
    • Kotlin的方式

泛型场景

先由我们熟悉的Java说起,有时在使用泛型的时候,会有这样的场景。
比如我们封装一个参数类,里面提供一个放入参数的方法,并且方法返回类型为它自身,以便调用时进行链式调用。

泛型递归和链式调用

为了让它的子类在调用时也返回它自身,我们定义一个泛型继承于它,并且返回值为这个泛型。如下:

public class Params<T extends Params> extends HashMap<String, Object> {protected final T putParams(String key, Object value) {put(key, value);return (T) this;}
}

然后我们的子类,如下声明参数:

public class OrderParams extends Params<OrderParams> {public OrderParams page(int page) {return putParams("page", page);}public OrderParams pageSize(int size) {return putParams("page_items", size);}// ...
}

调用的时候就可以实现链式调用了:

final OrderParams params = new OrderParams().page(page).pageSize(Constants.PAGE_SIZE)//...

这是第一种场景。

子类的泛型递归

在上面的前提下,比如我们发现有许多参数类都要声明一个共同的参数,于是我们继承自Params再封装一个类,并且还是使用递归泛型,使得子类调用这个类声明的方法的时候能够返回子类的类型,以便一路链式调用。如下:

public class ParkParams<T extends ParkParams> extends Params<T> {public final T park(String parkCode) {return putParams("park_code", parkCode);}
}

这是第二种场景。

直接使用父类

在项目里,还有另一种场景:某个接口声明里,我们只需要Params类,不需要添加其他参数,所以不需要再去声明一个它的子类。
比如我们使用Retrofit时,声明如下的接口:

    @POST("/iop/auth/logout")@NeedTokenObservable<DataResponse> logout(@Body Params params);

这是一个注销退出的接口,需要传token,但是我们已经在实现了Converter<T, RequestBody>的类里实现了token的添加,这里我们只需要一个Params参数。
这是第三种场景。

这几种场景,使用Java如此实现是没有问题的,但是转成Kotlin代码问题却出来了。

Kotlin的问题与方式

使用时必须指定类型的泛型的问题

我们使用Idea的转成Kotlin代码的功能,将Params类转成Kotlin代码,如下:

open class Params<T : Params<*>> : HashMap<String, Any?>() {protected fun putParams(key: String, value: Any?): T {put(key, value)return this as T}
}

这是编译不过去的,因为Kotlin的泛型不能省略,所以原来Java代码里泛型里面的<T extends Params>就被转成了<T : Params<*>>,但Params已经约束了是有上界的,所以不能使用*,我们改为class Params<T : Params<T>>。这样看起来第一种场景是通过了。

但是我们刚才的ParkParams报错了,因为我们声明的是public class ParkParams<T extends ParkParams> extends Params<T>,但它需要T继承自Params。没关系,如上转为Kotlin代码就好了:

open class ParkParams<T : ParkParams<T>> : Params<T>() {fun park(parkCode: String): T {return putParams("park_code", parkCode)}
}

第二种场景也没问题。

现在第三种场景的问题来了。
我们调用logout(params)需要传一个Params参数,把调用代码转为Kotlin之后报错了,因为Params没有指定泛型,推断出来的是Params<*>,它想要的是Params<Params<*>>。但是我们把它改为Params<Params<*>>,里面的<params<*>>又报了这一个问题。好吧,这对Kotlin而言,是个无穷无尽的泛型递归问题。
这里的问题就在于,logout(params)接口是用Java声明的,没有指定泛型参数的类型,所以Kotlin这边推断出来的是Params<*>,而由于Params的泛型有其上界,所以Kotlin创建不了这个类型的实例。

我们把logout(params)接口的代码也转成kotlin,会发现接口的参数类型只能声明为Params<*>才能编译通过。但是会有两个问题:
一是使用的时候,上面的问题还是没有解决。所以只能传Params的其他子类,比如传个前面定义的OrderParams。暂时忽略这个不可理喻的调用,至少编译通过了。
二是运行的时候Retrofit报错了,因为它不允许参数的泛型类型为通配符。<*>就相当于Java中的<?>
所以Kotlin使用泛型来解决这些问题是走不通的。

Kotlin的方式

我们回顾一下,其实只是想要三个诉求:

  1. Params类封装一个putParams(key, value)的方法,方法能够返回它的子类。
  2. Params的子类能够封装一些方法,方法能返回它的子类的子类。
  3. Params能够被直接调用及创建它的实例。

Kotlin做不到吗?不是的。如果是递归泛型是Java相对于Kotlin的特性,那Kotlin也有相对于Java的语言特性可以实现以上的需求,那就是扩展函数。

现在我们使用扩展函数来实现前面的功能。
首先,我们的Params也不需要声明泛型了,它定义如下:

open class Params : HashMap<String, Any?>() {fun <T : Params> T.putParams(key: String, value: Any?): T {put(key, value)return this}
}

由于Params没有泛型参数,所以第三个种场景也就解决了。然后我们在Params类里声明一个扩展函数,在这里我们使用了泛型。我们让这个泛型有Params上界,返回值类型是这个泛型。这样第一种场景也解决了。

对于第二种场景,也是使用扩展函数来实现,但是这里的扩展函数需要声明在类的外面,否则无法调用到这个函数。

open class ParkParams : Params()fun <T : ParkParams> T.park(parkCode: String) = putParams("park_code", parkCode)

这样问题就解决了。
对比Java,我们也可以看到,Kotlin的实现看起来更简洁清晰。

Kotlin泛型上界与扩展函数相关推荐

  1. Kotlin学习:Kotlin泛型

    Kotlin 泛型 泛型,即 "参数化类型",将类型参数化,可以用在类,接口,方法上. 与 Java 一样,Kotlin 也提供泛型,为类型安全提供保证,消除类型强转的烦恼. 声明 ...

  2. Kotlin学习笔记(五) 扩展函数 扩展属性

    2019独角兽企业重金招聘Python工程师标准>>> 扩展函数和属性可以定义在对应的类中和kotlin文件中; 1.扩展函数 1.扩展函数可以由对象直接调用,在扩展函数中,用thi ...

  3. kotlin泛型_Kotlin泛型

    kotlin泛型 In this tutorial, we'll be looking in Kotlin Generics and Variances. 在本教程中,我们将研究Kotlin的泛型和变 ...

  4. 一起来学Kotlin:概念:7. Kotlin 函数介绍:扩展函数,中缀函数,运算符函数,带有varrag输入的函数

    Kotlin:概念:7. Kotlin 函数介绍:扩展函数,中缀函数,运算符函数,带有varrag输入的函数,内联函数 这里介绍Kotlin的不同种类函数,包括:一般的函数表达方式,扩展函数,中缀函数 ...

  5. Java泛型与Kotlin泛型

    正文 本文主要列举Java泛型与Kotlin泛型的基本知识,以及两者的区别. 什么泛型 泛型程序设计是程序设计的一种风格或或规范.简单的说就是该类型可变,在编写代码时可以根据情况设置不同的类型.因为泛 ...

  6. kotlin 泛型约束

    fun <T:Comparable<T>> sort(list :List<T>){} 冒号之后指定的类型就是泛型参数的上界,对于泛型参数T,只允许使用Compar ...

  7. 泛型---上界通配符和下界通配符

    泛型 简介 ​ java中的可变类型都是使用通配符(通常为大写字母)代替,例如public interface test<T>,其中的T就是通配符,只起到占位符的作用,可以是任意的大写字母 ...

  8. Kotlin 安利贴 2. 扩展函数

    Hello,本次More Time 带来 Kotlin 超好用的特性--扩展函数 . 扩展函数的意思就是可以给一个类加上一个新函数. 有人说:那直接在类里面加呗. 有种东西叫做别人的代码,只能用,不能 ...

  9. Android Kotlin 文件读写操作扩展函数,Raw资源读取,Assets资源读取

    1.保存内容到文件及文件读取 /*** 保存信息到File* @param fileName 文件名* @param content 保存内容* @param mode 模式*/ fun Contex ...

最新文章

  1. 三维重建 3D reconstruction 有哪些实用算法?
  2. bartlett方差齐性检验_基于R实现统计中的检验方法方差分析
  3. 多位诺贝尔奖获得者推荐的精品力作:《大数据时代的调查师》
  4. python抓取股票数据_Python自动获取当日所有股票数据
  5. C#一探究竟——枚举
  6. java_jdbc_基本连接池
  7. 使用HTML5技术控制电脑或手机上的摄像头(转载)
  8. java面试宝典第五版,《程序员面试宝典(第5版)》和《Java程序员面试宝典(第4版)》的一些看法......
  9. 什么是订单管理系统OMS
  10. 微信小程序中的转发功能
  11. 触摸屏 java_价值10W大洋的触摸屏技术揭秘。。。
  12. 共享计算机用户帐户限制怎么办,系统之家Win7系统无法共享提示用户账户限制怎么办...
  13. 苏州承泽医疗科技面经
  14. mysql主从同步错误:The slave I/O thread stops because master and slave have equal MySQL server UUIDs
  15. <医学断层图像三维重建面绘制算法的研究>——第一遍阅读
  16. select获取选中的option(包含value和text,重点是text怎么获取)
  17. ucharts折线图出现断点时画小圆圈(当线的width很大时,小圆圈会很大)
  18. 联发科射频工程师题目_MTK联发科技面试经验
  19. revit二次开发2016
  20. mysql_slow_详解MySQL中SlowLog的配置方法(图文)

热门文章

  1. 巴比特 | 元宇宙每日必读:企业如何将业务转移到元宇宙?这7大步骤了解一下...
  2. 基于SSM的医院预约挂号系统 JAVA MYSQL
  3. Android 10.0 SystemUI状态栏屏蔽掉通知栏不显示通知
  4. ldb文件matlab,MongoDB v4.2.2 安装与配置及常规操作
  5. 中国有机盐行业市场供需与战略研究报告
  6. 如何利用自己的数据制作社交地图?只显示可视区域内的标注
  7. 电能计量芯片HLW8110/HLW8112
  8. 大数据 (三) BI报表系统 superset 2 制作表格
  9. Winserver2016安装Exchange2016总结(一堆坑)
  10. 2018年最新从PayPal提现美金的方法(实战教程)!