90%人不懂的泛型局限性,泛型擦除,星投影

全文分为 视频版文字版

  • 文字版: 文字侧重细节和深度,有些知识点,视频不好表达,文字描述的更加准确
  • 视频版: 视频会更加的直观,看完文字版,在看视频,知识点会更加清楚

视频版 bilibili 地址: https://b23.tv/fGXnKn1

在之前的文章 90%的人都不懂的泛型,泛型的缺陷和应用场景 中介绍了:

  • 为什么要有泛型
  • Kotlin 和 Java 的协变和逆变的区别和应用场景,
  • Java 数组协变的缺陷
  • 通配符 <? extends><? super><out><in> 的区别和应用场景

而今天这篇文章我们主要介绍泛型擦除和它的局限性,所以通过这篇文章你将学习到以下内容:

  • 能直接实例化泛型吗?
  • 泛型被擦除之后,一定会编译成 Object 类型吗?
  • 为什么无法获取泛型 Class 类型?
  • 为什么要擦除掉泛型?
  • 泛型信息被擦除了之后,泛型信息真的不存在了吗?
  • 迷惑的通配符 ? 号和星投影

泛型擦除我相信对于每个开发者并不陌生,先写一段示例代码,实例化泛型 <T>,我们花三秒钟思考一下,下面的代码是否可以正常编译。

// Java
public class GenericPersonJava<T> {
    GenericPersonJava() {
        T t = new T()
    }
}

//Kotlin
class GenericPersonKt<T> {
    init {
        val t: T = T()
    }
}

编译会出错,因为 <T> 被擦除之后,会将 <T> 编译成 Object, JVM 指令如下图所示。

如果传入的泛型参数是 String, 那么泛型被擦除之后,会编译成 Object 类型,类型显然不对,无论是 Java 还是 Kotlin 在编译阶段都无法确定泛型参数的类型,所以为了防止类型问题,编译器不支持直接对泛型实例化,这也是泛型的局限性,不能直接对泛型实例化

如果想实例化泛型,Java 和 Kotlin 通用解决方案,通过 Class 反射的方式来实例化泛型 <T>,我们修改一下上面的代码,即可正常编译运行。

// Java
public class GenericPersonJava<T> {
    GenericPersonJava(Class<T> classes) {
        T t = classes.newInstance();
    }
}

// kotlin
class GenericPersonKt<T>(classes: Class<T>) {  
    init {  
        val t: T = classes.newInstance()  
    }  
}

我们在思考一下泛型 <T> 被擦除之后,一定会编译成 Object 类型吗? 修改一下上面的代码,给泛型添加上界,在花 3 秒钟思考一下编译后的泛型 <T> 是什么类型。

// Java
public class GenericPersonJava<T extends String> {

}

// Kotlin
class GenericPersonKt<T : String>() {

}

修改后的代码,给泛型添加了上界,即泛型 <T> 继承自 String,泛型被擦除之后,会被编译成 String 类型,如下图所示。

因此我们可以得出一个结论,无论是 Java 还是 Kotin:

  • 如果泛型没有指定上界 <T>,泛型被擦除之后,会被编译成 Object 类型
  • 如果泛型指定了上界,例如 <T : String>,泛型被擦除之后,会被编译成 String 类型

无法获取泛型 Class 类型

我们在来介绍一下泛型另外一个局限性无法获取泛型 Class 类型,先写一段代码,我们在花 3 秒钟思考一下,以下代码输出的结果是什么。

// Java
public static void main(String... args) {
    List<Integer> p1 = new ArrayList<Integer>();
    List<Double> p2 = new ArrayList<Double>();
    System.out.println(p1.getClass() == p2.getClass());
}

// Kotlin
fun main() {
    val p1: List<Int> = ArrayList<Int>()
    val p2: List<Double> = ArrayList<Double>()
    println(p1.javaClass == p2.javaClass)
}

上面的代码 Java 和 Kotlin 输出的结果都是 true,因为泛型被擦除了之后,无论 ArrayList<Integer> 还是 ArrayList<Double> 获取 class 的时候,获取到的都是同一个 ArrayList class

所以对于一个泛型 ArrayList<T> 无论 <T> 是什么类型,编译完了之后都会被擦除掉,最后获取到的都是 ArrayList class 而不是 ArrayList<T> class

为什么要擦除掉泛型?

泛型是 Java 1.5 之后引入的,在之前的版本是没有泛型这个概念,所以为了兼容之前的版本,因此在生成字节码的时候,将泛型信息擦除掉了。

泛型信息被擦除,真的不存在了吗

我们在思考一下泛型信息被擦除了之后,泛型信息真的不存在了吗?我们先写一段代码,看一下编译后的 JVM 指令。

public class GenericJava {
    public static void main(String... args) {
        ArrayList<Integer> p1 = new ArrayList<Integer>();
    }
}

生成的 JVM 指令如下图所示。

标记 1 执行 New 命令创建了 ArrayList 对象,而不是 ArrayList<Integer>,由此可见泛型信息被擦除了。我们继续往下看有个 LocalVariableTableLocalVariableTypeTable

  • LocalVariableTable 就是我们常说的局部变量表,保存方法参数列表和方法内的局部变量,按照声明的顺序存储,它以数组的形式展示,更多关于局部变量表操作数栈的知识点,欢迎前往查看另外一个篇文章 CPU 如何记录函数调用过程和返回过程

  • LocalVariableTypeTable 它的数据结构和 LocalVariableTable 是一样的,只不过它是用来保存泛型信息

正如图中 标记 2 所示,我们在代码中编写的泛型 ArrayList<Integer>,泛型信息被擦除掉之后,会保存到 LocalVariableTypeTable 中,所以并没有真正意义上的将泛型相关的信息抹除掉。正因为泛型信息被保存了下来,所以我们在运行时,可以通过反射获取到泛型相关的信息。

无论使用 Java 还是 Kotlin, 定义在类型的上的泛型、定义在方法参数的泛型,定义在方法返回值的泛型、定义在局部变量的泛型、还是定义在全局变量中的泛型,它们的泛型信息都会保存下来。如下图所示。

? 号和星投影

Koltin 中的星投影,即泛型 <*>,其实等效于 Java 中的 <?> 号,用于表示不确定泛型是什么类型的信息。我们来看一段代码。

// Java
List<?> list;

// Kotlin
val list: List<*>

上面的代码其实等效于下面的代码。

// Java
List<? extends Object> list;

// Koltin
val list: List<out Any>
  • Java 中的通配符 <?> 号等效于 <? extends Object>
  • Kotlin 中的通配符 <*> 号等效于 <out Any>

在 Java 中 Object 类是所有类的父类,在 Kotlin 中分为非空可空两种类型,因此 Any 是所有非空类型的父类,而 Any? 是所有可空类型的父类。因此我们可以用 ObjectAny 来表示。

在 Java 中用通配符 ? extends 表示协变,而在 Kotlin 中关键字 out 表示协变,关于协变和逆变更多的知识点,可以前往查看 90%的人都不知道的知识点,Kotlin 和 Java 的协变和逆变


全文到这里就结束了,感谢你的阅读,坚持原创不易,欢迎在看、点赞、分享给身边的小伙伴,我会持续分享原创干货!!!



近期必读热门文章

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容