kotlin 泛型out是什么??

kotlin 泛型out是什么??

前言:

   今天在阅读第三方框架底层源码时,用kotlin写的。遇到 ,当时就在想这是啥? 我知道Java里面定义限定类型用的是 , 用的通配符类型是 .。于是带着这个疑惑,去kotlin官方文档去查了一下泛型。并巩固了一下泛型的知识。

主要分几个部分来总结一下:

1. 泛型的定义

  • 泛型类
       在kotlin里面定义泛型,只需要在类的后面加上<> 即可,里面定义泛型 T、M、K 等等,如下面:
 /**
 *  @description 泛型类的定义
 *  @author 陈元
 *  @date  2020 07/11
 */
class Generate<T>(var t:T)

 /**
 *  @description 泛型类定义多个泛型
 *  @author 陈元
 *  @date  2020 07/11
 */
class GenerateMore<T,K>(val t:T,val k:K)
  • 泛型接口
    泛型接口和泛型类的定义语法没多大变化,都是在名字后面加 <>
 /**
 *  @description 泛型接口的定义
 *  @author 陈元
 *  @date  2020 07/11
 */
interface GenerateInter<T>{
     //使用传过来的泛型
     fun useT(t:T)
}

 /**
 *  @description 泛型接口,定义多个泛型
 *  @author 陈元
 *  @date  2020 07/11
 */
interface GenerateInterMore<T,M>{
     //使用传过来的泛型
     fun useT(t:T)
     //使用传过来的多个泛型
     fun useTM(t:T,m:M)
}
  • 泛型方法
/**
 * 4.泛型方法
 */
fun <T> generate(t:T){
     println()
}

/**
 * 4.定义多个泛型
 */
fun <T,L> generateMore(t:T,l:L){
     
}

好了,以上就是泛型的定义。

2. 泛型的限定类型变量

通过名字就知道,限定类型,即然是限定类型,那么肯定是限定在某一个类型之内,不能超过。
为了演示这个限定类型,我们来定义几个类。几个类的关系如图
kotlin 泛型out是什么??_第1张图片
代码示例:

/**
 *  @description 水果
 *  @author 陈元
 *  @date  2020 07/11
 */
open class Fruit{

   open fun name(){
        println("水果父亲")
    }
}
/**
 *  @description 苹果
 *  @author 陈元
 *  @date  2020 07/11
 */
open class Apple : Fruit() {

    override fun name() {
        println("苹果")
    }
    
}
/**
 *  @description 香蕉
 *  @author 陈元
 *  @date  2020 07/11
 */
class Banana : Fruit() {
    override fun name() {
        println("香蕉")
    }
}
/**
 *  @description 小苹果
 *  @author 陈元
 *  @date  2020 07/11
 */
class SmallApple : Apple() {
    override fun name() {
        println("小苹果")
    }
}

代码非常简单,我们在使用泛型类型限定。首先说一下语法 定义泛型时,使用 ,具体如下

/**
 * 1.泛型的类型限定
 */
class Generate<T:Apple>(var t:T)

通过这里,大家可以看到,我们在定义泛型T时,限定了我们传入的T只能是Apple类型,包括Apple的所有的孩子。
我们来测试下吧!

	//因为限定了类型,所以传String是不行的
    val generate = Generate("apple")
    
    // 传入apple肯定可以,这点我们都知道
    val generate2 = Generate(Apple())

    //虽然Apple是派生自Fruit,这里传入Fruit依然不行
    val generate1 = Generate(Fruit())
    
    //这边传入Apple的孩子,也是可以的
    val generate3 = Generate(SmallApple())

有人读到这里,说Java里泛型不仅可以限定一个类型,还可以限定多个类型,我们这里当然也是可以的了,我们来看看

**
 *  @description 泛型限定
 *  @author 陈元
 *  @date  2020 07/11
 */

/**
 * 1.泛型的类型限定,限定多个类型
 */
class Generate<T>(var t:T) where T:Apple,T:Serializable

使用呢,是不是都符合,我们的期望,这里我们限定的两个类型
**

  • 1.限定必须是Apple类型,或派生自Apple
  • 2.限定必须实现了Serializable序列化接口

**
我们测试一下吧
这里我们先试一下,没有实现Serial… 这个接口的,

  // 传入apple肯定可以,这点我们都知道
    val generate4 = Generate(Apple())

我们运行,发现编译器报错了
e: Test.kt: (21, 21): Type parameter bound for T in constructor Generate(t: T) where T : Serializable
is not satisfied: inferred type Apple is not a subtype of Serializable

看到这个错,都知道了吧。这边同时还需要实现Serializable 接口,就可以了。

泛型接口,和 泛型方法,在使用的时候,类型限定都是一样的,这里就不一一展开了,大家可以去阅读官方文档哦。
官方文档地址

3. 泛型的继承规则

