专栏首页中国Android研究院听了他讲的泛型,我就明白为什么他的工资比我多30万了!

听了他讲的泛型,我就明白为什么他的工资比我多30万了!

阅读文本大概需要 6 分钟。

1

类 型 擦 除

Java是怎么实现泛型的?不错,类型擦除。Java编译器将源码编译成字节码的时候会将你在源码中声明的类型进行擦除,比如:

List<String> list = new ArrayList<String>();

在编译后,字节码中只有List,运行在JVM中也是一样的,那你可能会有疑问,既然将类型擦除了,那为什么我声明的泛型为String类型时,不能往里add一个整型的数据呢?这是因为编译器在编译前会进行类型检查,类型不一致会直接编译报错。 一般作为初级工程师知道这些就算合格了。

我们往深一层研究下,难道我们一定不能往声明泛型为String的list中增加一个整型元素吗?聪明的同学可能想到了,既然是在编译前检查类型的,编译后又将类型擦除了,那我是不是可以在运行时通过反射将整型数字add进去?不错!确实是可以的。

List<Long> list = new ArrayList<Long>();
list.add(100L);
list.getClass().getMethod("add", Object.class).invoke(list, "abc");

// 然后我们将list打印出来
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

打印出来确实是可以的,但是我能改成下面这样吗?

List<Long> list = new ArrayList<Long>();
list.add(100L);
list.getClass().getMethod("add", Object.class).invoke(list, "abc");

Long a = 0L;
// 然后我们将list打印出来
for (int i = 0; i < list.size(); i++) {
    a += list.get(i);
}

当然不能啦,当然如果你想的比较仔细,会发现一个很好玩的现象。对于上面这个list中第二个元素为"abc",那我们分别以不同的类型来赋值:

String s = "";
Long a = 0L;
s = list.get(1);
a = list.get(1);

无论你是用String还是Long类型来接收都会报错,你用String来接收会报"Long cannot be converted to String"。你用Long类型来赋值会报"String cannot be cast to java.lang.Long",你是不是感到迷茫了,其实这一切都是因为类型擦除,对于不能使用String类型来接收是因为编译器会做检查,因为list声明的泛型是Long类型的,而你使用String类型来赋值显然编译器会报错,第二种你使用Long类型来接收,编译器当然会认为是合法的,但是在运行的时候,list中的第二个实际值是String,强转成Long当然会报错了。这一切的根源是你使用反射向list中放入了一个和声明不同类型的数据。正常我们一般也不会这么做啦,这里只是验证一下这个机制而已。

好了,解释了这么多类型擦除的机制,那Java使用类型擦除来实现泛型有什么好处呢?

1、第一点我们将如此多的泛型在编译时擦除了,那么在运行时显然可以省不少的内存空间嘛。

2、第二点不得不说下兼容性,Java是在1.5版本推出的泛型,那1.5之前存在大量的线上代码没有泛型的,总不能舍弃吧,所以编译擦除后和没有泛型不是一样吗,这就兼容了之前更老的Java版本。

如果到这里你基本上都会的话,我觉得完全具有中级工程师的能力了。

2

类 型 擦 除 带 来 的 问 题

任何设计都会有自己的优点和缺点,在了解类型擦除的优点之后,我们也要剖析下类型擦除存在的现实问题:

1、不能使用基本数据类型

对于基本数据类型我们必须使用它的装箱类,那在我们使用过程中必然会平凡的涉及到拆箱和装箱的操作,这必定带来一定的资源开销,所以谷歌在针对key是int类型的情况下,使用SparseArray来代替HashMap。

2、不能用来方法的重载

为什么呢?举个例子:

如上图所示,在不同的泛型作为参数时,编译器编译时进行类型擦除,那参数不就一样了吗?那还谈什么重载呢!而C#没有进行类型擦除,所以编译完后是带有泛型的类型的,所以可以当作是重载的。

3、泛型类型不能当作真实的类型使用

上图中展示了5种使用方式,除了第四种Java能正常使用,其他Java都不能使用,而C#完全没问题。

4、静态方法无法引用类的泛型类型

Java中的泛型是类实例化的时候才能确定泛型的准确类型,而静态方法是不需要类实例化就能调用的,显然不能使用。

5、类型强转的开销

在Java1.5之前的版本,如上图所示,必须要进行强转才能使用自己想要的类型。 那Java1.5及以后的版本呢?

有兴趣的可以看看ArrayList的源码,它的get方法还是会做强转的。

如果到这里你都知道,说明你对泛型以及类型擦除了解的还是比较系统,完全具备高工的潜质。

本文分享自微信公众号 - IT烂笔头(nj_android),作者:手艺人

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-12-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 你说你是高工,String有多长也不知道?

    String类是由final修饰的,所以是不能被继承的①,我们在对字符串进行比较时,一般是期望对比其中的字符串是否一样,所以这里我们不能用"=="进行字符串的比...

    吴延宝
  • 面向切面编程(AOP)在Android中的应用

    AOP,字面翻译为面向切面编程。它是一种编程思想,不是什么新技术。可以这么理解,在Android开发过程中,我们经常会在我们的具体业务代码中加入全局性、系统性的...

    吴延宝
  • 高工做CPU架构适配的心得体会

    如上图所示,armabi的库可以运行在x86、x86-64以及armabi-v7a和armabi-v8a的CPU架构上,从下往上的方向上,下方架构的so库可以兼...

    吴延宝
  • Java 泛型,你了解类型擦除吗?

    大家可能会有疑问,我为什么叫做泛型是一个守门者。这其实是我个人的看法而已,我的意思是说泛型没有其看起来那么深不可测,它并不神秘与神奇。泛型是 Java 中一个很...

    Frank909
  • 揭开Java 泛型类型擦除神秘面纱

    大家可能会有疑问,我为什么叫做泛型是一个守门者。这其实是我个人的看法而已,我的意思是说泛型没有其看起来那么深不可测,它并不神秘与神奇。泛型是 Java 中一个很...

    java思维导图
  • 关于泛型,你可能不知道的事儿

    大家可能会有疑问,我为什么叫做泛型是一个守门者。这其实是我个人的看法而已,我的意思是说泛型没有其看起来那么深不可测,它并不神秘与神奇。泛型是 Java 中一个很...

    黄泽杰
  • Java泛型,你了解类型擦除吗?

    大家可能会有疑问,我为什么叫做泛型是一个守门者。这其实是我个人的看法而已,我的意思是说泛型没有其看起来那么深不可测,它并不神秘与神奇。泛型是 Java 中一个很...

    Java团长
  • 转:Java 泛型,你了解类型擦除吗?

    大家可能会有疑问,我为什么叫做泛型是一个守门者。这其实是我个人的看法而已,我的意思是说泛型没有其看起来那么深不可测,它并不神秘与神奇。泛型是 Java 中一个很...

    剑影啸清寒
  • 揭开Java 泛型类型擦除神秘面纱

    大家可能会有疑问,我为什么叫做泛型是一个守门者。这其实是我个人的看法而已,我的意思是说泛型没有其看起来那么深不可测,它并不神秘与神奇。泛型是 Java 中一个很...

    Java技术栈
  • Ways to Use Icons on Android (2)

    上一节提到,如果项目中很多自定义的图标,或者是各种不同来源的图标,我们可以通过对Iconify进行扩展来实现,但是在扩展之前我们需要制作自己的图标字体文件,那么...

    宅男潇涧

扫码关注云+社区

领取腾讯云代金券

玩转腾讯云 有奖征文活动