首页 > 其他分享 >Android kotlin泛型知识点梳理

Android kotlin泛型知识点梳理

时间:2022-11-04 11:35:36浏览次数:75  
标签:知识点 java kotlin List 类型 参数 泛型 实参


前言

学习知识需要提前设立目标,带着问题学习才能有的放矢。无论是java的泛型还是kotlin语言的泛型均是写框架,写通用工具类神器。如果不熟悉泛型语法,开发过程中将会遇到很多奇奇怪怪的问题。当然语言的高级特性肯定也理解不了。

本blog基于 《kotlin实战》 第九章泛型的理解而来

java 1.5 引入泛型,目的是运行时可以动态替换泛型参数的类型,泛型参数类型T在泛型内可以出现在{对象,属性,[方法形参,返回值]}位置, 泛型实参必须是引用类型 {class,interface,map,int[],set,list}

1.泛型函数和类的声明

kotlin 引入新概念:实化类型参数声明点变型使用点变形

实化类型参数:泛型函数的类型参数修用 refixed 饰符 如 :< refixed T> ,并且设置泛型函数为inline 内联函数,那么在运行时可以获取到泛型参数的泛型实参的具体类型。(普通的类和函数不行,非inline函数实参运行时类型信息会被擦除)

声明点变形: 可以说明一个带类型参数的泛型类型是否是另一个泛型类型的子类型,它们基础类型一致,类型参数不同

//声明的地方变型
interface Compare< A>{ }
interface Compare< B>{ }

使用点变形:可以达到和java通配符( ?)一样的效果

interface Compare{ 
//使用点变型
fun <T> compare(o1:T,o2:T){ }
}

1.1 泛型类型参数(泛型类)

class A<T>{

}
class B :A<String>()

如上:两个类A、B,类A后紧跟着尖括号中的T,称为类A的类型参数或者类型形参,而类B衍生自类A,并且对类A的泛型参数进行了实化,类B后面紧跟着类A的尖括号中的String类型,成为类型实参,也可以说成类B中对类A的类型参数使用String类型进行了实化。可以类比为参数的初始化赋值。

当然类型参数一个类不止可以声明一个类型参数,也可以声明N个,比方说kotlin的Functions.kt声明了22个之多:

//Functions.kt类
/** A function that takes 22 arguments. */
public interface Function22<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, in P22, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22): R
}

tips: java 语言允许使用没有类型参数的泛型类型(原生态类型)是因为java 1.5 才引入泛型,需要保证与老版兼容,而kotlin不支持原生类型,类型参数必须定义,因为kotlin一开始就支持泛型类型。

//①
ArrayList list=new ArrayList();
//②
val list: ArrayList<*> = ArrayList<Any?>()

1、2分别代表java原生类型,不带类型参数,②kotlin语言的等价写法必须定义类型参数

1.2 泛型函数和属性

fun <T> List<T>.slice(indices:IntRange):List<T>

class A<T>(val a: T) {

}

说明: 类型形参声明,接收者和返回值使用了类型形参T。泛型类型必须定义,如果系统可以推导出来就不需要手动指定泛型类型了。比如

val letter =('a' ..'z').toList()
//下面两种写法一种是未指定泛型实参类型,一种是指定了泛型实参,这是等价的,
//因为第二种系统可以通过letters集合中存储的值推导出来T的值是Char.
println(letters.slice<Char>(0..2))
println(letters.slice(0..2))

1.3 声明泛型类

kotlin通过在类名称后加上一对尖括号,并把类型参数放在尖括号中来声明泛型类或者
泛型接口的

class A<T>{}

interface List<T>{
operator fun get(index:Int):T
}

在接口内部T可以当做普通类型使用,如果你的类继承了泛型类,那么就需要用泛型实参来对泛型形参进行实化。类型实参可以是具体的类型或者另一个类型形参

class ArrayList<T> :List<T>{
override fun get(index:Int):T = ...
}

注意这里的ArrayList的类型形参T,和List 中的T不是一个T,名字可以都叫T,或者不叫T。叫其他的abc,Ac 都可以

class ArrayList<B> :List<T>{
override fun get(index:Int):T = ...
}

1.4 类型参数约束

为什么需要对泛型类型参数做约束,可以限制做为泛型类和泛型函数的类型实参的类型,限制List集合中只能添加衍生自某种类型的子类型或自身称为上界约束,限制集合中只能添加特定类型的超类称为下界约束,这里可以结合java泛型的声明:<? super T>和 <? extends T>

java写法:

<T extends Number> T sum(List<T> list)

<T super Number> T sum(List<T> list)