这边直接上代码解释,我们在使用泛型或设计框架时,泛型在使用泛型类时去继承异常,编译器直接报错,是无法使用的。以下就是 典型的错误的例子

class GenerateTException<T> : Exception()

class GenerateTException2<T> : Throwable()

这里再说明一下,我们在使用泛型时,比如前面提到的 Apple 派生自 Fruit。本来在使用的时候是没有任何问题的。如下

val fruit:Fruit = Apple()

但是在使用泛型时,要注意,以下是错误代码

val generate:Generate<Fruit>  = Generate<Apple>(Apple())

虽然在apple 是派生自水果,但是水果 Generate<> 和 苹果 Generate<> 是没有任何关系的。

4. 泛型的通配符类型(只读/只写模式)

本文的重点来了,out 和 in 这两个 原来是 java 里的 《?extends 类名》 和 《?super类名》
这边呢,咱们直接上代码解释
out
1.首先,我们定义一个泛型,如下:

 /**
 *  @description 泛型
 *  @author 陈元
 *  @date  2020 07/11
 */
 class Generate<T>(var t:T)

2.我们再定义一个方法,来使用泛型的通配符类形

fun generateFunc(generate:Generate<out Apple>){
    println(generate.t.name())
}

这个方法的意思是,我接收一个Generate类型的参数,里面的泛型 out Apple表示,我这里接收的泛型只能是Apple和Apple的孩子或孙子。。,如代码所示:

  val generate = Generate(Fruit())

    val generate1 = Generate(Apple())

    val generate2 = Generate(SmallApple())
   generateFunc(generate) //这里报错,说明不可以传入 Fruit
   generateFunc(generate1)
    generateFunc(generate2)

只读模式
为什么这里,是只读模式呢,因为,在方法里面我们调用 generate.t.name() 这个方法的时候,我们可以获取。 但是如果我们,去重新赋值是不行的,如下

fun generateFunc(generate:Generate<out Apple>){
    generate.t = SmallApple() //这句话报错,只能读,不能重新赋值,
    println(generate.t.name()) 
}

in

现在,我们就把方法里面的out 换成 in,再来看看我们的调用

 val generate = Generate(Fruit())

    val generate1 = Generate(Apple())

    val generate2 = Generate(SmallApple())
   generateFunc(generate) 
   generateFunc(generate1)
    generateFunc(generate2) //这里报错,说明不可以传入 smallApple也就是孩子

里面的泛型 in Apple表示,我这里接收的泛型只能是Apple和Apple的父亲或爷爷。。。
只写模式

fun generateFunc(generate:Generate<in Apple>){
    generate.t = SmallApple() 
    println(generate.t.name()) //这句话报错,只能写,不能读,
}

5. 泛型的底层原理,虚拟机如何实现泛型?

知道了,泛型的使用后,知道泛型好神奇呀,那我们再看一看,一起探究一下,泛型底层的原理到底是怎么实现的,虚拟机是如何实现泛型的。我们还是通过代码来看看,如下

/**
 *  @description 探究泛型的底层原理
 *  @author 陈元
 *  @date  2020 07/11
 */
fun main() {

    val generate = Generate(Fruit())

    val generate1 = Generate(Apple())

    println(generate.javaClass)
    println(generate1.javaClass)
    println(generate.javaClass == generate1.javaClass)

}

这边我们简单起见,就定义了两个类,然后我们输出genrate和generate1的class类型对象,得出来结果如下:

G:\androidStudio\Android\jre\bin\java.exe -javaagent:G:\androidStudio\Android\lib\idea_rt.jar=58896:G:\androidStudio\Android\bin -Dfile.encoding=UTF-8 -classpath G:\androidStudio\AndroidStudioSDk\platforms\android-29\android.jar;G:\androidStudio\AndroidStudioSDk\platforms\android-29\data\res;F:\android-eclipse-workspace\MyApplication\app\build\intermediates\javac\debug\classes;F:\android-eclipse-workspace\MyApplication\app\build\tmp\kotlin-classes\debug;F:\android-eclipse-workspace\MyApplication\app\build\generated\res\resValues\debug;C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-android-extensions-runtime\1.3.50\bec16087637a7cafe54894e73d38037977cb30d2\kotlin-android-extensions-runtime-1.3.50.jar;C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-stdlib-jdk7\1.3.50\50ad05ea1c2595fb31b800e76db464d08d599af3\kotlin-stdlib-jdk7-1.3.50.jar;C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-stdlib\1.3.50\b529d1738c7e98bbfa36a4134039528f2ce78ebf\kotlin-stdlib-1.3.50.jar;C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-stdlib-common\1.3.50\3d9cd3e1bc7b92e95f43d45be3bfbcf38e36ab87\kotlin-stdlib-common-1.3.50.jar;C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\org.jetbrains\annotations\13.0\919f0dfe192fb4e063e7dacadee7f8bb9a2672a9\annotations-13.0.jar;C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\androidx.collection\collection\1.1.0\1f27220b47669781457de0d600849a5de0e89909\collection-1.1.0.jar;C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\androidx.lifecycle\lifecycle-common\2.1.0\c67e7807d9cd6c329b9d0218b2ec4e505dd340b7\lifecycle-common-2.1.0.jar;C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\androidx.arch.core\core-common\2.1.0\b3152fc64428c9354344bd89848ecddc09b6f07e\core-common-2.1.0.jar;C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\androidx.annotation\annotation\1.1.0\e3a6fb2f40e3a3842e6b7472628ba4ce416ea4c8\annotation-1.1.0.jar;C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\androidx.constraintlayout\constraintlayout-solver\1.1.3\54abe9ffb22cc9019b0b6fcc10f185cc4e67b34e\constraintlayout-solver-1.1.3.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\b8f10ae6f5b38c660c7334f34edc8667\core-ktx-1.2.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\31016c6c0856b517b84ecfbcfb815ff0\appcompat-1.1.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\31016c6c0856b517b84ecfbcfb815ff0\appcompat-1.1.0\res;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\6ad38dfac4e106a4ff4e5e048c9d7854\cursoradapter-1.0.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\9c96e3c7610a71f64d933abf5d989c41\fragment-1.1.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\3c6026bf367a81ae0b26f6a0755e7c5c\appcompat-resources-1.1.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\3c6026bf367a81ae0b26f6a0755e7c5c\appcompat-resources-1.1.0\res;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\9eeb03172080be4be809eb3b789602a2\drawerlayout-1.0.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\d81313297acd4b62a12e1c4be346ea86\viewpager-1.0.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\d4d6f1b618f6740b3852fbc0a4f6708f\loader-1.0.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\47bf1c1672b530aea741a2298486de75\activity-1.0.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\8042381f1240df5f98ac8c5a464644dd\vectordrawable-animated-1.1.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\2485cb75ca766726d2f85318526894e3\vectordrawable-1.1.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\b403c837ac7d55e559d7b654aa580176\customview-1.0.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\415a936aa630178fa8f04bbb1a3bb075\core-1.2.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\415a936aa630178fa8f04bbb1a3bb075\core-1.2.0\res;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\5aa6e1ee8a4aa87ed09e4136bb1359d6\versionedparcelable-1.1.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\8be5234bafda9ab14ed7f88c9dd40d77\lifecycle-viewmodel-2.1.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\d5a39d8f51fcfc1b19c3ec4132b72e21\lifecycle-runtime-2.1.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\621e01429d79a08e6530f2907ad873bf\savedstate-1.0.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\e05a230acf535e3dec39f0f39ed5f0ca\interpolator-1.0.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\004fb91108e1b1927119709e2ff43030\lifecycle-livedata-2.0.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\3a3ed459510bf208b94d56b861c80196\lifecycle-livedata-core-2.0.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\ab93a8dbadb16e1d4464b7fe897665b9\core-runtime-2.0.0\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\c4b4b829cc770abdd306eef91b3e77f9\constraintlayout-1.1.3\jars\classes.jar;C:\Users\Administrator\.gradle\caches\transforms-2\files-2.1\c4b4b829cc770abdd306eef91b3e77f9\constraintlayout-1.1.3\res com.myapplication.TestKt
class com.myapplication.Generate
class com.myapplication.Generate
true

Process finished with exit code 0

这就尴尬了,明明是两个对象,为什么拿到的class对象为同一个呢。我们带着这个疑问,继续深究,我们去看一下底层的字节码 如下

 public final static main()V
   L0
    LINENUMBER 8 L0
    NEW com/myapplication/Generate
    DUP
    NEW com/myapplication/Fruit
    DUP
    INVOKESPECIAL com/myapplication/Fruit.<init> ()V  //这边首先初始化了Fruit
    INVOKESPECIAL com/myapplication/Generate.<init> (Ljava/lang/Object;)V  //这边初始化Generate时泛型的类型 竟然是object.
    ASTORE 0
   L1
    LINENUMBER 10 L1
    NEW com/myapplication/Generate
    DUP
    NEW com/myapplication/Apple
    DUP
    INVOKESPECIAL com/myapplication/Apple.<init> ()V
    INVOKESPECIAL com/myapplication/Generate.<init> (Ljava/lang/Object;)V
    ASTORE 1

我嚓,我嚓,什么鬼,我们看一下, 请看注释处,我们首先初始化了Fruit对象,然后我们初始化对象Generate 时,泛型变成Objcet了。再一看呢,最后第二行,初始化对象Generate时 泛型同样也变成Object了
泛型嚓除
原来虚拟机在实现泛型的时候,先把类型给嚓除了,变成了Object .

你可能感兴趣的:(Kotlin,泛型out是什么??)