kotlin扩展函数写法:

fun<T:Number> List<T>.sum():T

极少数情况下需要在一个类型参数上指定多个约束,如果需要可以使用where关键字,

fun<T> ensureTrailingPeriod(seq:T):where T:CharSequence,T:Appendable{

}

这种情况作为类型的实参的参数类型必须同事实现CharSequence和Appendable两个接口。

1.5 让类型形参非空

默认情况下泛型类型T的类型是Any?,也就是泛型类型可以使用空值和非空值进行赋值。那么如何限制类型形参必须为非空值能,只需要显式对泛型参数做一个非空约束就可以实现。

//默认类型是T:Any?
class Processor<T:Any>{
fun test(value:T){
value.hashCode()
}
}

2.实例化类型参数和类型擦除

jvm上泛型一般是通过类型擦除实现的,泛型类型实例的类型实参在 运行时是不保留的。

2.1 类型检查和转换

kotlin的泛型在运行时也被擦除了,意味着泛型类实例不会携带用于创建它的类型实参的信息。

val list=listof(1,2,3)
val strList=listof("a","b","c")

在运行时list、和strList你不能知道他们是否声明成字符串,整数列表或者其他对象列表。因为运行时不附带任何类型实参信息。这么做的好处就是节省内存,内存中保存的类型信息更少。

那么使用 is List也是不可以的。但是可以星号投影来判断类型是否是一个List列表而不是Set列表。<*>类似Java中的<?> 表示拥有未知类型。

fun printSum(c:Collection<*>){
val intList=c as? List<Int>?:throw IllegalArgumentException("List is expected")
println(intList.sum())
}

fun main(args: Array<String>) {
printSum(listOf(1,2,3))
printSum(listOf("1","2","3"))
}

这个函数表示对集合求元素数,对第一种情况正常输出 6,而第二种情况对String类型的列表求和,就会提示类型转换异常

Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Number

如何避免这种异常情况呢?
kotlin有特殊语法结构可以允许你在函数体中使用具体的类型实参,但只有inline函数可以

2.2 声明实化类型参数的函数

inline fun <reified T> isA(value:Any)=value is T

如上isA函数被声明成了inline函数并且地泛型参数进行reified修饰。(reified 实化修饰符),reified声明了类型参数不会在运行时被擦除。

public inline fun <reified R> Iterable<*>.filterIsInstance(): List<@kotlin.internal.NoInfer R> {
return filterIsInstanceTo(ArrayList<R>())
}

fun main(args: Array<String>) {
println(listOf(1,2,"abc").filterIsInstance<String>())
// printSum(listOf("1","2","3"))
}

系统级别的实化api,遍历列表中元素,判断元素是否是指定类型的对象。

为什么实化只对内联函数有效?

因为内联函数的字节码码会被编译到调用处,每次你调用实化类型参数的函数时,编译器知道这次特定调用中用做类型实参的确切类型。调用的时候filterIsInstance< String>(),传递的类型实参。因为生成的字节码引用了具体的类,而不是类型参数,它不会的被运行时发生的类型参数擦除影响。

tips:
reified类型参数的inline函数不能在java代码中调用,普通内联可以像常规函数那样在java中调用,但是失去了内联的特性。带类型实化参数的函数需要特殊处理,把类型实参的值替换到字节码中。必须永远是内联的,所以不能用java普通的方式调用。

2.3 实化类型代替类引用及使用的限制

实化reified使用场景。

inline fun <reified T:Activity> Context.startActivity(){
val intent=Intent(this,T::class.java)
startActivity(intent)
}

实化类型T,在函数中可以当做具体类型使用

可以用在:

  1. 类型检查和类型转换中(is,as?)
  2. 使用kotlin的反射api
  3. 获取Class(::class.java)
  4. 作为调用其他函数的类型实参

不能做:

  1. 创建指定类型参数的类的实例
  2. 调用类型参数的伴生对象
  3. 调用实化类型的参数函数的时候使用非实化类型形参作类型实参
  4. 把类,属性或者非内联函数的类型参数标记成reified

3.变型:声明点变形和使用点变形

变型:用来描述具有相同的基础类型和不同的类型实参(泛型)的类型之间是如何关联的。例如List<String> 和List<Any>,正确理解变形有助于创建既不会以不方便的方式限制用户,也不会破坏用户所期望的类型安全

fun printContents(list:List<Any>){
println(list.joinToString())
}

fun main{
printContents(listof("a","b"))
}

>>>> a,b

函数把每个元素当成Any对待,因为字符串是Any,这是完全安全的。那么有一种情况需要考虑,如果形参是List函数接口类型还会安全吗?

fun printContents(list:MutiableList<Any>){
list.add(100)
}

这种就是不安全的类型,因为在函数中对List的元素进行了编辑.如果按照上面的方式进行调用就会报类型转换异常。

3.1 类、类型、子类型

变量的类型规定类该变量可能的值,有时候可以把类型和类看成同样的概念使用。但是他们并不会完全一样的。

非泛型类:可以直接使用类名作为类型来使用。

var x:String
var x:String?

一个kotlin类可以用于构造至少两种类型【可空类型】和【非空类型】

泛型类的情况就变得更复杂了,要得到一个合法的类型的需要一个的类型实参对类型形参进行替换。List不是一个类型,但下面列举出来的所有替代品都是合法的类型。List、List<List>,每一个泛型类都可能生成潜在的无线数量的类型。

子类型、超类型是一对反义词。放在具体的上下文语境中。任何时候如果需要的是类型A的值,都能够使用类型B的值进行替换。类型B就是类型A的子类型。反之类型A是类型B的超类型。编译器每一次给变量赋值或者给函数传递实参都要做这项检查(实参的类型是否是形参类型的子类型。)

子类型子类,本质上意味着是一样的事物,但是kotlin同一个类的可空类型和非空类型,并不遵循这个。A是A?的子类型。反过来不成立。

不变类型:一个泛型类 MutableList 如果对于任意两种类型A和B,MutableList 既不是MutableList 的子类型也不是它的超类型,MutableList就被称为在该类型参数上是不变型的。在kotlin语言环境下来说,java中的类都是不变型。(可读类,Mutiable修饰可读写类)

3.2 协变,逆变

协变:保留子类型关系化,修饰符:<out T>只能用在返回值位置
逆变:反转子类型化关系,修饰符<in T>,只能用在入参位置。

interface TransFormer<out T>{
fun transform(t:T):T
}

这里入参声明 t:T 是in 位置,返回值T 是out位置,类型参数T上关键字out有两层含义。

  1. 子类型化会被保留(Producer<Cat>是Producer<Animal>的子类型)
  2. T只能用在out位置

例如:Kotlin类型List的声明导致List是可读列表,不能对List进行add元素。

//系统声明 List<out T>保留子类型化会被保留
public interface List<out E> : Collection<E> {

}
//
interface Comparator<in T>{
fun compare(e1:T,e2:T):Int{}
}

协变

逆变

不变型

Producer< out T>

Consumer< in T>

MutableList< T>

类的子类型化关系保留了:Producer< Cat>是Producer< Animal>的子类型

子类型化反转了,Consumer< Animal>是Consumer< Cat>的子类型

没有子类型化

T只能在out位置

T只能在ini位置

T可以在in或者out位置或者其他任意位置

Tips:
kotlin 的表示法(P)->R 是表示Function<P,R>另一种可读性的形式,可以写成
<in P,out R>,意味着这个函数类型的第一个类型参数,子类型化反转了,第二个类型参数子类型化保留了。

3.3 使用点变型:在类型出现的地方指定变型。

声明点变型:在类声明的时候指定参数类型使用变形修饰符是很方便的。修饰符修饰过之后,会应用到所有类被使用的地方。
使用点变形:每次在使用带类型参数的类型时,可以指定类型餐宿是否可以用它的子类型或者超类型替换.比如java的通配符语法<? extends&gt,<?Super>;

kotlin也支持使用点变形,允许在类型参数出现的具体位置指定变形。(即使类型声明时不能被声明成协变逆变

MutableList既不是协变也不是逆变,因为它同时生产和消费指定为它们类型参数的类型的值,但是对于这个类型的变量来说常用的场景是,在某个特定函数中当成一种角色使用情况挺常见。

fun <T> copyData(source:Mutiable<T>,destination:MutableList<T>){
for(src in source){
destination.add(src)
}
}

要让函数支持不同类型的列表,可以引入第二个泛型参数

fun <T:R,R> copyData(source:Mutiable<T>,destination:MutableList<R>){
for(src in source){
destination.add(src)
}
}

可以使用变形修饰符更优雅的实现相同的功能

fun <T> copyData(source:Mutiable<out T>,destination:MutableList<int T>){
for(src in source){
destination.add(src)
}
}

tips:
kotlin 使用点变型直接对应java的限界通配符,kotlin中的MutableList<outT>对应java中的MutableList<? extends T>是一个意思,in 投影的MutableList<in T> 对应java的MutableList<? super T>,使用点变型有助于放宽可接收类型的范围。

3.4 型号投影:使用*代替类型参数

星号投影:表明不知道关于泛型实参的任何信息

MutiableList<> 与MutiableList<Any?>不一样,(MutiableList 在T上是不变类型的),MutiableList<Any?>可以包含任意类型的元素,MutiableList<>包含特定类型的元素,具体那种元素不需要关心。

MutiableList<*>中包含特殊类型的列表,具体类型不清楚,就导致一个问题,这种类型的集合只能读取不能写入任何元素(不知道可以存储那种类型,存入那种类型都会报错)

总结:

kotlin 的泛型和java相当接近,同样的方式来声明泛型类和泛型函数,kotlin泛型,类型实参也会在运行时被擦除,所以不能使用is运算符,可以将函数声明为inline ,类型参数标记为reified 实化后即可在函数运行时获取到泛型参数的类型实参来使用is判断。变型是描述拥有相同基类不同类型参数的泛型类之间子类型化关系的方式的。它说明其中一个泛型参数是另一个泛型参数的子类型,反之是超类型。可以声明一个类在某个类型参数上是协变的,如果该参数只用在out位置,逆变正好相反,只用在in位置,kotlin中List声明成协变,那么List< String> 是List< Any>的子类型。MutiableList函数类型,可以声明在第一个参数上逆变,第二个参数上协变。使(Animal)->Int 成为(Cat)->Number的子类型。kotlin中既可以为整个泛型类指定变型(声明点变形),也可以为泛型类型特定的使用指定变型(使用点变型);当确切的类型实参是未知或者不重要的时候,可以使用投影语法

​泛型类型与java类型之间的映射关系​​java泛型 类型T 和 通配符?关系


标签:知识点,java,kotlin,List,类型,参数,泛型,实参
From: https://blog.51cto.com/u_15861646/5823164

相关文章

  • Android kotlin 类委托 by,by lazy关键
    前言接触kotlin语言也有几年时间了。日常开发工作中也推荐使用kotlin,但是对于一些kotlin语言语法的细节没有进行系统学习。碎片的知识点让工作中屡屡碰壁,前些天开始学习comp......
  • vue组件通信6种方式总结(常问知识点)
    前言在Vue组件库开发过程中,Vue组件之间的通信一直是一个重要的话题,虽然官方推出的Vuex状态管理方案可以很好的解决组件之间的通信问题,但是在组件库内部使用Vuex往往会......
  • 一种基于字典传递的Go泛型翻译方法
    https://mp.weixin.qq.com/s/xrFqTVJbwc-iST2D9xPQ3w一种基于字典传递的Go泛型翻译方法原创 宋林海 字节跳动技术质量 2022-11-0317:00 发表于上海来自牛津大学(Nob......
  • Java 语音基础知识点 笔记
    Java语音基础知识点笔记(1)什么是变量?变量分为哪几类?String是最基本的数据类型吗?char型变量中能不能储存一个中文汉字?为什么?赋值语句“floatf=3.4;"是否正确?(2)Java中有没......
  • 11月3日内容总结——对象之动静态方法、继承及相关知识点、类中名称查找顺序及经典类
    目录一、动静态方法动态方法静态方法二、面向对象之继承的概念面向对象三大特性1.继承的含义2.继承的目的3.继承解决了什么问题4.多继承的优缺点5.继承的实操三、继承的本......
  • 计算机二级python备考刷题知识点总结(二)
    1、center()语法:str.center(width,fillchar)注:fillchar必须要用引号引起了center()返回一个原字符串居中,并使用填充字符填充到长度为width的新字符串,默认填充字符为空格......
  • 大数据常见知识点
    什么是算子在英文中被成为“Operation”,在数学上可以解释为一个函数空间到函数空间上的映射O:X->X,其实就是一个处理单元,往往是指一个函数,在使用算子时往往会有输入和......
  • golang 重要知识点(二)
    packagemainimport"fmt"typeMyStructstruct{ Namestring}func(sMyStruct)SetName1(namestring){ s.Name=name}func(s*MyStruct)SetName2(name......
  • java向上转型知识点收录
    packagetex2polymorphism;/*总结如下:*对于多态,可以总结它为:一、使用父类类型的引用指向子类的对象;二、该引用只能调用父类中定义的方法和变量;三、如果子类......
  • day24 JDBC批处理(通用泛型查询方法 & 下划线转驼峰命名法)
    批处理publicstaticIntegeraddBatch(String[]sqls){ init(); try{ //设置关闭自动提交 conn.setAutoCommit(false); Statementstmt=conn.createState......